aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVsevolod Stakhov <vsevolod@rambler-co.ru>2008-05-08 19:05:30 +0400
committerVsevolod Stakhov <vsevolod@rambler-co.ru>2008-05-08 19:05:30 +0400
commit70c78281e3957bdbaa1204e40f2419a4a1918eb7 (patch)
tree29720ef474513c582f8d1faa855b2e81c5e1d3ae
downloadrspamd-70c78281e3957bdbaa1204e40f2419a4a1918eb7.tar.gz
rspamd-70c78281e3957bdbaa1204e40f2419a4a1918eb7.zip
* Add skeleton
-rw-r--r--Makefile.in30
-rw-r--r--cfg_file.h90
-rw-r--r--cfg_file.l174
-rw-r--r--cfg_file.y194
-rw-r--r--cfg_utils.c144
-rw-r--r--compat/md5.c331
-rw-r--r--compat/md5.h53
-rw-r--r--compat/queue.h618
-rw-r--r--compat/strlcpy.c70
-rw-r--r--compat/strlcpy.h8
-rwxr-xr-xconfigure516
-rw-r--r--main.c324
-rw-r--r--main.h71
-rw-r--r--memcached.c743
-rw-r--r--memcached.h113
-rw-r--r--upstream.c521
-rw-r--r--upstream.h43
-rw-r--r--util.c534
-rw-r--r--util.h53
-rw-r--r--worker.c52
20 files changed, 4682 insertions, 0 deletions
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 000000000..5685d6c32
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,30 @@
+
+all: $(TARGETS)
+
+memctest: upstream.c memcached.c memcached-test.c
+ $(CC) $(OPT_FLAGS) $(CFLAGS) $(PTHREAD_CFLAGS) -c upstream.c
+ $(CC) $(OPT_FLAGS) $(CFLAGS) $(PTHREAD_CFLAGS) -c memcached.c
+ $(CC) $(OPT_FLAGS) $(CFLAGS) $(PTHREAD_CFLAGS) -c memcached-test.c
+ $(CC) $(OPT_FLAGS) $(PTHREAD_LDFLAGS) $(LD_PATH) upstream.o memcached.o memcached-test.o $(LIBS) -o memcached-test
+
+install: $(EXEC) rmilter.8 rmilter.conf.sample
+ $(INSTALL) -b $(EXEC) $(PREFIX)/sbin/$(EXEC)
+ $(INSTALL) -v $(EXEC).sh $(PREFIX)/etc/rc.d
+ #$(INSTALL) -m0644 rspamd.8 $(MANPATH)/man8
+ #$(INSTALL) -m0644 rspamd.conf.sample $(PREFIX)/etc
+ $(MKDIR) -o $(RSPAMD_USER) -g $(RSPAMD_GROUP) /var/run/rspamd
+
+clean:
+ rm -f *.o $(EXEC) *.core
+ rm -f cfg_lex.c cfg_yacc.c cfg_yacc.h
+
+dist-clean: clean
+ rm -f Makefile
+ rm -f config.log
+ rm -f md5.h md5.c strlcpy.h strlcpy.c queue.h
+
+creategroup:
+ @echo "Create group $(RSPAMD_GROUP) before installing!"
+
+createuser:
+ @echo "Create user $(RSPAMD_USER) before installing!"
diff --git a/cfg_file.h b/cfg_file.h
new file mode 100644
index 000000000..53e0a7677
--- /dev/null
+++ b/cfg_file.h
@@ -0,0 +1,90 @@
+/*
+ * $Id$
+ */
+
+
+#ifndef CFG_FILE_H
+#define CFG_FILE_H
+
+#include <sys/types.h>
+#ifndef OWN_QUEUE_H
+#include <sys/queue.h>
+#else
+#include "queue.h"
+#endif
+#include <netinet/in.h>
+#include <sys/un.h>
+#include "upstream.h"
+#include "memcached.h"
+
+#define DEFAULT_BIND_PORT 768
+#define MAX_MEMCACHED_SERVERS 48
+#define DEFAULT_MEMCACHED_PORT 11211
+/* Memcached timeouts */
+#define DEFAULT_MEMCACHED_CONNECT_TIMEOUT 1000
+/* Upstream timeouts */
+#define DEFAULT_UPSTREAM_ERROR_TIME 10
+#define DEFAULT_UPSTREAM_ERROR_TIME 10
+#define DEFAULT_UPSTREAM_DEAD_TIME 300
+#define DEFAULT_UPSTREAM_MAXERRORS 10
+
+/* 1 worker by default */
+#define DEFAULT_WORKERS_NUM 1
+
+#define yyerror(fmt, ...) \
+ fprintf (stderr, "Config file parse error!\non line: %d\n", yylineno); \
+ fprintf (stderr, "while reading text: %s\nreason: ", yytext); \
+ fprintf (stderr, fmt, ##__VA_ARGS__); \
+ fprintf (stderr, "\n")
+#define yywarn(fmt, ...) \
+ fprintf (stderr, "Config file parse warning!\non line %d\n", yylineno); \
+ fprintf (stderr, "while reading text: %s\nreason: ", yytext); \
+ fprintf (stderr, fmt, ##__VA_ARGS__); \
+ fprintf (stderr, "\n")
+
+
+enum { VAL_UNDEF=0, VAL_TRUE, VAL_FALSE };
+
+struct memcached_server {
+ struct upstream up;
+ struct in_addr addr;
+ uint16_t port;
+ short alive;
+ short int num;
+};
+
+struct config_file {
+ char *cfg_name;
+ char *pid_file;
+ char *temp_dir;
+
+ char *bind_host;
+ struct in_addr bind_addr;
+ uint16_t bind_port;
+ uint16_t bind_family;
+
+ char no_fork;
+ unsigned int workers_number;
+
+ struct memcached_server memcached_servers[MAX_MEMCACHED_SERVERS];
+ size_t memcached_servers_num;
+ memc_proto_t memcached_protocol;
+ unsigned int memcached_error_time;
+ unsigned int memcached_dead_time;
+ unsigned int memcached_maxerrors;
+ unsigned int memcached_connect_timeout;
+};
+
+int add_memcached_server (struct config_file *cf, char *str);
+int parse_bind_line (struct config_file *cf, char *str);
+void init_defaults (struct config_file *cfg);
+void free_config (struct config_file *cfg);
+
+int yylex (void);
+int yyparse (void);
+void yyrestart (FILE *);
+
+#endif /* ifdef CFG_FILE_H */
+/*
+ * vi:ts=4
+ */
diff --git a/cfg_file.l b/cfg_file.l
new file mode 100644
index 000000000..453cb6992
--- /dev/null
+++ b/cfg_file.l
@@ -0,0 +1,174 @@
+%x incl
+
+%{
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include "cfg_file.h"
+#include "cfg_yacc.h"
+
+#define MAX_INCLUDE_DEPTH 10
+YY_BUFFER_STATE include_stack[MAX_INCLUDE_DEPTH];
+int include_stack_ptr = 0;
+
+static size_t
+parse_limit (const char *limit)
+{
+ size_t result = 0;
+ char *err_str;
+
+ if (!limit || *limit == '\0') return 0;
+
+ result = strtoul (limit, &err_str, 10);
+
+ if (*err_str != '\0') {
+ /* Megabytes */
+ if (*err_str == 'm' || *err_str == 'M') {
+ result *= 1048576L;
+ }
+ /* Kilobytes */
+ else if (*err_str == 'k' || *err_str == 'K') {
+ result *= 1024;
+ }
+ /* Gigabytes */
+ else if (*err_str == 'g' || *err_str == 'G') {
+ result *= 1073741824L;
+ }
+ }
+
+ return result;
+}
+
+static unsigned int
+parse_seconds (const char *t)
+{
+ unsigned int result = 0;
+ char *err_str;
+
+ if (!t || *t == '\0') return 0;
+
+ result = strtoul (t, &err_str, 10);
+
+ if (*err_str != '\0') {
+ /* Seconds */
+ if (*err_str == 's' || *err_str == 'S') {
+ result *= 1000;
+ }
+ }
+
+ return result;
+}
+
+static char
+parse_flag (const char *str)
+{
+ if (!str || !*str) return -1;
+
+ if ((*str == 'Y' || *str == 'y') && *(str + 1) == '\0') {
+ return 1;
+ }
+
+ if ((*str == 'Y' || *str == 'y') &&
+ (*(str + 1) == 'E' || *(str + 1) == 'e') &&
+ (*(str + 2) == 'S' || *(str + 2) == 's') &&
+ *(str + 3) == '\0') {
+ return 1;
+ }
+
+ if ((*str == 'N' || *str == 'n') && *(str + 1) == '\0') {
+ return 0;
+ }
+
+ if ((*str == 'N' || *str == 'n') &&
+ (*(str + 1) == 'O' || *(str + 1) == 'o') &&
+ *(str + 2) == '\0') {
+ return 0;
+ }
+
+ return -1;
+}
+
+%}
+
+%option noyywrap
+%option yylineno
+
+%%
+^[ \t]*#.* /* ignore comments */;
+.include BEGIN(incl);
+tempdir return TEMPDIR;
+pidfile return PIDFILE;
+workers return WORKERS;
+error_time return ERROR_TIME;
+dead_time return DEAD_TIME;
+maxerrors return MAXERRORS;
+reconnect_timeout return RECONNECT_TIMEOUT;
+connect_timeout return CONNECT_TIMEOUT;
+protocol return PROTOCOL;
+memcached return MEMCACHED;
+bind_socket return BINDSOCK;
+servers return SERVERS;
+
+\{ return OBRACE;
+\} return EBRACE;
+; return SEMICOLON;
+, return COMMA;
+= return EQSIGN;
+yes|YES|no|NO|[yY]|[nN] yylval.flag=parse_flag(yytext); return FLAG;
+\n /* ignore EOL */;
+[ \t]+ /* ignore whitespace */;
+\"[^"]+\" yylval.string=strdup(yytext + 1); yylval.string[strlen(yylval.string) - 1] = '\0'; return QUOTEDSTRING;
+\" return QUOTE;
+[0-9]+ yylval.number=strtol(yytext, NULL, 10); return NUMBER;
+[0-9]+[kKmMgG]? yylval.limit=parse_limit(yytext); return SIZELIMIT;
+[0-9]+[sS]|[0-9]+[mM][sS] yylval.seconds=parse_seconds(yytext); return SECONDS;
+[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3} yylval.string=strdup(yytext); return IPADDR;
+[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2} yylval.string=strdup(yytext); return IPNETWORK;
+[a-zA-Z<][a-zA-Z@+>_-]* yylval.string=strdup(yytext); return STRING;
+\/[^/\n]+\/ yylval.string=strdup(yytext); return REGEXP;
+[a-zA-Z0-9].[a-zA-Z0-9\/.-]+ yylval.string=strdup(yytext); return DOMAIN;
+[a-zA-Z0-9.-]+:[0-9]{1,5} yylval.string=strdup(yytext); return HOSTPORT;
+[a-zA-Z0-9\/.-]+ yylval.string=strdup(yytext); return FILENAME;
+<incl>[ \t]* /* eat the whitespace */
+<incl>[^ \t\n]+ { /* got the include file name */
+ if ( include_stack_ptr >= MAX_INCLUDE_DEPTH ) {
+ yyerror ("yylex: includes nested too deeply" );
+ return -1;
+ }
+
+ include_stack[include_stack_ptr++] =
+ YY_CURRENT_BUFFER;
+
+ yyin = fopen( yytext, "r" );
+
+ if ( ! yyin ) {
+ yyerror("yylex: cannot open include file");
+ return -1;
+ }
+
+ yy_switch_to_buffer(
+ yy_create_buffer( yyin, YY_BUF_SIZE ) );
+
+ BEGIN(INITIAL);
+ }
+
+<<EOF>> {
+ if ( --include_stack_ptr < 0 )
+ {
+ yyterminate();
+ }
+
+ else
+ {
+ yy_delete_buffer( YY_CURRENT_BUFFER );
+ yy_switch_to_buffer(
+ include_stack[include_stack_ptr] );
+ }
+ }
+
+%%
+/*
+ * vi:ts=4
+ */
diff --git a/cfg_file.y b/cfg_file.y
new file mode 100644
index 000000000..b4675f077
--- /dev/null
+++ b/cfg_file.y
@@ -0,0 +1,194 @@
+/* $Id$ */
+
+%{
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <syslog.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "cfg_file.h"
+
+#define YYDEBUG 0
+
+extern struct config_file *cfg;
+extern int yylineno;
+extern char *yytext;
+
+
+%}
+%union
+{
+ char *string;
+ size_t limit;
+ char flag;
+ unsigned int seconds;
+ unsigned int number;
+}
+
+%token ERROR STRING QUOTEDSTRING FLAG
+%token FILENAME REGEXP QUOTE SEMICOLON OBRACE EBRACE COMMA EQSIGN
+%token BINDSOCK SOCKCRED DOMAIN IPADDR IPNETWORK HOSTPORT NUMBER CHECK_TIMEOUT
+%token MAXSIZE SIZELIMIT SECONDS BEANSTALK MYSQL USER PASSWORD DATABASE
+%token TEMPDIR PIDFILE SERVERS ERROR_TIME DEAD_TIME MAXERRORS CONNECT_TIMEOUT PROTOCOL RECONNECT_TIMEOUT
+%token READ_SERVERS WRITE_SERVER DIRECTORY_SERVERS MAILBOX_QUERY USERS_QUERY LASTLOGIN_QUERY
+%token MEMCACHED WORKERS
+
+%type <string> STRING
+%type <string> QUOTEDSTRING
+%type <string> FILENAME
+%type <string> SOCKCRED
+%type <string> IPADDR IPNETWORK
+%type <string> HOSTPORT
+%type <string> DOMAIN
+%type <limit> SIZELIMIT
+%type <flag> FLAG
+%type <seconds> SECONDS
+%type <number> NUMBER
+%type <string> memcached_hosts bind_cred
+%%
+
+file : /* empty */
+ | file command SEMICOLON { }
+ ;
+
+command :
+ bindsock
+ | tempdir
+ | pidfile
+ | memcached
+ | workers
+ ;
+
+tempdir :
+ TEMPDIR EQSIGN FILENAME {
+ cfg->temp_dir = $3;
+ }
+ ;
+
+pidfile :
+ PIDFILE EQSIGN FILENAME {
+ cfg->pid_file = $3;
+ }
+ ;
+
+bindsock:
+ BINDSOCK EQSIGN bind_cred {
+ if (!parse_bind_line (cfg, $3)) {
+ yyerror ("yyparse: parse_bind_line");
+ YYERROR;
+ }
+ free ($3);
+ }
+ ;
+
+bind_cred:
+ STRING {
+ $$ = $1;
+ }
+ | IPADDR{
+ $$ = $1;
+ }
+ | DOMAIN {
+ $$ = $1;
+ }
+ | HOSTPORT {
+ $$ = $1;
+ }
+ | FILENAME {
+ $$ = $1;
+ }
+ ;
+
+memcached:
+ BEANSTALK OBRACE memcachedbody EBRACE
+ ;
+
+memcachedbody:
+ memcachedcmd SEMICOLON
+ | memcachedbody memcachedcmd SEMICOLON
+ ;
+
+memcachedcmd:
+ memcached_servers
+ | memcached_connect_timeout
+ | memcached_error_time
+ | memcached_dead_time
+ | memcached_maxerrors
+ | memcached_protocol
+ ;
+
+memcached_servers:
+ SERVERS EQSIGN memcached_server
+ ;
+
+memcached_server:
+ memcached_params
+ | memcached_server COMMA memcached_params
+ ;
+
+memcached_params:
+ memcached_hosts {
+ if (!add_memcached_server (cfg, $1)) {
+ yyerror ("yyparse: add_memcached_server");
+ YYERROR;
+ }
+ free ($1);
+ }
+ ;
+memcached_hosts:
+ STRING
+ | IPADDR
+ | DOMAIN
+ | HOSTPORT
+ ;
+memcached_error_time:
+ ERROR_TIME EQSIGN NUMBER {
+ cfg->memcached_error_time = $3;
+ }
+ ;
+memcached_dead_time:
+ DEAD_TIME EQSIGN NUMBER {
+ cfg->memcached_dead_time = $3;
+ }
+ ;
+memcached_maxerrors:
+ MAXERRORS EQSIGN NUMBER {
+ cfg->memcached_maxerrors = $3;
+ }
+ ;
+memcached_connect_timeout:
+ CONNECT_TIMEOUT EQSIGN SECONDS {
+ cfg->memcached_connect_timeout = $3;
+ }
+ ;
+
+memcached_protocol:
+ PROTOCOL EQSIGN STRING {
+ if (strncasecmp ($3, "udp", sizeof ("udp") - 1) == 0) {
+ cfg->memcached_protocol = UDP_TEXT;
+ }
+ else if (strncasecmp ($3, "tcp", sizeof ("tcp") - 1) == 0) {
+ cfg->memcached_protocol = TCP_TEXT;
+ }
+ else {
+ yyerror ("yyparse: cannot recognize protocol: %s", $3);
+ YYERROR;
+ }
+ }
+ ;
+workers:
+ WORKERS EQSIGN NUMBER {
+ cfg->workers_number = $3;
+ }
+ ;
+%%
+/*
+ * vi:ts=4
+ */
diff --git a/cfg_utils.c b/cfg_utils.c
new file mode 100644
index 000000000..d682310b0
--- /dev/null
+++ b/cfg_utils.c
@@ -0,0 +1,144 @@
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libmilter/mfapi.h>
+#include <sys/queue.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <syslog.h>
+#include <netdb.h>
+#include <math.h>
+
+#include "cfg_file.h"
+#include "memcached.h"
+
+extern int yylineno;
+extern char *yytext;
+
+
+int
+add_memcached_server (struct config_file *cf, char *str)
+{
+ char *cur_tok, *err_str;
+ struct memcached_server *mc;
+ struct hostent *he;
+ uint16_t port;
+
+ if (str == NULL) return 0;
+
+ cur_tok = strsep (&str, ":");
+
+ if (cur_tok == NULL || *cur_tok == '\0') return 0;
+
+ if(cf->memcached_servers_num == MAX_MEMCACHED_SERVERS) {
+ yywarn ("yyparse: maximum number of memcached servers is reached %d", MAX_MEMCACHED_SERVERS);
+ }
+
+ mc = &cf->memcached_servers[cf->memcached_servers_num];
+ if (mc == NULL) return 0;
+ /* cur_tok - server name, str - server port */
+ if (str == NULL) {
+ port = DEFAULT_MEMCACHED_PORT;
+ }
+ else {
+ port = (uint16_t)strtoul (str, &err_str, 10);
+ if (*err_str != '\0') {
+ return 0;
+ }
+ }
+
+ if (!inet_aton (cur_tok, &mc->addr)) {
+ /* Try to call gethostbyname */
+ he = gethostbyname (cur_tok);
+ if (he == NULL) {
+ return 0;
+ }
+ else {
+ memcpy((char *)&mc->addr, he->h_addr, sizeof(struct in_addr));
+ }
+ }
+ mc->port = port;
+ cf->memcached_servers_num++;
+ return 1;
+}
+
+int
+parse_bind_line (struct config_file *cf, char *str)
+{
+ char *cur_tok, *err_str;
+ struct hostent *he;
+ size_t s;
+
+ if (str == NULL) return 0;
+
+ cur_tok = strsep (&str, ":");
+
+ if (cur_tok[0] == '/' || cur_tok[0] == '.') {
+ cf->bind_host = strdup (cur_tok);
+ cf->bind_family = AF_UNIX;
+ return 1;
+
+ } else {
+ if (str == '\0') {
+ cf->bind_port = DEFAULT_BIND_PORT;
+ }
+ else {
+ cf->bind_port = (uint16_t)strtoul (str, &err_str, 10);
+ if (*err_str != '\0') {
+ return 0;
+ }
+ }
+
+ if (!inet_aton (cur_tok, &cf->bind_addr)) {
+ /* Try to call gethostbyname */
+ he = gethostbyname (cur_tok);
+ if (he == NULL) {
+ return 0;
+ }
+ else {
+ cf->bind_host = strdup (cur_tok);
+ memcpy((char *)&cf->bind_addr, he->h_addr, sizeof(struct in_addr));
+ s = strlen (cur_tok) + 1;
+ }
+ }
+
+ cf->bind_family = AF_INET;
+
+ return 1;
+ }
+
+ return 0;
+}
+
+void
+init_defaults (struct config_file *cfg)
+{
+ cfg->memcached_error_time = DEFAULT_UPSTREAM_ERROR_TIME;
+ cfg->memcached_dead_time = DEFAULT_UPSTREAM_DEAD_TIME;
+ cfg->memcached_maxerrors = DEFAULT_UPSTREAM_MAXERRORS;
+ cfg->memcached_protocol = TCP_TEXT;
+
+ cfg->workers_number = DEFAULT_WORKERS_NUM;
+}
+
+void
+free_config (struct config_file *cfg)
+{
+ if (cfg->pid_file) {
+ free (cfg->pid_file);
+ }
+ if (cfg->temp_dir) {
+ free (cfg->temp_dir);
+ }
+ if (cfg->bind_host) {
+ free (cfg->bind_host);
+ }
+}
+
+/*
+ * vi:ts=4
+ */
diff --git a/compat/md5.c b/compat/md5.c
new file mode 100644
index 000000000..76c1f375e
--- /dev/null
+++ b/compat/md5.c
@@ -0,0 +1,331 @@
+/*
+ * MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
+ *
+ * Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+ * rights reserved.
+ *
+ * License to copy and use this software is granted provided that it
+ * is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+ * Algorithm" in all material mentioning or referencing this software
+ * or this function.
+ *
+ * License is also granted to make and use derivative works provided
+ * that such works are identified as "derived from the RSA Data
+ * Security, Inc. MD5 Message-Digest Algorithm" in all material
+ * mentioning or referencing the derived work.
+ *
+ * RSA Data Security, Inc. makes no representations concerning either
+ * the merchantability of this software or the suitability of this
+ * software for any particular purpose. It is provided "as is"
+ * without express or implied warranty of any kind.
+ *
+ * These notices must be retained in any copies of any part of this
+ * documentation and/or software.
+ *
+ * This code is the same as the code published by RSA Inc. It has been
+ * edited for clarity and style only.
+ */
+
+#include <sys/cdefs.h>
+
+#include <sys/types.h>
+
+#include <string.h>
+
+#ifdef HAVE_ENDIAN_H
+#include <endian.h>
+#endif
+#ifdef HAVE_MACHINE_ENDIAN_H
+#include <machine/endian.h>
+#endif
+#include "md5.h"
+
+static void MD5Transform(u_int32_t [4], const unsigned char [64]);
+
+#if (BYTE_ORDER == LITTLE_ENDIAN)
+#define Encode memcpy
+#define Decode memcpy
+#else
+
+/*
+ * Encodes input (u_int32_t) into output (unsigned char). Assumes len is
+ * a multiple of 4.
+ */
+
+static void
+Encode (unsigned char *output, u_int32_t *input, unsigned int len)
+{
+ unsigned int i;
+ u_int32_t *op = (u_int32_t *)output;
+
+ for (i = 0; i < len / 4; i++)
+ op[i] = htole32(input[i]);
+}
+
+/*
+ * Decodes input (unsigned char) into output (u_int32_t). Assumes len is
+ * a multiple of 4.
+ */
+
+static void
+Decode (u_int32_t *output, const unsigned char *input, unsigned int len)
+{
+ unsigned int i;
+ const u_int32_t *ip = (const u_int32_t *)input;
+
+ for (i = 0; i < len / 4; i++)
+ output[i] = le32toh(ip[i]);
+}
+#endif
+
+static unsigned char PADDING[64] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* F, G, H and I are basic MD5 functions. */
+#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))
+#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/* ROTATE_LEFT rotates x left n bits. */
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n))))
+
+/*
+ * FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
+ * Rotation is separate from addition to prevent recomputation.
+ */
+#define FF(a, b, c, d, x, s, ac) { \
+ (a) += F ((b), (c), (d)) + (x) + (u_int32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define GG(a, b, c, d, x, s, ac) { \
+ (a) += G ((b), (c), (d)) + (x) + (u_int32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define HH(a, b, c, d, x, s, ac) { \
+ (a) += H ((b), (c), (d)) + (x) + (u_int32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+#define II(a, b, c, d, x, s, ac) { \
+ (a) += I ((b), (c), (d)) + (x) + (u_int32_t)(ac); \
+ (a) = ROTATE_LEFT ((a), (s)); \
+ (a) += (b); \
+ }
+
+/* MD5 initialization. Begins an MD5 operation, writing a new context. */
+
+void
+MD5Init (context)
+ MD5_CTX *context;
+{
+
+ context->count[0] = context->count[1] = 0;
+
+ /* Load magic initialization constants. */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xefcdab89;
+ context->state[2] = 0x98badcfe;
+ context->state[3] = 0x10325476;
+}
+
+/*
+ * MD5 block update operation. Continues an MD5 message-digest
+ * operation, processing another message block, and updating the
+ * context.
+ */
+
+void
+MD5Update (context, in, inputLen)
+ MD5_CTX *context;
+ const void *in;
+ unsigned int inputLen;
+{
+ unsigned int i, idx, partLen;
+ const unsigned char *input = in;
+
+ /* Compute number of bytes mod 64 */
+ idx = (unsigned int)((context->count[0] >> 3) & 0x3F);
+
+ /* Update number of bits */
+ if ((context->count[0] += ((u_int32_t)inputLen << 3))
+ < ((u_int32_t)inputLen << 3))
+ context->count[1]++;
+ context->count[1] += ((u_int32_t)inputLen >> 29);
+
+ partLen = 64 - idx;
+
+ /* Transform as many times as possible. */
+ if (inputLen >= partLen) {
+ memcpy((void *)&context->buffer[idx], (const void *)input,
+ partLen);
+ MD5Transform (context->state, context->buffer);
+
+ for (i = partLen; i + 63 < inputLen; i += 64)
+ MD5Transform (context->state, &input[i]);
+
+ idx = 0;
+ }
+ else
+ i = 0;
+
+ /* Buffer remaining input */
+ memcpy ((void *)&context->buffer[idx], (const void *)&input[i],
+ inputLen-i);
+}
+
+/*
+ * MD5 padding. Adds padding followed by original length.
+ */
+
+void
+MD5Pad (context)
+ MD5_CTX *context;
+{
+ unsigned char bits[8];
+ unsigned int idx, padLen;
+
+ /* Save number of bits */
+ Encode (bits, context->count, 8);
+
+ /* Pad out to 56 mod 64. */
+ idx = (unsigned int)((context->count[0] >> 3) & 0x3f);
+ padLen = (idx < 56) ? (56 - idx) : (120 - idx);
+ MD5Update (context, PADDING, padLen);
+
+ /* Append length (before padding) */
+ MD5Update (context, bits, 8);
+}
+
+/*
+ * MD5 finalization. Ends an MD5 message-digest operation, writing the
+ * the message digest and zeroizing the context.
+ */
+
+void
+MD5Final (digest, context)
+ unsigned char digest[16];
+ MD5_CTX *context;
+{
+ /* Do padding. */
+ MD5Pad (context);
+
+ /* Store state in digest */
+ Encode (digest, context->state, 16);
+
+ /* Zeroize sensitive information. */
+ memset ((void *)context, 0, sizeof (*context));
+}
+
+/* MD5 basic transformation. Transforms state based on block. */
+
+static void
+MD5Transform (state, block)
+ u_int32_t state[4];
+ const unsigned char block[64];
+{
+ u_int32_t a = state[0], b = state[1], c = state[2], d = state[3], x[16];
+
+ Decode (x, block, 64);
+
+ /* Round 1 */
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+ FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */
+ FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */
+ FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */
+ FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */
+ FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */
+ FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */
+ FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */
+ FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */
+ FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */
+ FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */
+ FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */
+ FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */
+ FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */
+ FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */
+ FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */
+ FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */
+
+ /* Round 2 */
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+ GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */
+ GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */
+ GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */
+ GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */
+ GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */
+ GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */
+ GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */
+ GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */
+ GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */
+ GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */
+ GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */
+ GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */
+ GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */
+ GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */
+ GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */
+ GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */
+
+ /* Round 3 */
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+ HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */
+ HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */
+ HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */
+ HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */
+ HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */
+ HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */
+ HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */
+ HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */
+ HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */
+ HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */
+ HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */
+ HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */
+ HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */
+ HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */
+ HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */
+ HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */
+
+ /* Round 4 */
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+ II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */
+ II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */
+ II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */
+ II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */
+ II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */
+ II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */
+ II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */
+ II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */
+ II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */
+ II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */
+ II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */
+ II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */
+ II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */
+ II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */
+ II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */
+ II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */
+
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+
+ /* Zeroize sensitive information. */
+ memset ((void *)x, 0, sizeof (x));
+}
diff --git a/compat/md5.h b/compat/md5.h
new file mode 100644
index 000000000..b07c6f0b8
--- /dev/null
+++ b/compat/md5.h
@@ -0,0 +1,53 @@
+/* MD5.H - header file for MD5C.C
+ * $FreeBSD: src/sys/sys/md5.h,v 1.20 2006/03/15 19:47:12 andre Exp $
+ */
+
+/*-
+ Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
+rights reserved.
+
+License to copy and use this software is granted provided that it
+is identified as the "RSA Data Security, Inc. MD5 Message-Digest
+Algorithm" in all material mentioning or referencing this software
+or this function.
+
+License is also granted to make and use derivative works provided
+that such works are identified as "derived from the RSA Data
+Security, Inc. MD5 Message-Digest Algorithm" in all material
+mentioning or referencing the derived work.
+
+RSA Data Security, Inc. makes no representations concerning either
+the merchantability of this software or the suitability of this
+software for any particular purpose. It is provided "as is"
+without express or implied warranty of any kind.
+
+These notices must be retained in any copies of any part of this
+documentation and/or software.
+ */
+
+#ifndef _SYS_MD5_H_
+#define _SYS_MD5_H_
+
+#define MD5_BLOCK_LENGTH 64
+#define MD5_DIGEST_LENGTH 16
+#define MD5_DIGEST_STRING_LENGTH (MD5_DIGEST_LENGTH * 2 + 1)
+
+/* MD5 context. */
+typedef struct MD5Context {
+ u_int32_t state[4]; /* state (ABCD) */
+ u_int32_t count[2]; /* number of bits, modulo 2^64 (lsb first) */
+ unsigned char buffer[64]; /* input buffer */
+} MD5_CTX;
+
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+void MD5Init (MD5_CTX *);
+void MD5Update (MD5_CTX *, const void *, unsigned int);
+void MD5Final (unsigned char [16], MD5_CTX *);
+char * MD5End(MD5_CTX *, char *);
+char * MD5File(const char *, char *);
+char * MD5FileChunk(const char *, char *, off_t, off_t);
+char * MD5Data(const void *, unsigned int, char *);
+__END_DECLS
+#endif /* _SYS_MD5_H_ */
diff --git a/compat/queue.h b/compat/queue.h
new file mode 100644
index 000000000..d62afcc84
--- /dev/null
+++ b/compat/queue.h
@@ -0,0 +1,618 @@
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)queue.h 8.5 (Berkeley) 8/20/94
+ * $FreeBSD: src/sys/sys/queue.h,v 1.68 2006/10/24 11:20:29 ru Exp $
+ */
+
+#ifndef _SYS_QUEUE_H_
+#define _SYS_QUEUE_H_
+
+#include <sys/cdefs.h>
+
+/*
+ * This file defines four types of data structures: singly-linked lists,
+ * singly-linked tail queues, lists and tail queues.
+ *
+ * A singly-linked list is headed by a single forward pointer. The elements
+ * are singly linked for minimum space and pointer manipulation overhead at
+ * the expense of O(n) removal for arbitrary elements. New elements can be
+ * added to the list after an existing element or at the head of the list.
+ * Elements being removed from the head of the list should use the explicit
+ * macro for this purpose for optimum efficiency. A singly-linked list may
+ * only be traversed in the forward direction. Singly-linked lists are ideal
+ * for applications with large datasets and few or no removals or for
+ * implementing a LIFO queue.
+ *
+ * A singly-linked tail queue is headed by a pair of pointers, one to the
+ * head of the list and the other to the tail of the list. The elements are
+ * singly linked for minimum space and pointer manipulation overhead at the
+ * expense of O(n) removal for arbitrary elements. New elements can be added
+ * to the list after an existing element, at the head of the list, or at the
+ * end of the list. Elements being removed from the head of the tail queue
+ * should use the explicit macro for this purpose for optimum efficiency.
+ * A singly-linked tail queue may only be traversed in the forward direction.
+ * Singly-linked tail queues are ideal for applications with large datasets
+ * and few or no removals or for implementing a FIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ *
+ *
+ * SLIST LIST STAILQ TAILQ
+ * _HEAD + + + +
+ * _HEAD_INITIALIZER + + + +
+ * _ENTRY + + + +
+ * _INIT + + + +
+ * _EMPTY + + + +
+ * _FIRST + + + +
+ * _NEXT + + + +
+ * _PREV - - - +
+ * _LAST - - + +
+ * _FOREACH + + + +
+ * _FOREACH_SAFE + + + +
+ * _FOREACH_REVERSE - - - +
+ * _FOREACH_REVERSE_SAFE - - - +
+ * _INSERT_HEAD + + + +
+ * _INSERT_BEFORE - + - +
+ * _INSERT_AFTER + + + +
+ * _INSERT_TAIL - - + +
+ * _CONCAT - - + +
+ * _REMOVE_HEAD + - + -
+ * _REMOVE + + + +
+ *
+ */
+#ifdef QUEUE_MACRO_DEBUG
+/* Store the last 2 places the queue element or head was altered */
+struct qm_trace {
+ char * lastfile;
+ int lastline;
+ char * prevfile;
+ int prevline;
+};
+
+#define TRACEBUF struct qm_trace trace;
+#define TRASHIT(x) do {(x) = (void *)-1;} while (0)
+
+#define QMD_TRACE_HEAD(head) do { \
+ (head)->trace.prevline = (head)->trace.lastline; \
+ (head)->trace.prevfile = (head)->trace.lastfile; \
+ (head)->trace.lastline = __LINE__; \
+ (head)->trace.lastfile = __FILE__; \
+} while (0)
+
+#define QMD_TRACE_ELEM(elem) do { \
+ (elem)->trace.prevline = (elem)->trace.lastline; \
+ (elem)->trace.prevfile = (elem)->trace.lastfile; \
+ (elem)->trace.lastline = __LINE__; \
+ (elem)->trace.lastfile = __FILE__; \
+} while (0)
+
+#else
+#define QMD_TRACE_ELEM(elem)
+#define QMD_TRACE_HEAD(head)
+#define TRACEBUF
+#define TRASHIT(x)
+#endif /* QUEUE_MACRO_DEBUG */
+
+/*
+ * Singly-linked List declarations.
+ */
+#define SLIST_HEAD(name, type) \
+struct name { \
+ struct type *slh_first; /* first element */ \
+}
+
+#define SLIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define SLIST_ENTRY(type) \
+struct { \
+ struct type *sle_next; /* next element */ \
+}
+
+/*
+ * Singly-linked List functions.
+ */
+#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
+
+#define SLIST_FIRST(head) ((head)->slh_first)
+
+#define SLIST_FOREACH(var, head, field) \
+ for ((var) = SLIST_FIRST((head)); \
+ (var); \
+ (var) = SLIST_NEXT((var), field))
+
+#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = SLIST_FIRST((head)); \
+ (var) && ((tvar) = SLIST_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \
+ for ((varp) = &SLIST_FIRST((head)); \
+ ((var) = *(varp)) != NULL; \
+ (varp) = &SLIST_NEXT((var), field))
+
+#define SLIST_INIT(head) do { \
+ SLIST_FIRST((head)) = NULL; \
+} while (0)
+
+#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
+ SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \
+ SLIST_NEXT((slistelm), field) = (elm); \
+} while (0)
+
+#define SLIST_INSERT_HEAD(head, elm, field) do { \
+ SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \
+ SLIST_FIRST((head)) = (elm); \
+} while (0)
+
+#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
+
+#define SLIST_REMOVE(head, elm, type, field) do { \
+ if (SLIST_FIRST((head)) == (elm)) { \
+ SLIST_REMOVE_HEAD((head), field); \
+ } \
+ else { \
+ struct type *curelm = SLIST_FIRST((head)); \
+ while (SLIST_NEXT(curelm, field) != (elm)) \
+ curelm = SLIST_NEXT(curelm, field); \
+ SLIST_NEXT(curelm, field) = \
+ SLIST_NEXT(SLIST_NEXT(curelm, field), field); \
+ } \
+ TRASHIT((elm)->field.sle_next); \
+} while (0)
+
+#define SLIST_REMOVE_HEAD(head, field) do { \
+ SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \
+} while (0)
+
+/*
+ * Singly-linked Tail queue declarations.
+ */
+#define STAILQ_HEAD(name, type) \
+struct name { \
+ struct type *stqh_first;/* first element */ \
+ struct type **stqh_last;/* addr of last next element */ \
+}
+
+#define STAILQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).stqh_first }
+
+#define STAILQ_ENTRY(type) \
+struct { \
+ struct type *stqe_next; /* next element */ \
+}
+
+/*
+ * Singly-linked Tail queue functions.
+ */
+#define STAILQ_CONCAT(head1, head2) do { \
+ if (!STAILQ_EMPTY((head2))) { \
+ *(head1)->stqh_last = (head2)->stqh_first; \
+ (head1)->stqh_last = (head2)->stqh_last; \
+ STAILQ_INIT((head2)); \
+ } \
+} while (0)
+
+#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
+
+#define STAILQ_FIRST(head) ((head)->stqh_first)
+
+#define STAILQ_FOREACH(var, head, field) \
+ for((var) = STAILQ_FIRST((head)); \
+ (var); \
+ (var) = STAILQ_NEXT((var), field))
+
+
+#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = STAILQ_FIRST((head)); \
+ (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define STAILQ_INIT(head) do { \
+ STAILQ_FIRST((head)) = NULL; \
+ (head)->stqh_last = &STAILQ_FIRST((head)); \
+} while (0)
+
+#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \
+ if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+ STAILQ_NEXT((tqelm), field) = (elm); \
+} while (0)
+
+#define STAILQ_INSERT_HEAD(head, elm, field) do { \
+ if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+ STAILQ_FIRST((head)) = (elm); \
+} while (0)
+
+#define STAILQ_INSERT_TAIL(head, elm, field) do { \
+ STAILQ_NEXT((elm), field) = NULL; \
+ *(head)->stqh_last = (elm); \
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+} while (0)
+
+#define STAILQ_LAST(head, type, field) \
+ (STAILQ_EMPTY((head)) ? \
+ NULL : \
+ ((struct type *)(void *) \
+ ((char *)((head)->stqh_last) - __offsetof(struct type, field))))
+
+#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
+
+#define STAILQ_REMOVE(head, elm, type, field) do { \
+ if (STAILQ_FIRST((head)) == (elm)) { \
+ STAILQ_REMOVE_HEAD((head), field); \
+ } \
+ else { \
+ struct type *curelm = STAILQ_FIRST((head)); \
+ while (STAILQ_NEXT(curelm, field) != (elm)) \
+ curelm = STAILQ_NEXT(curelm, field); \
+ if ((STAILQ_NEXT(curelm, field) = \
+ STAILQ_NEXT(STAILQ_NEXT(curelm, field), field)) == NULL)\
+ (head)->stqh_last = &STAILQ_NEXT((curelm), field);\
+ } \
+ TRASHIT((elm)->field.stqe_next); \
+} while (0)
+
+#define STAILQ_REMOVE_HEAD(head, field) do { \
+ if ((STAILQ_FIRST((head)) = \
+ STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \
+ (head)->stqh_last = &STAILQ_FIRST((head)); \
+} while (0)
+
+/*
+ * List declarations.
+ */
+#define LIST_HEAD(name, type) \
+struct name { \
+ struct type *lh_first; /* first element */ \
+}
+
+#define LIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define LIST_ENTRY(type) \
+struct { \
+ struct type *le_next; /* next element */ \
+ struct type **le_prev; /* address of previous next element */ \
+}
+
+/*
+ * List functions.
+ */
+
+#if (defined(_KERNEL) && defined(INVARIANTS))
+#define QMD_LIST_CHECK_HEAD(head, field) do { \
+ if (LIST_FIRST((head)) != NULL && \
+ LIST_FIRST((head))->field.le_prev != \
+ &LIST_FIRST((head))) \
+ panic("Bad list head %p first->prev != head", (head)); \
+} while (0)
+
+#define QMD_LIST_CHECK_NEXT(elm, field) do { \
+ if (LIST_NEXT((elm), field) != NULL && \
+ LIST_NEXT((elm), field)->field.le_prev != \
+ &((elm)->field.le_next)) \
+ panic("Bad link elm %p next->prev != elm", (elm)); \
+} while (0)
+
+#define QMD_LIST_CHECK_PREV(elm, field) do { \
+ if (*(elm)->field.le_prev != (elm)) \
+ panic("Bad link elm %p prev->next != elm", (elm)); \
+} while (0)
+#else
+#define QMD_LIST_CHECK_HEAD(head, field)
+#define QMD_LIST_CHECK_NEXT(elm, field)
+#define QMD_LIST_CHECK_PREV(elm, field)
+#endif /* (_KERNEL && INVARIANTS) */
+
+#define LIST_EMPTY(head) ((head)->lh_first == NULL)
+
+#define LIST_FIRST(head) ((head)->lh_first)
+
+#define LIST_FOREACH(var, head, field) \
+ for ((var) = LIST_FIRST((head)); \
+ (var); \
+ (var) = LIST_NEXT((var), field))
+
+#define LIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = LIST_FIRST((head)); \
+ (var) && ((tvar) = LIST_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define LIST_INIT(head) do { \
+ LIST_FIRST((head)) = NULL; \
+} while (0)
+
+#define LIST_INSERT_AFTER(listelm, elm, field) do { \
+ QMD_LIST_CHECK_NEXT(listelm, field); \
+ if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\
+ LIST_NEXT((listelm), field)->field.le_prev = \
+ &LIST_NEXT((elm), field); \
+ LIST_NEXT((listelm), field) = (elm); \
+ (elm)->field.le_prev = &LIST_NEXT((listelm), field); \
+} while (0)
+
+#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
+ QMD_LIST_CHECK_PREV(listelm, field); \
+ (elm)->field.le_prev = (listelm)->field.le_prev; \
+ LIST_NEXT((elm), field) = (listelm); \
+ *(listelm)->field.le_prev = (elm); \
+ (listelm)->field.le_prev = &LIST_NEXT((elm), field); \
+} while (0)
+
+#define LIST_INSERT_HEAD(head, elm, field) do { \
+ QMD_LIST_CHECK_HEAD((head), field); \
+ if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \
+ LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\
+ LIST_FIRST((head)) = (elm); \
+ (elm)->field.le_prev = &LIST_FIRST((head)); \
+} while (0)
+
+#define LIST_NEXT(elm, field) ((elm)->field.le_next)
+
+#define LIST_REMOVE(elm, field) do { \
+ QMD_LIST_CHECK_NEXT(elm, field); \
+ QMD_LIST_CHECK_PREV(elm, field); \
+ if (LIST_NEXT((elm), field) != NULL) \
+ LIST_NEXT((elm), field)->field.le_prev = \
+ (elm)->field.le_prev; \
+ *(elm)->field.le_prev = LIST_NEXT((elm), field); \
+ TRASHIT((elm)->field.le_next); \
+ TRASHIT((elm)->field.le_prev); \
+} while (0)
+
+/*
+ * Tail queue declarations.
+ */
+#define TAILQ_HEAD(name, type) \
+struct name { \
+ struct type *tqh_first; /* first element */ \
+ struct type **tqh_last; /* addr of last next element */ \
+ TRACEBUF \
+}
+
+#define TAILQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).tqh_first }
+
+#define TAILQ_ENTRY(type) \
+struct { \
+ struct type *tqe_next; /* next element */ \
+ struct type **tqe_prev; /* address of previous next element */ \
+ TRACEBUF \
+}
+
+/*
+ * Tail queue functions.
+ */
+#if (defined(_KERNEL) && defined(INVARIANTS))
+#define QMD_TAILQ_CHECK_HEAD(head, field) do { \
+ if (!TAILQ_EMPTY(head) && \
+ TAILQ_FIRST((head))->field.tqe_prev != \
+ &TAILQ_FIRST((head))) \
+ panic("Bad tailq head %p first->prev != head", (head)); \
+} while (0)
+
+#define QMD_TAILQ_CHECK_TAIL(head, field) do { \
+ if (*(head)->tqh_last != NULL) \
+ panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \
+} while (0)
+
+#define QMD_TAILQ_CHECK_NEXT(elm, field) do { \
+ if (TAILQ_NEXT((elm), field) != NULL && \
+ TAILQ_NEXT((elm), field)->field.tqe_prev != \
+ &((elm)->field.tqe_next)) \
+ panic("Bad link elm %p next->prev != elm", (elm)); \
+} while (0)
+
+#define QMD_TAILQ_CHECK_PREV(elm, field) do { \
+ if (*(elm)->field.tqe_prev != (elm)) \
+ panic("Bad link elm %p prev->next != elm", (elm)); \
+} while (0)
+#else
+#define QMD_TAILQ_CHECK_HEAD(head, field)
+#define QMD_TAILQ_CHECK_TAIL(head, headname)
+#define QMD_TAILQ_CHECK_NEXT(elm, field)
+#define QMD_TAILQ_CHECK_PREV(elm, field)
+#endif /* (_KERNEL && INVARIANTS) */
+
+#define TAILQ_CONCAT(head1, head2, field) do { \
+ if (!TAILQ_EMPTY(head2)) { \
+ *(head1)->tqh_last = (head2)->tqh_first; \
+ (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
+ (head1)->tqh_last = (head2)->tqh_last; \
+ TAILQ_INIT((head2)); \
+ QMD_TRACE_HEAD(head1); \
+ QMD_TRACE_HEAD(head2); \
+ } \
+} while (0)
+
+#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
+
+#define TAILQ_FIRST(head) ((head)->tqh_first)
+
+#define TAILQ_FOREACH(var, head, field) \
+ for ((var) = TAILQ_FIRST((head)); \
+ (var); \
+ (var) = TAILQ_NEXT((var), field))
+
+#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = TAILQ_FIRST((head)); \
+ (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
+ for ((var) = TAILQ_LAST((head), headname); \
+ (var); \
+ (var) = TAILQ_PREV((var), headname, field))
+
+#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
+ for ((var) = TAILQ_LAST((head), headname); \
+ (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
+ (var) = (tvar))
+
+#define TAILQ_INIT(head) do { \
+ TAILQ_FIRST((head)) = NULL; \
+ (head)->tqh_last = &TAILQ_FIRST((head)); \
+ QMD_TRACE_HEAD(head); \
+} while (0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ QMD_TAILQ_CHECK_NEXT(listelm, field); \
+ if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\
+ TAILQ_NEXT((elm), field)->field.tqe_prev = \
+ &TAILQ_NEXT((elm), field); \
+ else { \
+ (head)->tqh_last = &TAILQ_NEXT((elm), field); \
+ QMD_TRACE_HEAD(head); \
+ } \
+ TAILQ_NEXT((listelm), field) = (elm); \
+ (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+ QMD_TRACE_ELEM(&listelm->field); \
+} while (0)
+
+#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
+ QMD_TAILQ_CHECK_PREV(listelm, field); \
+ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
+ TAILQ_NEXT((elm), field) = (listelm); \
+ *(listelm)->field.tqe_prev = (elm); \
+ (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+ QMD_TRACE_ELEM(&listelm->field); \
+} while (0)
+
+#define TAILQ_INSERT_HEAD(head, elm, field) do { \
+ QMD_TAILQ_CHECK_HEAD(head, field); \
+ if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \
+ TAILQ_FIRST((head))->field.tqe_prev = \
+ &TAILQ_NEXT((elm), field); \
+ else \
+ (head)->tqh_last = &TAILQ_NEXT((elm), field); \
+ TAILQ_FIRST((head)) = (elm); \
+ (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \
+ QMD_TRACE_HEAD(head); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+} while (0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) do { \
+ QMD_TAILQ_CHECK_TAIL(head, field); \
+ TAILQ_NEXT((elm), field) = NULL; \
+ (elm)->field.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = (elm); \
+ (head)->tqh_last = &TAILQ_NEXT((elm), field); \
+ QMD_TRACE_HEAD(head); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+} while (0)
+
+#define TAILQ_LAST(head, headname) \
+ (*(((struct headname *)((head)->tqh_last))->tqh_last))
+
+#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+
+#define TAILQ_PREV(elm, headname, field) \
+ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+
+#define TAILQ_REMOVE(head, elm, field) do { \
+ QMD_TAILQ_CHECK_NEXT(elm, field); \
+ QMD_TAILQ_CHECK_PREV(elm, field); \
+ if ((TAILQ_NEXT((elm), field)) != NULL) \
+ TAILQ_NEXT((elm), field)->field.tqe_prev = \
+ (elm)->field.tqe_prev; \
+ else { \
+ (head)->tqh_last = (elm)->field.tqe_prev; \
+ QMD_TRACE_HEAD(head); \
+ } \
+ *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \
+ TRASHIT((elm)->field.tqe_next); \
+ TRASHIT((elm)->field.tqe_prev); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+} while (0)
+
+
+#ifdef _KERNEL
+
+/*
+ * XXX insque() and remque() are an old way of handling certain queues.
+ * They bogusly assumes that all queue heads look alike.
+ */
+
+struct quehead {
+ struct quehead *qh_link;
+ struct quehead *qh_rlink;
+};
+
+#ifdef __CC_SUPPORTS___INLINE
+
+static __inline void
+insque(void *a, void *b)
+{
+ struct quehead *element = (struct quehead *)a,
+ *head = (struct quehead *)b;
+
+ element->qh_link = head->qh_link;
+ element->qh_rlink = head;
+ head->qh_link = element;
+ element->qh_link->qh_rlink = element;
+}
+
+static __inline void
+remque(void *a)
+{
+ struct quehead *element = (struct quehead *)a;
+
+ element->qh_link->qh_rlink = element->qh_rlink;
+ element->qh_rlink->qh_link = element->qh_link;
+ element->qh_rlink = 0;
+}
+
+#else /* !__CC_SUPPORTS___INLINE */
+
+void insque(void *a, void *b);
+void remque(void *a);
+
+#endif /* __CC_SUPPORTS___INLINE */
+
+#endif /* _KERNEL */
+
+#endif /* !_SYS_QUEUE_H_ */
diff --git a/compat/strlcpy.c b/compat/strlcpy.c
new file mode 100644
index 000000000..1c0805548
--- /dev/null
+++ b/compat/strlcpy.c
@@ -0,0 +1,70 @@
+/* $OpenBSD: strlcpy.c,v 1.4 1999/05/01 18:56:41 millert Exp $ */
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static char *rcsid = "$OpenBSD: strlcpy.c,v 1.4 1999/05/01 18:56:41 millert Exp $";
+#endif /* LIBC_SCCS and not lint */
+#include <sys/cdefs.h>
+
+#include <sys/types.h>
+#include <string.h>
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+strlcpy(dst, src, siz)
+ char *dst;
+ const char *src;
+ size_t siz;
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0 && --n != 0) {
+ do {
+ if ((*d++ = *s++) == 0)
+ break;
+ } while (--n != 0);
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+
+ return(s - src - 1); /* count does not include NUL */
+}
diff --git a/compat/strlcpy.h b/compat/strlcpy.h
new file mode 100644
index 000000000..74d772d31
--- /dev/null
+++ b/compat/strlcpy.h
@@ -0,0 +1,8 @@
+#ifndef STRLCPY_H
+#define STRLCPY_H
+
+#include <sys/types.h>
+
+size_t strlcpy(char *, const char*, size_t);
+
+#endif
diff --git a/configure b/configure
new file mode 100755
index 000000000..e967bb075
--- /dev/null
+++ b/configure
@@ -0,0 +1,516 @@
+#!/bin/sh
+
+GCC="gcc"
+MAKE=""
+LEX=""
+YACC=""
+OS=""
+
+LOCALBASE=/usr/local
+PREFIX=$LOCALBASE
+
+VERSION=0.0.1
+COMPAT_DIR="./compat"
+
+YACC_SRC="cfg_file.y"
+LEX_SRC="cfg_file.l"
+YACC_OUTPUT="cfg_yacc.c"
+LEX_OUTPUT="cfg_lex.c"
+
+SOURCES="upstream.c cfg_utils.c memcached.c main.c util.c worker.c ${LEX_OUTPUT} ${YACC_OUTPUT}"
+
+CFLAGS="$CFLAGS -W -Wall -Wpointer-arith -Wno-unused-parameter"
+CFLAGS="$CFLAGS -Wno-unused-function -Wunused-variable -Wno-sign-compare"
+CFLAGS="$CFLAGS -Wunused-value -ggdb -I${LOCALBASE}/include"
+CFLAGS="$CFLAGS -DRVERSION=\\\"${VERSION}\\\" -DHASH_COMPAT"
+LDFLAGS="$LDFLAGS -L/usr/lib -L${LOCALBASE}/lib"
+OPT_FLAGS="-O -pipe -fno-omit-frame-pointer"
+DEPS="cfg_file.h memcached.h util.h main.h upstream.h ${LEX_OUTPUT} ${YACC_OUTPUT}"
+EXEC=rspamd
+USER=postfix
+GROUP=postfix
+INSTALL="/usr/bin/install -v"
+MKDIR="/usr/bin/install -v -d"
+MANPATH="${PREFIX}/share/man"
+
+MAKEFILE="Makefile"
+MAKEFILE_IN="Makefile.in"
+
+TARGETS="${EXEC}"
+
+cleanup()
+{
+ rm -f autotest.c
+ rm -f autotest
+ INCLUDE=""
+}
+
+check_compiler()
+{
+ GCC=`PATH="$PATH:$PREFIX/bin:$LOCALBASE/bin" which gcc`
+ echo -n "Testing for gcc: "
+ if [ -x $GCC ] ; then
+ echo "found -> $GCC"
+ return 0
+ else
+ echo "not found"
+ exit 1
+ fi
+}
+
+check_make()
+{
+ MAKE=`PATH="$PATH:$PREFIX/bin:$LOCALBASE/bin" which make`
+ echo -n "Testing for make: "
+ if [ -x $MAKE ] ; then
+ echo "found -> $MAKE"
+ return 0
+ else
+ echo "not found"
+ exit 1
+ fi
+
+}
+
+check_lex()
+{
+ LEX=`PATH="$PATH:$PREFIX/bin:$LOCALBASE/bin" which lex`
+ echo -n "Testing for lex: "
+ if [ -x $LEX ] ; then
+ echo "found -> $LEX"
+ return 0
+ else
+ echo "not found"
+ exit 1
+ fi
+
+}
+
+check_yacc()
+{
+ YACC=`PATH="$PATH:$PREFIX/bin:$LOCALBASE/bin" which yacc`
+ echo -n "Testing for yacc: "
+ if [ -x $YACC ] ; then
+ echo "found -> $YACC"
+ return 0
+ else
+ echo "not found"
+ exit 1
+ fi
+
+}
+
+check_function()
+{
+ FUNCTION=$1
+ while [ $# -ne 0 -a -n $2 ] ; do
+ shift
+ if [ "F$INCLUDE" = "F" ] ; then
+ INCLUDE="$1"
+ else
+ INCLUDE="$INCLUDE $1"
+ fi
+ done
+ echo -n "Testing for $FUNCTION: "
+ echo >> config.log
+ echo "Testing for $FUNCTION: " >> config.log
+ echo "#include <sys/types.h>" > autotest.c
+ if [ "F$INCLUDE" != "F" ] ; then
+ for inc in $INCLUDE ; do
+ echo "#include \"$inc\"" >> autotest.c
+ done
+ fi
+ echo "#include <stdlib.h>" >> autotest.c
+ echo "int main (int argc, char **argv) { $FUNCTION; return 0; }" >> autotest.c
+ echo "$GCC $PTHREAD_CFLAGS $CFLAGS -o autotest $LDFLAGS $LIBS $PTHREAD_LDFLAGS autotest.c" >>config.log
+ $GCC $PTHREAD_CFLAGS $CFLAGS -o autotest $LDFLAGS $LIBS $PTHREAD_LDFLAGS autotest.c >>config.log 2>&1
+ if [ $? -eq 0 ] ; then
+ echo "found"
+ cleanup
+ echo "-> OK" >> config.log
+ return 0
+ else
+ echo "not found"
+ echo "-> FAILED" >> config.log
+ echo "Failed program was:" >> config.log
+ cat autotest.c >> config.log
+ cleanup
+ return 1
+ fi
+}
+
+check_include()
+{
+ INCLUDE="$1"
+ echo -n "Testing for $INCLUDE: "
+ echo >> config.log
+ echo "Testing for $INCLUDE: " >> config.log
+ echo "#include <sys/types.h>" > autotest.c
+ echo "#include \"$INCLUDE\"" >> autotest.c
+ echo "#include <stdlib.h>" >> autotest.c
+ echo "int main (int argc, char **argv) { return 0; }" >> autotest.c
+ echo "$GCC $CFLAGS $PTHREAD_CFLAGS -o autotest $LDFLAGS $LIBS $PTHREAD_LDFLAGS autotest.c" >>config.log
+ $GCC $CFLAGS $PTHREAD_CFLAGS -o autotest $LDFLAGS $LIBS $PTHREAD_LDFLAGS autotest.c >>config.log 2>&1
+ if [ $? -eq 0 ] ; then
+ echo "found"
+ echo "-> OK" >> config.log
+ _CFLAG=`echo "-DHAVE_$INCLUDE" | sed -e 's/[./]/_/g' | tr '[:lower:]' '[:upper:]'`
+ CFLAGS="$CFLAGS $_CFLAG"
+ cleanup
+ return 0
+ else
+ echo "not found"
+ echo "-> FAILED" >> config.log
+ echo "Failed program was:" >> config.log
+ cat autotest.c >> config.log
+ cleanup
+ return 1
+ fi
+}
+
+check_macro()
+{
+ MACRO=$1
+ while [ $# -ne 1 -a -n $1 ] ; do
+ shift
+ if [ "F$INCLUDE" = "F" ] ; then
+ INCLUDE="$1"
+ else
+ INCLUDE="$INCLUDE $1"
+ fi
+ done
+ echo -n "Testing for $MACRO: "
+ echo >> config.log
+ echo "Testing for $MACRO: " >> config.log
+ echo "#include <sys/types.h>" > autotest.c
+ for inc in $INCLUDE ; do
+ echo "#include \"$inc\"" >> autotest.c
+ done
+ echo "#include \"${INCLUDE}\"" >> autotest.c
+ echo "#include <stdlib.h>" >> autotest.c
+ echo "int main (int argc, char **argv) {" >>autotest.c
+ echo "#ifndef $MACRO" >>autotest.c
+ echo "#error \"$MACRO not defined\"" >>autotest.c
+ echo "#endif" >> autotest.c
+ echo "}" >>autotest.c
+ echo "$GCC $CFLAGS $PTHREAD_CFLAGS -o autotest $LDFLAGS $LIBS $PTHREAD_LDFLAGS autotest.c" >> config.log
+ $GCC $CFLAGS $PTHREAD_CFLAGS -o autotest $LDFLAGS $LIBS $PTHREAD_LDFLAGS autotest.c >>config.log 2>&1
+ if [ $? -eq 0 ] ; then
+ echo "found"
+ cleanup
+ echo "-> OK" >> config.log
+ return 0
+ else
+ echo "not found"
+ echo "-> FAILED" >> config.log
+ echo "Failed program was:" >> config.log
+ cat autotest.c >> config.log
+ cleanup
+ return 1
+ fi
+}
+
+
+check_lib()
+{
+ LIB=$1
+ while [ $# -ne 1 -a -n $1 ] ; do
+ shift
+ if [ "F$INCLUDE" = "F" ] ; then
+ INCLUDE="$1"
+ else
+ INCLUDE="$INCLUDE $1"
+ fi
+ done
+ echo -n "Testing for lib$LIB: "
+ echo >> config.log
+ echo "Testing for lib$LIB: " >> config.log
+ echo "#include <sys/types.h>" > autotest.c
+ if [ "F$INCLUDE" != "F" ] ; then
+ for inc in $INCLUDE ; do
+ echo "#include \"$inc\"" >> autotest.c
+ done
+ fi
+ echo "#include <stdlib.h>" >> autotest.c
+ echo "int main (int argc, char **argv) { return 0; }" >> autotest.c
+ echo "$GCC $CFLAGS $PTHREAD_CFLAGS -o autotest $LDFLAGS $LIBS -l$LIB $PTHREAD_LDFLAGS autotest.c" >>config.log
+ $GCC $CFLAGS $PTHREAD_CFLAGS -o autotest $LDFLAGS $LIBS -l$LIB $PTHREAD_LDFLAGS autotest.c >>config.log 2>&1
+ if [ $? -eq 0 ] ; then
+ echo "found"
+ LIBS="$LIBS -l$LIB"
+ cleanup
+ echo "-> OK" >> config.log
+ return 0
+ else
+ echo "not found"
+ echo "-> FAILED" >> config.log
+ echo "Failed program was:" >> config.log
+ cat autotest.c >> config.log
+ cleanup
+ return 1
+ fi
+}
+
+check_os()
+{
+ _OS=`uname -s`
+ case "$_OS" in
+ FreeBSD*)
+ OS="freebsd"
+ CFLAGS="${CFLAGS} -DFREEBSD"
+ INSTALL="/usr/bin/install -C -S -v"
+ MKDIR="/usr/bin/install -d -v"
+ MANPATH="${PREFIX}/man" ;;
+ Linux*) OS="linux" CFLAGS="${CFLAGS} -DLINUX -D_GNU_SOURCE" ;;
+ Solaris*) OS="solaris" CFLAGS="${CFLAGS} -DSOLARIS" ;;
+ *) OS="unknown" ;;
+ esac
+}
+
+check_user()
+{
+ _user=$1
+ echo -n "Checking for user $_user: "
+ grep $_user /etc/passwd > /dev/null 2>&1
+ if [ $? -eq 0 ] ; then
+ echo "found"
+ return 0
+ else
+ echo "not found"
+ return 1
+ fi
+}
+
+check_group()
+{
+ _group=$1
+ echo -n "Checking for group $_group: "
+ grep $_group /etc/group > /dev/null 2>&1
+ if [ $? -eq 0 ] ; then
+ echo "found"
+ return 0
+ else
+ echo "not found"
+ return 1
+ fi
+}
+
+write_result()
+{
+ echo "Compiler: $GCC" >> config.log
+ echo "Make: $MAKE" >> config.log
+ echo "Sources: $SOURCES" >> config.log
+ echo "Cflags: $CFLAGS" >> config.log
+ echo "Ldflags: $LDFLAGS" >> config.log
+ echo "Libs: $LIBS" >> config.log
+ OBJECTS=`echo $SOURCES | sed -e 's/\.c/\.o/g'`
+ cat > $MAKEFILE << END
+# This is ${EXEC} Makefile
+# For options edit Makefile.in, this file is autogenerated by configure
+
+CC=$GCC
+# Optimization flags
+OPT_FLAGS=$OPT_FLAGS
+# Compile time flags
+CFLAGS=$CFLAGS
+# Link time flags
+LDFLAGS=$LDFLAGS
+# Libraries to link
+LIBS=$LIBS
+# ${EXEC} sources
+SOURCES=$SOURCES
+# ${EXEC} objects
+OBJECTS=$OBJECTS
+# Version of product
+VERION=$VERSION
+# Detected operation system
+OS=$OS
+# Lex and yacc executables
+LEX=$LEX
+YACC=$YACC
+# Pthread specific flags
+PTHREAD_CFLAGS=$PTHREAD_CFLAGS
+PTHREAD_LDFLAGS=$PTHREAD_LDFLAGS
+# Prefix to install
+PREFIX=$PREFIX
+# Where local libs and includes are located
+LOCALBASE=$LOCALBASE
+# Install commands
+INSTALL=$INSTALL
+MKDIR=$MKDIR
+# Executable name
+EXEC=$EXEC
+# User and group
+RSPAMD_USER=$USER
+RSPAMD_GROUP=$GROUP
+# All target dependenses
+TARGETS=$TARGETS
+# Common dependenses
+DEPS=$DEPS
+# Path to install manual page
+MANPATH=$MANPATH
+
+END
+ # Write build targets to makefile
+
+ cat $MAKEFILE_IN >> $MAKEFILE
+ cat >> $MAKEFILE << END
+${EXEC}: \$(OBJECTS)
+ \$(CC) \$(PTHREAD_LDFLAGS) \$(LDFLAGS) \$(OBJECTS) \$(LIBS) -o \$(EXEC)
+END
+ for o in $OBJECTS ; do
+ SO=`echo $o | sed -e 's/\.o/\.c/g'`
+ cat >> $MAKEFILE << END
+${o}: \$(DEPS) ${SO}
+ \$(CC) \$(OPT_FLAGS) \$(CFLAGS) \$(PTHREAD_CFLAGS) -c ${SO}
+
+END
+ done
+ cat >> $MAKEFILE << END
+${LEX_OUTPUT}: cfg_file.h ${LEX_SRC} ${YACC_OUTPUT}
+ \$(LEX) -o${LEX_OUTPUT} ${LEX_SRC}
+
+${YACC_OUTPUT}: cfg_file.h ${YACC_SRC}
+ \$(YACC) -d -o ${YACC_OUTPUT} ${YACC_SRC}
+END
+
+}
+
+
+for option
+do
+ case "$option" in
+ -*=*) value=`echo "$option" | sed -e 's/[-_a-zA-Z0-9]*=//'` ;;
+ *) value="" ;;
+ esac
+
+ case "$option" in
+ --help) help=yes ;;
+ --prefix) PREFIX=$value ;;
+ --user) USER=$value ;;
+ --group) GROUP=$value ;;
+ --enable-debug) CFLAGS="$CFLAGS -DWITH_DEBUG" OPT_FLAGS="" ;;
+ --enable-opt) OPT_FLAGS="-O3 -pipe" ;;
+ *)
+ echo "$0: error: invalid option \"$option\""
+ exit 1
+ ;;
+ esac
+done
+
+if [ "F$help" = "Fyes" ] ; then
+ cat << END
+
+ --help this message
+
+ --prefix=PATH set the installation prefix
+ --enable-debug turn on extra debug messages
+ --enable-opt turn on extra optimization
+ --user=USER set user to use
+ --group=GROUP set group to use
+END
+ exit 1
+fi
+
+CFLAGS="$CFLAGS -I$PREFIX/include"
+LDFLAGS="$LDFLAGS -L$PREFIX/lib"
+
+echo "Starting configure for rmilter" >config.log
+echo $0 $@ >> config.log
+
+check_compiler
+check_make
+check_lex
+check_yacc
+check_os
+
+check_lib "event" "event.h"
+if [ $? -eq 1 ] ; then
+ echo "Libevent not found, check config.log for details"
+ exit 1
+fi
+
+check_lib "m"
+check_lib "pcre"
+check_lib "md"
+if [ $? -eq 1 ] ; then
+ cp $COMPAT_DIR/md5.c .
+ cp $COMPAT_DIR/md5.h .
+ SOURCES="$SOURCES md5.c"
+ CFLAGS="$CFLAGS -DHAVE_OWN_MD5"
+ DEPS="$DEPS md5.h"
+fi
+
+check_lib "util"
+if [ $? -eq 0 ] ; then
+ CFLAGS="$CFLAGS -DHAVE_LIBUTIL"
+fi
+
+check_function "pidfile_open" "sys/param.h" "libutil.h"
+if [ $? -eq 0 ] ; then
+ CFLAGS="$CFLAGS -DHAVE_PIDFILE"
+fi
+
+check_function "strlcpy" "string.h"
+if [ $? -eq 1 ] ; then
+ cp $COMPAT_DIR/strlcpy.c .
+ cp $COMPAT_DIR/strlcpy.h .
+ SOURCES="$SOURCES strlcpy.c"
+ CFLAGS="$CFLAGS -DHAVE_STRLCPY_H"
+ DEPS="$DEPS strlcpy.h"
+fi
+check_function "bzero" "string.h"
+check_function "srandomdev"
+if [ $? -eq 0 ] ; then
+ CFLAGS="$CFLAGS -DHAVE_SRANDOMDEV"
+fi
+
+check_function "setproctitle" "unistd.h"
+if [ $? -eq 0 ] ; then
+ CFLAGS="$CFLAGS -DHAVE_SETPROCTITLE"
+fi
+
+check_include "endian.h"
+check_include "libutil.h"
+check_include "machine/endian.h"
+check_include "sys/time.h"
+check_include "time.h"
+check_include "stdint.h"
+if [ $? -eq 1 ] ; then
+ check_include "inttypes.h"
+fi
+check_include "strlcpy.h"
+check_include "md5.h"
+check_include "sys/queue.h"
+if [ $? -eq 1 ] ; then
+ cp $COMPAT_DIR/queue.h .
+ DEPS="$DEPS queue.h"
+fi
+check_macro "SLIST_FOREACH_SAFE" "sys/queue.h"
+if [ $? -eq 1 ] ; then
+ cp $COMPAT_DIR/queue.h .
+ CFLAGS="$CFLAGS -DOWN_QUEUE_H"
+ DEPS="$DEPS queue.h"
+fi
+
+check_macro "PATH_MAX" "limits.h"
+if [ $? -eq 1 ] ; then
+ check_macro "MAXPATHLEN" "sys/param.h"
+ if [ $? -eq 1 ] ; then
+ CFLAGS="$CFLAGS -DHAVE_MAXPATHLEN -DMAXPATHLEN=4096"
+ else
+ CFLAGS="$CFLAGS -DHAVE_MAXPATHLEN"
+ fi
+else
+ CFLAGS="$CFLAGS -DHAVE_PATH_MAX"
+fi
+
+check_group $GROUP
+if [ $? -ne 0 ] ; then
+ TARGETS="$TARGETS creategroup"
+fi
+check_user $USER
+if [ $? -ne 0 ] ; then
+ TARGETS="$TARGETS createuser"
+fi
+write_result
diff --git a/main.c b/main.c
new file mode 100644
index 000000000..1b7a00f15
--- /dev/null
+++ b/main.c
@@ -0,0 +1,324 @@
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <sys/param.h>
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <signal.h>
+#ifdef HAVE_LIBUTIL_H
+#include <libutil.h>
+#endif
+#include <syslog.h>
+
+#include "main.h"
+#include "cfg_file.h"
+#include "util.h"
+
+struct config_file *cfg;
+
+static void sig_handler (int );
+static struct rspamd_worker * fork_worker (struct rspamd_main *, int, int, enum process_type);
+
+sig_atomic_t do_restart;
+sig_atomic_t do_terminate;
+sig_atomic_t child_dead;
+sig_atomic_t child_ready;
+
+extern int yynerrs;
+extern FILE *yyin;
+
+static
+void sig_handler (int signo)
+{
+ switch (signo) {
+ case SIGHUP:
+ do_restart = 1;
+ break;
+ case SIGINT:
+ case SIGTERM:
+ do_terminate = 1;
+ break;
+ case SIGCHLD:
+ child_dead = 1;
+ break;
+ case SIGUSR2:
+ child_ready = 1;
+ break;
+ }
+}
+
+static struct rspamd_worker *
+fork_worker (struct rspamd_main *rspamd, int listen_sock, int reconfig, enum process_type type)
+{
+ struct rspamd_worker *cur;
+ char *cfg_file;
+ FILE *f;
+ struct config_file *tmp_cfg;
+ /* Starting worker process */
+ cur = (struct rspamd_worker *)malloc (sizeof (struct rspamd_worker));
+ if (cur) {
+ /* Reconfig needed */
+ if (reconfig) {
+ tmp_cfg = (struct config_file *) malloc (sizeof (struct config_file));
+ if (tmp_cfg) {
+ cfg_file = strdup (rspamd->cfg->cfg_name);
+ bzero (tmp_cfg, sizeof (struct config_file));
+ f = fopen (rspamd->cfg->cfg_name , "r");
+ if (f == NULL) {
+ msg_warn ("fork_worker: cannot open file: %s", rspamd->cfg->cfg_name );
+ }
+ else {
+ yyin = f;
+ yyrestart (yyin);
+
+ if (yyparse() != 0 || yynerrs > 0) {
+ msg_warn ("fork_worker: yyparse: cannot parse config file, %d errors", yynerrs);
+ fclose (f);
+ }
+ else {
+ free_config (rspamd->cfg);
+ free (rspamd->cfg);
+ rspamd->cfg = tmp_cfg;
+ rspamd->cfg->cfg_name = cfg_file;
+ }
+ }
+ }
+ }
+ bzero (cur, sizeof (struct rspamd_worker));
+ TAILQ_INSERT_HEAD (&rspamd->workers, cur, next);
+ cur->srv = rspamd;
+ cur->pid = fork();
+ switch (cur->pid) {
+ case 0:
+ /* TODO: add worker code */
+ switch (type) {
+ case TYPE_WORKER:
+ default:
+ setproctitle ("worker process");
+ pidfile_close (rspamd->pfh);
+ msg_info ("fork_worker: starting worker process %d", getpid ());
+ cur->type = TYPE_WORKER;
+ start_worker (cur, listen_sock);
+ break;
+ }
+ break;
+ case -1:
+ msg_err ("fork_worker: cannot fork main process. %m");
+ pidfile_remove (rspamd->pfh);
+ exit (-errno);
+ break;
+ }
+ }
+
+ return cur;
+}
+
+int
+main (int argc, char **argv)
+{
+ struct rspamd_main *rspamd;
+ int res = 0, i, listen_sock;
+ struct sigaction signals;
+ struct rspamd_worker *cur, *cur_tmp, *active_worker;
+ struct sockaddr_un *un_addr;
+ FILE *f;
+
+ rspamd = (struct rspamd_main *)malloc (sizeof (struct rspamd_main));
+ bzero (rspamd, sizeof (struct rspamd_main));
+ cfg = (struct config_file *)malloc (sizeof (struct config_file));
+ rspamd->cfg = cfg;
+ if (!rspamd || !rspamd->cfg) {
+ fprintf(stderr, "Cannot allocate memory\n");
+ exit(-errno);
+ }
+
+ do_terminate = 0;
+ do_restart = 0;
+ child_dead = 0;
+ child_ready = 0;
+ active_worker = NULL;
+
+ bzero (rspamd->cfg, sizeof (struct config_file));
+ init_defaults (rspamd->cfg);
+
+ bzero (&signals, sizeof (struct sigaction));
+
+ rspamd->cfg->cfg_name = strdup (FIXED_CONFIG_FILE);
+ read_cmd_line (argc, argv, rspamd->cfg);
+
+ openlog("rspamd", LOG_PID, LOG_MAIL);
+ msg_warn ("(main) starting...");
+
+ #ifndef HAVE_SETPROCTITLE
+ init_title (argc, argv, environ);
+ #endif
+
+ f = fopen (rspamd->cfg->cfg_name , "r");
+ if (f == NULL) {
+ msg_warn ("cannot open file: %s", rspamd->cfg->cfg_name );
+ return EBADF;
+ }
+ yyin = f;
+
+ if (yyparse() != 0 || yynerrs > 0) {
+ msg_warn ("yyparse: cannot parse config file, %d errors", yynerrs);
+ return EBADF;
+ }
+
+ fclose (f);
+ rspamd->cfg->cfg_name = strdup (rspamd->cfg->cfg_name );
+
+ /* Strictly set temp dir */
+ if (!rspamd->cfg->temp_dir) {
+ msg_warn ("tempdir is not set, trying to use $TMPDIR");
+ rspamd->cfg->temp_dir = getenv ("TMPDIR");
+
+ if (!rspamd->cfg->temp_dir) {
+ rspamd->cfg->temp_dir = strdup ("/tmp");
+ }
+ }
+
+ if (!rspamd->cfg->no_fork && daemon (1, 1) == -1) {
+ fprintf (stderr, "Cannot daemonize\n");
+ exit (-errno);
+ }
+
+ if (write_pid (rspamd) == -1) {
+ msg_err ("main: cannot write pid file %s", rspamd->cfg->pid_file);
+ exit (-errno);
+ }
+
+ rspamd->pid = getpid();
+ rspamd->type = TYPE_MAIN;
+
+ init_signals (&signals, sig_handler);
+ /* Block signals to use sigsuspend in future */
+ sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL);
+
+ if (rspamd->cfg->bind_family == AF_INET) {
+ if ((listen_sock = make_socket (rspamd->cfg->bind_host, rspamd->cfg->bind_port)) == -1) {
+ msg_err ("main: cannot create tcp listen socket. %m");
+ exit(-errno);
+ }
+ }
+ else {
+ un_addr = (struct sockaddr_un *) malloc (sizeof (struct sockaddr_un));
+ if (!un_addr || (listen_sock = make_unix_socket (rspamd->cfg->bind_host, un_addr)) == -1) {
+ msg_err ("main: cannot create unix listen socket. %m");
+ exit(-errno);
+ }
+ }
+
+ if (listen (listen_sock, -1) == -1) {
+ msg_err ("main: cannot listen on socket. %m");
+ exit(-errno);
+ }
+
+ TAILQ_INIT (&rspamd->workers);
+
+ setproctitle ("main process");
+
+ for (i = 0; i < cfg->workers_number; i++) {
+ fork_worker (rspamd, listen_sock, 0, TYPE_WORKER);
+ }
+
+
+ /* Signal processing cycle */
+ for (;;) {
+ msg_debug ("main: calling sigsuspend");
+ sigemptyset (&signals.sa_mask);
+ sigsuspend (&signals.sa_mask);
+ if (do_terminate) {
+ msg_debug ("main: catch termination signal, waiting for childs");
+ pass_signal_worker (&rspamd->workers, SIGTERM);
+ break;
+ }
+ if (child_dead) {
+ child_dead = 0;
+ msg_debug ("main: catch SIGCHLD signal, finding terminated worker");
+ /* Remove dead child form childs list */
+ pid_t wrk = waitpid (0, &res, 0);
+ TAILQ_FOREACH_SAFE (cur, &rspamd->workers, next, cur_tmp) {
+ if (wrk == cur->pid) {
+ /* Catch situations if active worker is abnormally terminated */
+ if (cur == active_worker) {
+ active_worker = NULL;
+ }
+ TAILQ_REMOVE(&rspamd->workers, cur, next);
+ if (WIFEXITED (res) && WEXITSTATUS (res) == 0) {
+ /* Normal worker termination, do not fork one more */
+ msg_info ("main: worker process %d terminated normally", cur->pid);
+ }
+ else {
+ if (WIFSIGNALED (res)) {
+ msg_warn ("main: worker process %d terminated abnormally by signal: %d",
+ cur->pid, WTERMSIG(res));
+ }
+ else {
+ msg_warn ("main: worker process %d terminated abnormally", cur->pid);
+ }
+ /* Fork another worker in replace of dead one */
+ fork_worker (rspamd, listen_sock, 0, cur->type);
+ }
+ free (cur);
+ }
+ }
+ }
+ if (do_restart) {
+ do_restart = 0;
+
+ if (active_worker == NULL) {
+ /* Start new worker that would reread configuration*/
+ active_worker = fork_worker (rspamd, listen_sock, 1, TYPE_WORKER);
+ }
+ /* Do not start new workers untill active worker is not ready for accept */
+ }
+ if (child_ready) {
+ child_ready = 0;
+
+ if (active_worker != NULL) {
+ msg_info ("main: worker process %d has been successfully started", active_worker->pid);
+ TAILQ_FOREACH_SAFE (cur, &rspamd->workers, next, cur_tmp) {
+ if (cur != active_worker && !cur->is_dying) {
+ /* Send to old workers SIGUSR2 */
+ kill (cur->pid, SIGUSR2);
+ cur->is_dying = 1;
+ }
+ }
+ active_worker = NULL;
+ }
+ }
+ }
+
+ /* Wait for workers termination */
+ while (!TAILQ_EMPTY(&rspamd->workers)) {
+ cur = TAILQ_FIRST(&rspamd->workers);
+ waitpid (cur->pid, &res, 0);
+ msg_debug ("main(cleaning): worker process %d terminated", cur->pid);
+ TAILQ_REMOVE(&rspamd->workers, cur, next);
+ free(cur);
+ }
+
+ msg_info ("main: terminating...");
+
+
+ if (rspamd->cfg->bind_family == AF_UNIX) {
+ unlink (rspamd->cfg->bind_host);
+ }
+
+ free_config (rspamd->cfg);
+ free (rspamd->cfg);
+ free (rspamd);
+
+ return (res);
+}
+
+/*
+ * vi:ts=4
+ */
diff --git a/main.h b/main.h
new file mode 100644
index 000000000..fa5685526
--- /dev/null
+++ b/main.h
@@ -0,0 +1,71 @@
+#ifndef RPOP_MAIN_H
+#define RPOP_MAIN_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#ifndef OWN_QUEUE_H
+#include <sys/queue.h>
+#else
+#include "queue.h"
+#endif
+#include <sys/time.h>
+
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <signal.h>
+
+/* Default values */
+#define FIXED_CONFIG_FILE "./rspamd.conf"
+/* Time in seconds to exit for old worker */
+#define SOFT_SHUTDOWN_TIME 60
+
+/* Logging in postfix style */
+#define msg_err(args...) syslog(LOG_ERR, ##args)
+#define msg_warn(args...) syslog(LOG_WARNING, ##args)
+#define msg_info(args...) syslog(LOG_INFO, ##args)
+#define msg_debug(args...) syslog(LOG_DEBUG, ##args)
+
+/* Process type: main or worker */
+enum process_type {
+ TYPE_MAIN,
+ TYPE_WORKER,
+};
+
+/* Worker process structure */
+struct rspamd_worker {
+ pid_t pid;
+ char is_initialized;
+ char is_dying;
+ TAILQ_ENTRY (rspamd_worker) next;
+ struct rspamd_main *srv;
+ enum process_type type;
+};
+
+struct pidfh;
+struct config_file;
+
+/* Struct that determine main server object (for logging purposes) */
+struct rspamd_main {
+ struct config_file *cfg;
+ pid_t pid;
+ /* Pid file structure */
+ struct pidfh *pfh;
+ enum process_type type;
+ unsigned ev_initialized:1;
+
+ TAILQ_HEAD (workq, rspamd_worker) workers;
+};
+
+struct worker_task {
+ int id;
+};
+
+void start_worker (struct rspamd_worker *worker, int listen_sock);
+
+#endif
+
+/*
+ * vi:ts=4
+ */
diff --git a/memcached.c b/memcached.c
new file mode 100644
index 000000000..91aa21fdb
--- /dev/null
+++ b/memcached.c
@@ -0,0 +1,743 @@
+#ifdef _THREAD_SAFE
+#include <pthread.h>
+#endif
+
+#include <stdarg.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <syslog.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/uio.h>
+
+#include "memcached.h"
+
+#define CRLF "\r\n"
+#define END_TRAILER "END" CRLF
+#define STORED_TRAILER "STORED" CRLF
+#define NOT_STORED_TRAILER "NOT STORED" CRLF
+#define EXISTS_TRAILER "EXISTS" CRLF
+#define DELETED_TRAILER "DELETED" CRLF
+#define NOT_FOUND_TRAILER "NOT_FOUND" CRLF
+#define CLIENT_ERROR_TRAILER "CLIENT_ERROR"
+#define SERVER_ERROR_TRAILER "SERVER_ERROR"
+
+#define READ_BUFSIZ 1500
+#define MAX_RETRIES 3
+
+/* Header for udp protocol */
+struct memc_udp_header
+{
+ uint16_t req_id;
+ uint16_t seq_num;
+ uint16_t dg_sent;
+ uint16_t unused;
+};
+
+/*
+ * Poll file descriptor for read or write during specified timeout
+ */
+static int
+poll_d (int fd, u_char want_read, u_char want_write, int timeout)
+{
+ int r;
+ struct pollfd fds[1];
+
+ fds->fd = fd;
+ fds->revents = 0;
+ fds->events = 0;
+
+ if (want_read != 0) {
+ fds->events |= POLLIN;
+ }
+ if (want_write != 0) {
+ fds->events |= POLLOUT;
+ }
+ while ((r = poll(fds, 1, timeout)) < 0) {
+ if (errno != EINTR)
+ break;
+ }
+
+ return r;
+}
+
+/*
+ * Write to syslog if OPT_DEBUG is specified
+ */
+static void
+memc_log (const memcached_ctx_t *ctx, int line, const char *fmt, ...)
+{
+ va_list args;
+ if (ctx->options & MEMC_OPT_DEBUG) {
+ va_start (args, fmt);
+ syslog (LOG_DEBUG, "memc_debug(%d): host: %s, port: %d", line, inet_ntoa (ctx->addr), ntohs (ctx->port));
+ vsyslog (LOG_DEBUG, fmt, args);
+ va_end (args);
+ }
+}
+
+/*
+ * Make socket for udp connection
+ */
+static int
+memc_make_udp_sock (memcached_ctx_t *ctx)
+{
+ struct sockaddr_in sc;
+ int ofl;
+
+ bzero (&sc, sizeof (struct sockaddr_in *));
+ sc.sin_family = AF_INET;
+ sc.sin_port = ctx->port;
+ memcpy (&sc.sin_addr, &ctx->addr, sizeof (struct in_addr));
+
+ ctx->sock = socket (PF_INET, SOCK_DGRAM, 0);
+
+ if (ctx->sock == -1) {
+ memc_log (ctx, __LINE__, "memc_make_udp_sock: socket() failed: %m");
+ return -1;
+ }
+
+ /* set nonblocking */
+ ofl = fcntl(ctx->sock, F_GETFL, 0);
+ fcntl(ctx->sock, F_SETFL, ofl | O_NONBLOCK);
+
+ /*
+ * Call connect to set default destination for datagrams
+ * May not block
+ */
+ return connect (ctx->sock, (struct sockaddr*)&sc, sizeof (struct sockaddr_in));
+}
+
+/*
+ * Make socket for tcp connection
+ */
+static int
+memc_make_tcp_sock (memcached_ctx_t *ctx)
+{
+ struct sockaddr_in sc;
+ int ofl, r;
+
+ bzero (&sc, sizeof (struct sockaddr_in *));
+ sc.sin_family = AF_INET;
+ sc.sin_port = ctx->port;
+ memcpy (&sc.sin_addr, &ctx->addr, sizeof (struct in_addr));
+
+ ctx->sock = socket (PF_INET, SOCK_STREAM, 0);
+
+ if (ctx->sock == -1) {
+ memc_log (ctx, __LINE__, "memc_make_tcp_sock: socket() failed: %m");
+ return -1;
+ }
+
+ /* set nonblocking */
+ ofl = fcntl(ctx->sock, F_GETFL, 0);
+ fcntl(ctx->sock, F_SETFL, ofl | O_NONBLOCK);
+
+ if ((r = connect (ctx->sock, (struct sockaddr*)&sc, sizeof (struct sockaddr_in))) == -1) {
+ if (errno != EINPROGRESS) {
+ close (ctx->sock);
+ ctx->sock = -1;
+ memc_log (ctx, __LINE__, "memc_make_tcp_sock: connect() failed: %m");
+ return -1;
+ }
+ }
+ /* Get write readiness */
+ if (poll_d (ctx->sock, 0, 1, ctx->timeout) == 1) {
+ return 0;
+ }
+ else {
+ memc_log (ctx, __LINE__, "memc_make_tcp_sock: poll() timeout");
+ close (ctx->sock);
+ ctx->sock = -1;
+ return -1;
+ }
+}
+
+/*
+ * Parse VALUE reply from server and set len argument to value returned by memcached
+ */
+static int
+memc_parse_header (char *buf, size_t *len, char **end)
+{
+ char *p, *c;
+ int i;
+
+ /* VALUE <key> <flags> <bytes> [<cas unique>]\r\n */
+ c = strstr (buf, CRLF);
+ if (c == NULL) {
+ return -1;
+ }
+ *end = c + sizeof (CRLF) - 1;
+
+ if (strncmp (buf, "VALUE ", sizeof ("VALUE ") - 1) == 0) {
+ p = buf + sizeof ("VALUE ") - 1;
+
+ /* Read bytes value and ignore all other fields, such as flags and key */
+ for (i = 0; i < 2; i++) {
+ while (p++ < c && *p != ' ');
+
+ if (p > c) {
+ return -1;
+ }
+ }
+ *len = strtoul (p, &c, 10);
+ return 1;
+ }
+ /* If value not found memcached return just END\r\n , in this case return 0 */
+ else if (strncmp (buf, END_TRAILER, sizeof (END_TRAILER) - 1) == 0) {
+ return 0;
+ }
+
+ return -1;
+}
+/*
+ * Common read command handler for memcached
+ */
+memc_error_t
+memc_read (memcached_ctx_t *ctx, const char *cmd, memcached_param_t *params, size_t *nelem)
+{
+ char read_buf[READ_BUFSIZ];
+ char *p;
+ int i, retries;
+ ssize_t r, sum = 0, written = 0;
+ size_t datalen;
+ struct memc_udp_header header;
+ struct iovec iov[2];
+
+ for (i = 0; i < *nelem; i++) {
+ if (ctx->protocol == UDP_TEXT) {
+ /* Send udp header */
+ bzero (&header, sizeof (header));
+ header.dg_sent = htons (1);
+ header.req_id = ctx->count;
+ }
+
+ r = snprintf (read_buf, READ_BUFSIZ, "%s %s" CRLF, cmd, params[i].key);
+ memc_log (ctx, __LINE__, "memc_read: send read request to memcached: %s", read_buf);
+ if (ctx->protocol == UDP_TEXT) {
+ iov[0].iov_base = &header;
+ iov[0].iov_len = sizeof (struct memc_udp_header);
+ iov[1].iov_base = read_buf;
+ iov[1].iov_len = r;
+ writev (ctx->sock, iov, 2);
+ }
+ else {
+ write (ctx->sock, read_buf, r);
+ }
+
+ /* Read reply from server */
+ retries = 0;
+ while (ctx->protocol == UDP_TEXT) {
+ if (poll_d (ctx->sock, 1, 0, ctx->timeout) != 1) {
+ return SERVER_TIMEOUT;
+ }
+ iov[0].iov_base = &header;
+ iov[0].iov_len = sizeof (struct memc_udp_header);
+ iov[1].iov_base = read_buf;
+ iov[1].iov_len = READ_BUFSIZ;
+ if ((r = readv (ctx->sock, iov, 2)) == -1) {
+ return SERVER_ERROR;
+ }
+ memc_log (ctx, __LINE__, "memc_read: got read_buf: %s", read_buf);
+ if (header.req_id != ctx->count && retries < MAX_RETRIES) {
+ memc_log (ctx, __LINE__, "memc_read: got wrong packet id: %d, %d was awaited", header.req_id, ctx->count);
+ retries++;
+ /* Not our reply packet */
+ continue;
+ }
+ break;
+ }
+ if (ctx->protocol != UDP_TEXT) {
+ if (poll_d (ctx->sock, 1, 0, ctx->timeout) != 1) {
+ memc_log (ctx, __LINE__, "memc_read: timeout waiting reply");
+ return SERVER_TIMEOUT;
+ }
+ r = read (ctx->sock, read_buf, READ_BUFSIZ - 1);
+ }
+
+ if (r > 0) {
+ sum += r;
+ read_buf[r] = 0;
+ r = memc_parse_header (read_buf, &datalen, &p);
+ if (r < 0) {
+ memc_log (ctx, __LINE__, "memc_read: cannot parse memcached reply");
+ return SERVER_ERROR;
+ }
+ else if (r == 0) {
+ memc_log (ctx, __LINE__, "memc_read: record does not exists");
+ return NOT_EXISTS;
+ }
+
+ if (datalen != params[i].bufsize) {
+ memc_log (ctx, __LINE__, "memc_read: user's buffer is too small: %zd, %zd required", params[i].bufsize, datalen);
+ return WRONG_LENGTH;
+ }
+
+ /* Subtract from sum parsed header's length */
+ sum -= p - read_buf;
+ /* Check if we already have all data in buffer */
+ if (sum >= datalen + sizeof (END_TRAILER) + sizeof (CRLF) - 2) {
+ /* Store all data in param's buffer */
+ memcpy (params[i].buf, p, datalen);
+ /* Increment count */
+ ctx->count++;
+ return OK;
+ }
+ else {
+ /* Store this part of data in param's buffer */
+ memcpy (params[i].buf, p, sum);
+ written += sum;
+ }
+ }
+ else {
+ memc_log (ctx, __LINE__, "memc_read: read(v) failed: %d, %m", r);
+ return SERVER_ERROR;
+ }
+ /* Read data from multiply datagrams */
+ p = read_buf;
+
+ while (sum < datalen + sizeof (END_TRAILER) + sizeof (CRLF) - 2) {
+ retries = 0;
+ while (ctx->protocol == UDP_TEXT) {
+ if (poll_d (ctx->sock, 1, 0, ctx->timeout) != 1) {
+ memc_log (ctx, __LINE__, "memc_read: timeout waiting reply");
+ return SERVER_TIMEOUT;
+ }
+ iov[0].iov_base = &header;
+ iov[0].iov_len = sizeof (struct memc_udp_header);
+ iov[1].iov_base = read_buf;
+ iov[1].iov_len = READ_BUFSIZ;
+ if ((r = readv (ctx->sock, iov, 2)) == -1) {
+ memc_log (ctx, __LINE__, "memc_read: read(v) failed: %d, %m", r);
+ return SERVER_ERROR;
+ }
+ if (header.req_id != ctx->count && retries < MAX_RETRIES) {
+ memc_log (ctx, __LINE__, "memc_read: got wrong packet id: %d, %d was awaited", header.req_id, ctx->count);
+ retries ++;
+ /* Not our reply packet */
+ continue;
+ }
+ }
+ if (ctx->protocol != UDP_TEXT) {
+ if (poll_d (ctx->sock, 1, 0, ctx->timeout) != 1) {
+ memc_log (ctx, __LINE__, "memc_read: timeout waiting reply");
+ return SERVER_TIMEOUT;
+ }
+ r = read (ctx->sock, read_buf, READ_BUFSIZ - 1);
+ }
+
+ p = read_buf;
+ sum += r;
+ if (r <= 0) {
+ break;
+ }
+ /* Copy received buffer to result buffer */
+ while (r--) {
+ /* Break on reading END\r\n */
+ if (strncmp (p, END_TRAILER, sizeof (END_TRAILER) - 1) == 0) {
+ break;
+ }
+ if (written < datalen) {
+ params[i].buf[written++] = *p++;
+ }
+ }
+ }
+ /* Increment count */
+ ctx->count++;
+ }
+
+ return OK;
+}
+
+/*
+ * Common write command handler for memcached
+ */
+memc_error_t
+memc_write (memcached_ctx_t *ctx, const char *cmd, memcached_param_t *params, size_t *nelem, int expire)
+{
+ char read_buf[READ_BUFSIZ];
+ int i, retries, ofl;
+ ssize_t r;
+ struct memc_udp_header header;
+ struct iovec iov[4];
+
+ for (i = 0; i < *nelem; i++) {
+ if (ctx->protocol == UDP_TEXT) {
+ /* Send udp header */
+ bzero (&header, sizeof (header));
+ header.dg_sent = htons (1);
+ header.req_id = ctx->count;
+ }
+
+ r = snprintf (read_buf, READ_BUFSIZ, "%s %s 0 %d %zu" CRLF, cmd, params[i].key, expire, params[i].bufsize);
+ memc_log (ctx, __LINE__, "memc_write: send write request to memcached: %s", read_buf);
+ /* Set socket blocking */
+ ofl = fcntl(ctx->sock, F_GETFL, 0);
+ fcntl(ctx->sock, F_SETFL, ofl & (~O_NONBLOCK));
+
+ if (ctx->protocol == UDP_TEXT) {
+ iov[0].iov_base = &header;
+ iov[0].iov_len = sizeof (struct memc_udp_header);
+ iov[1].iov_base = read_buf;
+ iov[1].iov_len = r;
+ iov[2].iov_base = params[i].buf;
+ iov[2].iov_len = params[i].bufsize;
+ iov[3].iov_base = CRLF;
+ iov[3].iov_len = sizeof (CRLF) - 1;
+ writev (ctx->sock, iov, 4);
+ }
+ else {
+ iov[0].iov_base = read_buf;
+ iov[0].iov_len = r;
+ iov[1].iov_base = params[i].buf;
+ iov[1].iov_len = params[i].bufsize;
+ iov[2].iov_base = CRLF;
+ iov[2].iov_len = sizeof (CRLF) - 1;
+ writev (ctx->sock, iov, 3);
+ }
+
+ /* Restore socket mode */
+ fcntl(ctx->sock, F_SETFL, ofl);
+ /* Read reply from server */
+ if (poll_d (ctx->sock, 1, 0, ctx->timeout) != 1) {
+ memc_log (ctx, __LINE__, "memc_write: server timeout while reading reply");
+ return SERVER_ERROR;
+ }
+ /* Read header */
+ retries = 0;
+ while (ctx->protocol == UDP_TEXT) {
+ if (poll_d (ctx->sock, 1, 0, ctx->timeout) != 1) {
+ memc_log (ctx, __LINE__, "memc_write: timeout waiting reply");
+ return SERVER_TIMEOUT;
+ }
+ iov[0].iov_base = &header;
+ iov[0].iov_len = sizeof (struct memc_udp_header);
+ iov[1].iov_base = read_buf;
+ iov[1].iov_len = READ_BUFSIZ;
+ if ((r = readv (ctx->sock, iov, 2)) == -1) {
+ return SERVER_ERROR;
+ }
+ if (header.req_id != ctx->count && retries < MAX_RETRIES) {
+ retries ++;
+ /* Not our reply packet */
+ continue;
+ }
+ break;
+ }
+ if (ctx->protocol != UDP_TEXT) {
+ if (poll_d (ctx->sock, 1, 0, ctx->timeout) != 1) {
+ memc_log (ctx, __LINE__, "memc_write: timeout waiting reply");
+ return SERVER_TIMEOUT;
+ }
+ r = read (ctx->sock, read_buf, READ_BUFSIZ - 1);
+ }
+ /* Increment count */
+ ctx->count++;
+
+ if (strncmp (read_buf, STORED_TRAILER, sizeof (STORED_TRAILER) - 1) == 0) {
+ continue;
+ }
+ else if (strncmp (read_buf, NOT_STORED_TRAILER, sizeof (NOT_STORED_TRAILER) - 1) == 0) {
+ return CLIENT_ERROR;
+ }
+ else if (strncmp (read_buf, EXISTS_TRAILER, sizeof (EXISTS_TRAILER) - 1) == 0) {
+ return EXISTS;
+ }
+ else {
+ return SERVER_ERROR;
+ }
+ }
+
+ return OK;
+}
+/*
+ * Delete command handler
+ */
+memc_error_t
+memc_delete (memcached_ctx_t *ctx, memcached_param_t *params, size_t *nelem)
+{
+ char read_buf[READ_BUFSIZ];
+ int i, retries;
+ ssize_t r;
+ struct memc_udp_header header;
+ struct iovec iov[2];
+
+ for (i = 0; i < *nelem; i++) {
+ if (ctx->protocol == UDP_TEXT) {
+ /* Send udp header */
+ bzero (&header, sizeof (header));
+ header.dg_sent = htons(1);
+ header.req_id = ctx->count;
+ }
+
+ r = snprintf (read_buf, READ_BUFSIZ, "delete %s" CRLF, params[i].key);
+ memc_log (ctx, __LINE__, "memc_delete: send delete request to memcached: %s", read_buf);
+ if (ctx->protocol == UDP_TEXT) {
+ iov[0].iov_base = &header;
+ iov[0].iov_len = sizeof (struct memc_udp_header);
+ iov[1].iov_base = read_buf;
+ iov[1].iov_len = r;
+ writev (ctx->sock, iov, 2);
+ }
+ else {
+ write (ctx->sock, read_buf, r);
+ }
+
+ /* Read reply from server */
+ retries = 0;
+ while (ctx->protocol == UDP_TEXT) {
+ if (poll_d (ctx->sock, 1, 0, ctx->timeout) != 1) {
+ return SERVER_TIMEOUT;
+ }
+ iov[0].iov_base = &header;
+ iov[0].iov_len = sizeof (struct memc_udp_header);
+ iov[1].iov_base = read_buf;
+ iov[1].iov_len = READ_BUFSIZ;
+ if ((r = readv (ctx->sock, iov, 2)) == -1) {
+ return SERVER_ERROR;
+ }
+ if (header.req_id != ctx->count && retries < MAX_RETRIES) {
+ retries ++;
+ /* Not our reply packet */
+ continue;
+ }
+ break;
+ }
+ if (ctx->protocol != UDP_TEXT) {
+ if (poll_d (ctx->sock, 1, 0, ctx->timeout) != 1) {
+ return SERVER_TIMEOUT;
+ }
+ r = read (ctx->sock, read_buf, READ_BUFSIZ - 1);
+ }
+
+ /* Increment count */
+ ctx->count++;
+ if (strncmp (read_buf, DELETED_TRAILER, sizeof (DELETED_TRAILER) - 1) == 0) {
+ continue;
+ }
+ else if (strncmp (read_buf, NOT_FOUND_TRAILER, sizeof (NOT_FOUND_TRAILER) - 1) == 0) {
+ return NOT_EXISTS;
+ }
+ else {
+ return SERVER_ERROR;
+ }
+ }
+
+ return OK;
+}
+
+/*
+ * Write handler for memcached mirroring
+ * writing is done to each memcached server
+ */
+memc_error_t
+memc_write_mirror (memcached_ctx_t *ctx, size_t memcached_num, const char *cmd, memcached_param_t *params, size_t *nelem, int expire)
+{
+ memc_error_t r, result = OK;
+
+ while (memcached_num --) {
+ if (ctx[memcached_num].alive == 1) {
+ r = memc_write (&ctx[memcached_num], cmd, params, nelem, expire);
+ if (r != OK) {
+ memc_log (&ctx[memcached_num], __LINE__, "memc_write_mirror: cannot write to mirror server: %s", memc_strerror (r));
+ result = r;
+ ctx[memcached_num].alive = 0;
+ }
+ }
+ }
+
+ return result;
+}
+
+/*
+ * Read handler for memcached mirroring
+ * reading is done from first active memcached server
+ */
+memc_error_t
+memc_read_mirror (memcached_ctx_t *ctx, size_t memcached_num, const char *cmd, memcached_param_t *params, size_t *nelem)
+{
+ memc_error_t r, result = OK;
+
+ while (memcached_num --) {
+ if (ctx[memcached_num].alive == 1) {
+ r = memc_read (&ctx[memcached_num], cmd, params, nelem);
+ if (r != OK) {
+ result = r;
+ if (r != NOT_EXISTS) {
+ ctx[memcached_num].alive = 0;
+ memc_log (&ctx[memcached_num], __LINE__, "memc_read_mirror: cannot write read from mirror server: %s", memc_strerror (r));
+ }
+ else {
+ memc_log (&ctx[memcached_num], __LINE__, "memc_read_mirror: record not exists", memc_strerror (r));
+ }
+ }
+ else {
+ break;
+ }
+ }
+ }
+
+ return result;
+}
+
+/*
+ * Delete handler for memcached mirroring
+ * deleting is done for each active memcached server
+ */
+memc_error_t
+memc_delete_mirror (memcached_ctx_t *ctx, size_t memcached_num, const char *cmd, memcached_param_t *params, size_t *nelem)
+{
+ memc_error_t r, result = OK;
+
+ while (memcached_num --) {
+ if (ctx[memcached_num].alive == 1) {
+ r = memc_delete (&ctx[memcached_num], params, nelem);
+ if (r != OK) {
+ result = r;
+ if (r != NOT_EXISTS) {
+ ctx[memcached_num].alive = 0;
+ memc_log (&ctx[memcached_num], __LINE__, "memc_delete_mirror: cannot delete from mirror server: %s", memc_strerror (r));
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+
+/*
+ * Initialize memcached context for specified protocol
+ */
+int
+memc_init_ctx (memcached_ctx_t *ctx)
+{
+ if (ctx == NULL) {
+ return -1;
+ }
+
+ ctx->count = 0;
+ ctx->alive = 1;
+
+ switch (ctx->protocol) {
+ case UDP_TEXT:
+ return memc_make_udp_sock (ctx);
+ break;
+ case TCP_TEXT:
+ return memc_make_tcp_sock (ctx);
+ break;
+ /* Not implemented */
+ case UDP_BIN:
+ case TCP_BIN:
+ default:
+ return -1;
+ }
+}
+/*
+ * Mirror init
+ */
+int
+memc_init_ctx_mirror (memcached_ctx_t *ctx, size_t memcached_num)
+{
+ int r, result = -1;
+ while (memcached_num--) {
+ if (ctx[memcached_num].alive == 1) {
+ r = memc_init_ctx (&ctx[memcached_num]);
+ if (r == -1) {
+ ctx[memcached_num].alive = 0;
+ memc_log (&ctx[memcached_num], __LINE__, "memc_init_ctx_mirror: cannot connect to server");
+ }
+ else {
+ result = 1;
+ }
+ }
+ }
+
+ return result;
+}
+
+/*
+ * Close context connection
+ */
+int
+memc_close_ctx (memcached_ctx_t *ctx)
+{
+ if (ctx != NULL && ctx->sock != -1) {
+ return close (ctx->sock);
+ }
+
+ return -1;
+}
+/*
+ * Mirror close
+ */
+int
+memc_close_ctx_mirror (memcached_ctx_t *ctx, size_t memcached_num)
+{
+ int r = 0;
+ while (memcached_num--) {
+ if (ctx[memcached_num].alive == 1) {
+ r = memc_close_ctx (&ctx[memcached_num]);
+ if (r == -1) {
+ memc_log (&ctx[memcached_num], __LINE__, "memc_close_ctx_mirror: cannot close connection to server properly");
+ ctx[memcached_num].alive = 0;
+ }
+ }
+ }
+
+ return r;
+}
+
+
+const char * memc_strerror (memc_error_t err)
+{
+ const char *p;
+
+ switch (err) {
+ case OK:
+ p = "Ok";
+ break;
+ case BAD_COMMAND:
+ p = "Bad command";
+ break;
+ case CLIENT_ERROR:
+ p = "Client error";
+ break;
+ case SERVER_ERROR:
+ p = "Server error";
+ break;
+ case SERVER_TIMEOUT:
+ p = "Server timeout";
+ break;
+ case NOT_EXISTS:
+ p = "Key not found";
+ break;
+ case EXISTS:
+ p = "Key already exists";
+ break;
+ case WRONG_LENGTH:
+ p = "Wrong result length";
+ break;
+ default:
+ p = "Unknown error";
+ break;
+ }
+
+ return p;
+}
+
+/*
+ * vi:ts=4
+ */
diff --git a/memcached.h b/memcached.h
new file mode 100644
index 000000000..c0ae896e3
--- /dev/null
+++ b/memcached.h
@@ -0,0 +1,113 @@
+#ifndef MEMCACHED_H
+#define MEMCACHED_H
+
+#include <sys/types.h>
+#include <netinet/in.h>
+
+#define MAXKEYLEN 250
+
+#define MEMC_OPT_DEBUG 0x1
+
+typedef enum memc_error {
+ OK,
+ BAD_COMMAND,
+ CLIENT_ERROR,
+ SERVER_ERROR,
+ SERVER_TIMEOUT,
+ NOT_EXISTS,
+ EXISTS,
+ WRONG_LENGTH
+} memc_error_t;
+
+/* XXX: Only UDP_TEXT is supported at present */
+typedef enum memc_proto {
+ UDP_TEXT,
+ TCP_TEXT,
+ UDP_BIN,
+ TCP_BIN
+} memc_proto_t;
+
+/* Port must be in network byte order */
+typedef struct memcached_ctx_s {
+ memc_proto_t protocol;
+ struct in_addr addr;
+ uint16_t port;
+ int sock;
+ int timeout;
+ /* Counter that is used for memcached operations in network byte order */
+ uint16_t count;
+ /* Flag that signalize that this memcached is alive */
+ short alive;
+ /* Options that can be specified for memcached connection */
+ short options;
+} memcached_ctx_t;
+
+typedef struct memcached_param_s {
+ char key[MAXKEYLEN];
+ u_char *buf;
+ size_t bufsize;
+} memcached_param_t;
+
+/*
+ * Initialize connection to memcached server:
+ * addr, port and timeout fields in ctx must be filled with valid values
+ * Return:
+ * 0 - success
+ * -1 - error (error is stored in errno)
+ */
+int memc_init_ctx (memcached_ctx_t *ctx);
+int memc_init_ctx_mirror (memcached_ctx_t *ctx, size_t memcached_num);
+/*
+ * Memcached function for getting, setting, adding values to memcached server
+ * ctx - valid memcached context
+ * key - key to extract (max 250 characters as it specified in memcached API)
+ * buf, elemsize, nelem - allocated buffer of length nelem structures each of elemsize
+ * that would contain extracted data (NOT NULL TERMINATED)
+ * Return:
+ * memc_error_t
+ * nelem is changed according to actual number of extracted data
+ *
+ * "set" means "store this data".
+ *
+ * "add" means "store this data, but only if the server *doesn't* already
+ * hold data for this key".
+
+ * "replace" means "store this data, but only if the server *does*
+ * already hold data for this key".
+
+ * "append" means "add this data to an existing key after existing data".
+
+ * "prepend" means "add this data to an existing key before existing data".
+ */
+#define memc_get(ctx, params, nelem) memc_read(ctx, "get", params, nelem)
+#define memc_set(ctx, params, nelem, expire) memc_write(ctx, "set", params, nelem, expire)
+#define memc_add(ctx, params, nelem, expire) memc_write(ctx, "add", params, nelem, expire)
+#define memc_replace(ctx, params, nelem, expire) memc_write(ctx, "replace", params, nelem, expire)
+#define memc_append(ctx, params, nelem, expire) memc_write(ctx, "append", params, nelem, expire)
+#define memc_prepend(ctx, params, nelem, expire) memc_write(ctx, "prepend", params, nelem, expire)
+
+/* Functions that works with mirror of memcached servers */
+#define memc_get_mirror(ctx, num, params, nelem) memc_read_mirror(ctx, num, "get", params, nelem)
+#define memc_set_mirror(ctx, num, params, nelem, expire) memc_write_mirror(ctx, num, "set", params, nelem, expire)
+#define memc_add_mirror(ctx, num, params, nelem, expire) memc_write_mirror(ctx, num, "add", params, nelem, expire)
+#define memc_replace_mirror(ctx, num, params, nelem, expire) memc_write_mirror(ctx, num, "replace", params, nelem, expire)
+#define memc_append_mirror(ctx, num, params, nelem, expire) memc_write_mirror(ctx, num, "append", params, nelem, expire)
+#define memc_prepend_mirror(ctx, num, params, nelem, expire) memc_write_mirror(ctx, num, "prepend", params, nelem, expire)
+
+
+memc_error_t memc_read (memcached_ctx_t *ctx, const char *cmd, memcached_param_t *params, size_t *nelem);
+memc_error_t memc_write (memcached_ctx_t *ctx, const char *cmd, memcached_param_t *params, size_t *nelem, int expire);
+memc_error_t memc_delete (memcached_ctx_t *ctx, memcached_param_t *params, size_t *nelem);
+
+memc_error_t memc_write_mirror (memcached_ctx_t *ctx, size_t memcached_num, const char *cmd, memcached_param_t *params, size_t *nelem, int expire);
+memc_error_t memc_read_mirror (memcached_ctx_t *ctx, size_t memcached_num, const char *cmd, memcached_param_t *params, size_t *nelem);
+memc_error_t memc_delete_mirror (memcached_ctx_t *ctx, size_t memcached_num, const char *cmd, memcached_param_t *params, size_t *nelem);
+
+/* Return symbolic name of memcached error*/
+const char * memc_strerror (memc_error_t err);
+
+/* Destroy socket from ctx */
+int memc_close_ctx (memcached_ctx_t *ctx);
+int memc_close_ctx_mirror (memcached_ctx_t *ctx, size_t memcached_num);
+
+#endif
diff --git a/upstream.c b/upstream.c
new file mode 100644
index 000000000..87aab8535
--- /dev/null
+++ b/upstream.c
@@ -0,0 +1,521 @@
+#ifdef _THREAD_SAFE
+#include <pthread.h>
+#endif
+
+#include <sys/types.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+#include <inttypes.h>
+#endif
+#include <limits.h>
+#ifdef WITH_DEBUG
+#include <syslog.h>
+#endif
+#include "upstream.h"
+
+#ifdef WITH_DEBUG
+#define msg_debug(args...) syslog(LOG_DEBUG, ##args)
+#else
+#define msg_debug(args...) do {} while(0)
+#endif
+
+#ifdef _THREAD_SAFE
+pthread_rwlock_t upstream_mtx = PTHREAD_RWLOCK_INITIALIZER;
+#define U_RLOCK() do { pthread_rwlock_rdlock (&upstream_mtx); } while (0)
+#define U_WLOCK() do { pthread_rwlock_wrlock (&upstream_mtx); } while (0)
+#define U_UNLOCK() do { pthread_rwlock_unlock (&upstream_mtx); } while (0)
+#else
+#define U_RLOCK() do {} while (0)
+#define U_WLOCK() do {} while (0)
+#define U_UNLOCK() do {} while (0)
+#endif
+
+#define MAX_TRIES 20
+
+/*
+ * Poly: 0xedb88320
+ * Init: 0x0
+ */
+
+static const uint32_t crc32lookup[256] = {
+ 0x00000000U, 0x77073096U, 0xee0e612cU, 0x990951baU, 0x076dc419U, 0x706af48fU,
+ 0xe963a535U, 0x9e6495a3U, 0x0edb8832U, 0x79dcb8a4U, 0xe0d5e91eU, 0x97d2d988U,
+ 0x09b64c2bU, 0x7eb17cbdU, 0xe7b82d07U, 0x90bf1d91U, 0x1db71064U, 0x6ab020f2U,
+ 0xf3b97148U, 0x84be41deU, 0x1adad47dU, 0x6ddde4ebU, 0xf4d4b551U, 0x83d385c7U,
+ 0x136c9856U, 0x646ba8c0U, 0xfd62f97aU, 0x8a65c9ecU, 0x14015c4fU, 0x63066cd9U,
+ 0xfa0f3d63U, 0x8d080df5U, 0x3b6e20c8U, 0x4c69105eU, 0xd56041e4U, 0xa2677172U,
+ 0x3c03e4d1U, 0x4b04d447U, 0xd20d85fdU, 0xa50ab56bU, 0x35b5a8faU, 0x42b2986cU,
+ 0xdbbbc9d6U, 0xacbcf940U, 0x32d86ce3U, 0x45df5c75U, 0xdcd60dcfU, 0xabd13d59U,
+ 0x26d930acU, 0x51de003aU, 0xc8d75180U, 0xbfd06116U, 0x21b4f4b5U, 0x56b3c423U,
+ 0xcfba9599U, 0xb8bda50fU, 0x2802b89eU, 0x5f058808U, 0xc60cd9b2U, 0xb10be924U,
+ 0x2f6f7c87U, 0x58684c11U, 0xc1611dabU, 0xb6662d3dU, 0x76dc4190U, 0x01db7106U,
+ 0x98d220bcU, 0xefd5102aU, 0x71b18589U, 0x06b6b51fU, 0x9fbfe4a5U, 0xe8b8d433U,
+ 0x7807c9a2U, 0x0f00f934U, 0x9609a88eU, 0xe10e9818U, 0x7f6a0dbbU, 0x086d3d2dU,
+ 0x91646c97U, 0xe6635c01U, 0x6b6b51f4U, 0x1c6c6162U, 0x856530d8U, 0xf262004eU,
+ 0x6c0695edU, 0x1b01a57bU, 0x8208f4c1U, 0xf50fc457U, 0x65b0d9c6U, 0x12b7e950U,
+ 0x8bbeb8eaU, 0xfcb9887cU, 0x62dd1ddfU, 0x15da2d49U, 0x8cd37cf3U, 0xfbd44c65U,
+ 0x4db26158U, 0x3ab551ceU, 0xa3bc0074U, 0xd4bb30e2U, 0x4adfa541U, 0x3dd895d7U,
+ 0xa4d1c46dU, 0xd3d6f4fbU, 0x4369e96aU, 0x346ed9fcU, 0xad678846U, 0xda60b8d0U,
+ 0x44042d73U, 0x33031de5U, 0xaa0a4c5fU, 0xdd0d7cc9U, 0x5005713cU, 0x270241aaU,
+ 0xbe0b1010U, 0xc90c2086U, 0x5768b525U, 0x206f85b3U, 0xb966d409U, 0xce61e49fU,
+ 0x5edef90eU, 0x29d9c998U, 0xb0d09822U, 0xc7d7a8b4U, 0x59b33d17U, 0x2eb40d81U,
+ 0xb7bd5c3bU, 0xc0ba6cadU, 0xedb88320U, 0x9abfb3b6U, 0x03b6e20cU, 0x74b1d29aU,
+ 0xead54739U, 0x9dd277afU, 0x04db2615U, 0x73dc1683U, 0xe3630b12U, 0x94643b84U,
+ 0x0d6d6a3eU, 0x7a6a5aa8U, 0xe40ecf0bU, 0x9309ff9dU, 0x0a00ae27U, 0x7d079eb1U,
+ 0xf00f9344U, 0x8708a3d2U, 0x1e01f268U, 0x6906c2feU, 0xf762575dU, 0x806567cbU,
+ 0x196c3671U, 0x6e6b06e7U, 0xfed41b76U, 0x89d32be0U, 0x10da7a5aU, 0x67dd4accU,
+ 0xf9b9df6fU, 0x8ebeeff9U, 0x17b7be43U, 0x60b08ed5U, 0xd6d6a3e8U, 0xa1d1937eU,
+ 0x38d8c2c4U, 0x4fdff252U, 0xd1bb67f1U, 0xa6bc5767U, 0x3fb506ddU, 0x48b2364bU,
+ 0xd80d2bdaU, 0xaf0a1b4cU, 0x36034af6U, 0x41047a60U, 0xdf60efc3U, 0xa867df55U,
+ 0x316e8eefU, 0x4669be79U, 0xcb61b38cU, 0xbc66831aU, 0x256fd2a0U, 0x5268e236U,
+ 0xcc0c7795U, 0xbb0b4703U, 0x220216b9U, 0x5505262fU, 0xc5ba3bbeU, 0xb2bd0b28U,
+ 0x2bb45a92U, 0x5cb36a04U, 0xc2d7ffa7U, 0xb5d0cf31U, 0x2cd99e8bU, 0x5bdeae1dU,
+ 0x9b64c2b0U, 0xec63f226U, 0x756aa39cU, 0x026d930aU, 0x9c0906a9U, 0xeb0e363fU,
+ 0x72076785U, 0x05005713U, 0x95bf4a82U, 0xe2b87a14U, 0x7bb12baeU, 0x0cb61b38U,
+ 0x92d28e9bU, 0xe5d5be0dU, 0x7cdcefb7U, 0x0bdbdf21U, 0x86d3d2d4U, 0xf1d4e242U,
+ 0x68ddb3f8U, 0x1fda836eU, 0x81be16cdU, 0xf6b9265bU, 0x6fb077e1U, 0x18b74777U,
+ 0x88085ae6U, 0xff0f6a70U, 0x66063bcaU, 0x11010b5cU, 0x8f659effU, 0xf862ae69U,
+ 0x616bffd3U, 0x166ccf45U, 0xa00ae278U, 0xd70dd2eeU, 0x4e048354U, 0x3903b3c2U,
+ 0xa7672661U, 0xd06016f7U, 0x4969474dU, 0x3e6e77dbU, 0xaed16a4aU, 0xd9d65adcU,
+ 0x40df0b66U, 0x37d83bf0U, 0xa9bcae53U, 0xdebb9ec5U, 0x47b2cf7fU, 0x30b5ffe9U,
+ 0xbdbdf21cU, 0xcabac28aU, 0x53b39330U, 0x24b4a3a6U, 0xbad03605U, 0xcdd70693U,
+ 0x54de5729U, 0x23d967bfU, 0xb3667a2eU, 0xc4614ab8U, 0x5d681b02U, 0x2a6f2b94U,
+ 0xb40bbe37U, 0xc30c8ea1U, 0x5a05df1bU, 0x2d02ef8dU
+};
+
+/*
+ * Check upstream parameters and mark it whether valid or dead
+ */
+static void
+check_upstream (struct upstream *up, time_t now, time_t error_timeout, time_t revive_timeout, size_t max_errors)
+{
+ if (up->dead) {
+ if (now - up->time >= revive_timeout) {
+ msg_debug ("check_upstream: reviving upstream after %ld seconds", (long int) now - up->time);
+ U_WLOCK ();
+ up->dead = 0;
+ up->errors = 0;
+ up->time = 0;
+ up->weight = up->priority;
+ U_UNLOCK ();
+ }
+ }
+ else {
+ if (now - up->time >= error_timeout && up->errors >= max_errors) {
+ msg_debug ("check_upstream: marking upstreams as dead after %ld errors", (long int) up->errors);
+ U_WLOCK ();
+ up->dead = 1;
+ up->time = now;
+ up->weight = 0;
+ U_UNLOCK ();
+ }
+ }
+}
+
+/*
+ * Call this function after failed upstream request
+ */
+void
+upstream_fail (struct upstream *up, time_t now)
+{
+ if (up->time != 0) {
+ up->errors ++;
+ }
+ else {
+ U_WLOCK ();
+ up->time = now;
+ up->errors ++;
+ U_UNLOCK ();
+ }
+}
+/*
+ * Call this function after successfull upstream request
+ */
+void
+upstream_ok (struct upstream *up, time_t now)
+{
+ if (up->errors != 0) {
+ U_WLOCK ();
+ up->errors = 0;
+ up->time = 0;
+ U_UNLOCK ();
+ }
+
+ up->weight --;
+}
+/*
+ * Mark all upstreams as active. This function is used when all upstreams are marked as inactive
+ */
+void
+revive_all_upstreams (void *ups, size_t members, size_t msize)
+{
+ int i;
+ struct upstream *cur;
+ u_char *p;
+
+ U_WLOCK ();
+ msg_debug ("revive_all_upstreams: starting reviving all upstreams");
+ p = ups;
+ for (i = 0; i < members; i++) {
+ cur = (struct upstream *)p;
+ cur->time = 0;
+ cur->errors = 0;
+ cur->dead = 0;
+ cur->weight = cur->priority;
+ p += msize;
+ }
+ U_UNLOCK ();
+}
+
+/*
+ * Scan all upstreams for errors and mark upstreams dead or alive depends on conditions,
+ * return number of alive upstreams
+ */
+static int
+rescan_upstreams (void *ups, size_t members, size_t msize, time_t now, time_t error_timeout, time_t revive_timeout, size_t max_errors)
+{
+ int i, alive;
+ struct upstream *cur;
+ u_char *p;
+
+ /* Recheck all upstreams */
+ p = ups;
+ alive = members;
+ for (i = 0; i < members; i++) {
+ cur = (struct upstream *)p;
+ check_upstream (cur, now, error_timeout, revive_timeout, max_errors);
+ alive -= cur->dead;
+ p += msize;
+ }
+
+ /* All upstreams are dead */
+ if (alive == 0) {
+ revive_all_upstreams (ups, members, msize);
+ alive = members;
+ }
+
+ msg_debug ("rescan_upstreams: %d upstreams alive", alive);
+
+ return alive;
+
+}
+
+/* Return alive upstream by its number */
+static struct upstream *
+get_upstream_by_number (void *ups, size_t members, size_t msize, int selected)
+{
+ int i;
+ u_char *p, *c;
+ struct upstream *cur;
+
+ i = 0;
+ p = ups;
+ c = ups;
+ U_RLOCK ();
+ for (;;) {
+ /* Out of range, return NULL */
+ if (p > c + members * msize) {
+ break;
+ }
+
+ cur = (struct upstream *)p;
+ p += msize;
+
+ if (cur->dead) {
+ /* Skip inactive upstreams */
+ continue;
+ }
+ /* Return selected upstream */
+ if (i == selected) {
+ U_UNLOCK ();
+ return cur;
+ }
+ i++;
+ }
+ U_UNLOCK ();
+
+ /* Error */
+ return NULL;
+
+}
+
+/*
+ * Get hash key for specified key (perl hash)
+ */
+static uint32_t
+get_hash_for_key (uint32_t hash, char *key, size_t keylen)
+{
+ uint32_t h, index;
+ const char *end = key + keylen;
+
+ h = ~hash;
+
+ while (key < end) {
+ index = (h ^ (u_char) *key) & 0x000000ffU;
+ h = (h >> 8) ^ crc32lookup[index];
+ ++key;
+ }
+
+ return (~h);
+}
+
+/*
+ * Recheck all upstreams and return random active upstream
+ */
+struct upstream *
+get_random_upstream (void *ups, size_t members, size_t msize, time_t now, time_t error_timeout, time_t revive_timeout, size_t max_errors)
+{
+ int alive, selected;
+
+ alive = rescan_upstreams (ups, members, msize, now, error_timeout, revive_timeout, max_errors);
+ selected = rand () % alive;
+ msg_debug ("get_random_upstream: return upstream with number %d of %d", selected, alive);
+
+ return get_upstream_by_number (ups, members, msize, selected);
+}
+
+/*
+ * Return upstream by hash, that is calculated from active upstreams number
+ */
+struct upstream *
+get_upstream_by_hash (void *ups, size_t members, size_t msize, time_t now,
+ time_t error_timeout, time_t revive_timeout, size_t max_errors,
+ char *key, size_t keylen)
+{
+ int alive, tries = 0, r;
+ uint32_t h = 0, ht;
+ char *p, numbuf[4];
+ struct upstream *cur;
+
+ alive = rescan_upstreams (ups, members, msize, now, error_timeout, revive_timeout, max_errors);
+
+ if (alive == 0) {
+ return NULL;
+ }
+
+ h = get_hash_for_key (0, key, keylen);
+#ifdef HASH_COMPAT
+ h = (h >> 16) & 0x7fff;
+#endif
+ h %= members;
+ msg_debug ("get_upstream_by_hash: try to select upstream number %d of %zd", h, members);
+
+ for (;;) {
+ p = (char *)ups + msize * h;
+ cur = (struct upstream *)p;
+ if (!cur->dead) {
+ break;
+ }
+ r = snprintf (numbuf, sizeof (numbuf), "%d", tries);
+ ht = get_hash_for_key (0, numbuf, r);
+ ht = get_hash_for_key (ht, key, keylen);
+#ifdef HASH_COMPAT
+ h += (ht >> 16) & 0x7fff;
+#else
+ h += ht;
+#endif
+ h %= members;
+ msg_debug ("get_upstream_by_hash: try to select upstream number %d of %zd, tries: %d", h, members, tries);
+ tries ++;
+ if (tries > MAX_TRIES) {
+ msg_debug ("get_upstream_by_hash: max tries exceed, returning NULL");
+ return NULL;
+ }
+ }
+
+ U_RLOCK ();
+ p = ups;
+ U_UNLOCK ();
+ return cur;
+}
+
+/*
+ * Recheck all upstreams and return upstream in round-robin order according to weight and priority
+ */
+struct upstream *
+get_upstream_round_robin (void *ups, size_t members, size_t msize, time_t now, time_t error_timeout, time_t revive_timeout, size_t max_errors)
+{
+ int alive, max_weight, i;
+ struct upstream *cur, *selected = NULL;
+ u_char *p;
+
+ /* Recheck all upstreams */
+ alive = rescan_upstreams (ups, members, msize, now, error_timeout, revive_timeout, max_errors);
+
+ p = ups;
+ max_weight = 0;
+ selected = (struct upstream *)p;
+ U_RLOCK ();
+ for (i = 0; i < members; i++) {
+ cur = (struct upstream *)p;
+ if (!cur->dead) {
+ if (max_weight < cur->weight) {
+ max_weight = cur->weight;
+ selected = cur;
+ }
+ }
+ p += msize;
+ }
+ U_UNLOCK ();
+
+ if (max_weight == 0) {
+ p = ups;
+ U_WLOCK ();
+ for (i = 0; i < members; i++) {
+ cur = (struct upstream *)p;
+ cur->weight = cur->priority;
+ if (!cur->dead) {
+ if (max_weight < cur->priority) {
+ max_weight = cur->priority;
+ selected = cur;
+ }
+ }
+ p += msize;
+ }
+ U_UNLOCK ();
+ }
+ msg_debug ("get_upstream_round_robin: selecting upstream with weight %d", max_weight);
+
+ return selected;
+}
+
+/*
+ * Recheck all upstreams and return upstream in round-robin order according to only priority (master-slaves)
+ */
+struct upstream *
+get_upstream_master_slave (void *ups, size_t members, size_t msize, time_t now, time_t error_timeout, time_t revive_timeout, size_t max_errors)
+{
+ int alive, max_weight, i;
+ struct upstream *cur, *selected = NULL;
+ u_char *p;
+
+ /* Recheck all upstreams */
+ alive = rescan_upstreams (ups, members, msize, now, error_timeout, revive_timeout, max_errors);
+
+ p = ups;
+ max_weight = 0;
+ selected = (struct upstream *)p;
+ U_RLOCK ();
+ for (i = 0; i < members; i++) {
+ cur = (struct upstream *)p;
+ if (!cur->dead) {
+ if (max_weight < cur->priority) {
+ max_weight = cur->priority;
+ selected = cur;
+ }
+ }
+ p += msize;
+ }
+ U_UNLOCK ();
+ msg_debug ("get_upstream_master_slave: selecting upstream with priority %d", max_weight);
+
+ return selected;
+}
+
+/*
+ * Ketama manipulation functions
+ */
+
+static int
+ketama_sort_cmp (const void *a1, const void *a2)
+{
+ return *((uint32_t *)a1) - *((uint32_t *)a2);
+}
+
+/*
+ * Add ketama points for specified upstream
+ */
+int
+upstream_ketama_add (struct upstream *up, char *up_key, size_t keylen, size_t keypoints)
+{
+ uint32_t h = 0;
+ char tmp[4];
+ int i;
+
+ /* Allocate ketama points array */
+ if (up->ketama_points == NULL) {
+ up->ketama_points_size = keypoints;
+ up->ketama_points = malloc (sizeof (uint32_t) * up->ketama_points_size);
+ if (up->ketama_points == NULL) {
+ return -1;
+ }
+ }
+
+ h = get_hash_for_key (h, up_key, keylen);
+
+ for (i = 0; i < keypoints; i++) {
+ tmp[0] = i & 0xff;
+ tmp[1] = (i >> 8) & 0xff;
+ tmp[2] = (i >> 16) & 0xff;
+ tmp[3] = (i >> 24) & 0xff;
+
+ h = get_hash_for_key (h, tmp, sizeof (tmp) * sizeof (char));
+ up->ketama_points[i] = h;
+ }
+ /* Keep points sorted */
+ qsort (up->ketama_points, keypoints, sizeof (uint32_t), ketama_sort_cmp);
+
+ return 0;
+}
+
+/*
+ * Return upstream by hash and find nearest ketama point in some server
+ */
+struct upstream *
+get_upstream_by_hash_ketama (void *ups, size_t members, size_t msize, time_t now,
+ time_t error_timeout, time_t revive_timeout, size_t max_errors,
+ char *key, size_t keylen)
+{
+ int alive, i;
+ uint32_t h = 0, step, middle, d, min_diff = UINT_MAX;
+ char *p;
+ struct upstream *cur = NULL, *nearest = NULL;
+
+ alive = rescan_upstreams (ups, members, msize, now, error_timeout, revive_timeout, max_errors);
+
+ if (alive == 0) {
+ return NULL;
+ }
+
+ h = get_hash_for_key (h, key, keylen);
+
+ U_RLOCK ();
+ p = ups;
+ nearest = (struct upstream *)p;
+ for (i = 0; i < members; i++) {
+ cur = (struct upstream *)p;
+ if (!cur->dead && cur->ketama_points != NULL) {
+ /* Find nearest ketama point for this key */
+ step = cur->ketama_points_size / 2;
+ middle = step;
+ while (step != 1) {
+ d = cur->ketama_points[middle] - h;
+ if (abs (d) < min_diff) {
+ min_diff = abs (d);
+ nearest = cur;
+ }
+ step /= 2;
+ if (d > 0) {
+ middle -= step;
+ }
+ else {
+ middle += step;
+ }
+ }
+ }
+ }
+ U_UNLOCK ();
+ return nearest;
+}
+
+#undef U_LOCK
+#undef U_UNLOCK
+#undef msg_debug
+/*
+ * vi:ts=4
+ */
diff --git a/upstream.h b/upstream.h
new file mode 100644
index 000000000..ad2ac4f4b
--- /dev/null
+++ b/upstream.h
@@ -0,0 +1,43 @@
+#ifndef UPSTREAM_H
+#define UPSTREAM_H
+
+#include <sys/types.h>
+
+
+struct upstream {
+ unsigned int errors;
+ time_t time;
+ unsigned char dead;
+ unsigned char priority;
+ int16_t weight;
+ uint32_t *ketama_points;
+ size_t ketama_points_size;
+};
+
+void upstream_fail (struct upstream *up, time_t now);
+void upstream_ok (struct upstream *up, time_t now);
+void revive_all_upstreams (void *ups, size_t members, size_t msize);
+int upstream_ketama_add (struct upstream *up, char *up_key, size_t keylen, size_t keypoints);
+
+struct upstream* get_random_upstream (void *ups, size_t members, size_t msize,
+ time_t now, time_t error_timeout,
+ time_t revive_timeout, size_t max_errors);
+
+struct upstream* get_upstream_by_hash (void *ups, size_t members, size_t msize,
+ time_t now, time_t error_timeout,
+ time_t revive_timeout, size_t max_errors,
+ char *key, size_t keylen);
+
+struct upstream* get_upstream_round_robin (void *ups, size_t members, size_t msize,
+ time_t now, time_t error_timeout,
+ time_t revive_timeout, size_t max_errors);
+
+struct upstream* get_upstream_by_hash_ketama (void *ups, size_t members, size_t msize, time_t now,
+ time_t error_timeout, time_t revive_timeout, size_t max_errors,
+ char *key, size_t keylen);
+
+
+#endif /* UPSTREAM_H */
+/*
+ * vi:ts=4
+ */
diff --git a/util.c b/util.c
new file mode 100644
index 000000000..65cafb97e
--- /dev/null
+++ b/util.c
@@ -0,0 +1,534 @@
+#include <sys/param.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <errno.h>
+#include <unistd.h>
+#ifdef HAVE_LIBUTIL_H
+#include <libutil.h>
+#endif
+#include <stdarg.h>
+#include <sys/file.h>
+#include "util.h"
+#include "cfg_file.h"
+
+int
+event_make_socket_nonblocking (int fd)
+{
+ if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) {
+ return -1;
+ }
+ return 0;
+}
+
+static int
+make_socket_ai (struct addrinfo *ai)
+{
+ struct linger linger;
+ int fd, on = 1, r;
+ int serrno;
+
+ /* Create listen socket */
+ fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (fd == -1) {
+ return (-1);
+ }
+
+ if (event_make_socket_nonblocking(fd) < 0)
+ goto out;
+
+ if (fcntl(fd, F_SETFD, 1) == -1) {
+ goto out;
+ }
+
+ setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on));
+ setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));
+ linger.l_onoff = 1;
+ linger.l_linger = 5;
+ setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *)&linger, sizeof(linger));
+
+ r = bind(fd, ai->ai_addr, ai->ai_addrlen);
+
+ if (r == -1) {
+ if (errno != EINPROGRESS) {
+ goto out;
+ }
+ }
+
+ return (fd);
+
+ out:
+ serrno = errno;
+ close(fd);
+ errno = serrno;
+ return (-1);
+}
+
+int
+make_socket (const char *address, u_short port)
+{
+ int fd;
+ struct addrinfo ai, *aitop = NULL;
+ char strport[NI_MAXSERV];
+ int ai_result;
+
+ memset(&ai, 0, sizeof (ai));
+ ai.ai_family = AF_INET;
+ ai.ai_socktype = SOCK_STREAM;
+ ai.ai_flags = AI_PASSIVE;
+ snprintf(strport, sizeof (strport), "%d", port);
+ if ((ai_result = getaddrinfo(address, strport, &ai, &aitop)) != 0) {
+ return (-1);
+ }
+
+ fd = make_socket_ai(aitop);
+
+ freeaddrinfo(aitop);
+
+ return (fd);
+}
+
+int
+make_unix_socket (const char *path, struct sockaddr_un *addr)
+{
+ size_t len = strlen (path);
+ int sock;
+
+ if (len > sizeof (addr->sun_path) - 1) return -1;
+
+ #ifdef FREEBSD
+ addr->sun_len = sizeof (struct sockaddr_un);
+ #endif
+
+ addr->sun_family = AF_UNIX;
+
+ strncpy (addr->sun_path, path, len);
+
+ sock = socket (PF_LOCAL, SOCK_STREAM, 0);
+
+ if (sock != -1) {
+ if (bind (sock, (struct sockaddr *) addr, sizeof (struct sockaddr_un)) == -1) return -1;
+ }
+
+ return sock;
+}
+
+void
+read_cmd_line (int argc, char **argv, struct config_file *cfg)
+{
+ int ch;
+ while ((ch = getopt(argc, argv, "hfc:")) != -1) {
+ switch (ch) {
+ case 'f':
+ cfg->no_fork = 1;
+ break;
+ case 'c':
+ if (optarg && cfg->cfg_name) {
+ free (cfg->cfg_name);
+ cfg->cfg_name = strdup (optarg);
+ }
+ break;
+ case 'h':
+ case '?':
+ default:
+ /* Show help message and exit */
+ printf ("Rspamd version " RVERSION "\n"
+ "Usage: rspamd [-h] [-n] [-f] [-c config_file]\n"
+ "-h: This help message\n"
+ "-f: Do not daemonize main process\n"
+ "-c: Specify config file (./rspamd.conf is used by default)\n");
+ exit (0);
+ break;
+ }
+ }
+}
+
+int
+write_pid (struct rspamd_main *main)
+{
+ pid_t pid;
+ main->pfh = pidfile_open (main->cfg->pid_file, 0644, &pid);
+
+ if (main->pfh == NULL) {
+ return -1;
+ }
+
+ pidfile_write (main->pfh);
+
+ return 0;
+}
+
+void
+init_signals (struct sigaction *signals, sig_t sig_handler)
+{
+ /* Setting up signal handlers */
+ /* SIGUSR1 - reopen config file */
+ /* SIGUSR2 - worker is ready for accept */
+ sigemptyset(&signals->sa_mask);
+ sigaddset(&signals->sa_mask, SIGTERM);
+ sigaddset(&signals->sa_mask, SIGINT);
+ sigaddset(&signals->sa_mask, SIGHUP);
+ sigaddset(&signals->sa_mask, SIGCHLD);
+ sigaddset(&signals->sa_mask, SIGUSR1);
+ sigaddset(&signals->sa_mask, SIGUSR2);
+
+
+ signals->sa_handler = sig_handler;
+ sigaction (SIGTERM, signals, NULL);
+ sigaction (SIGINT, signals, NULL);
+ sigaction (SIGHUP, signals, NULL);
+ sigaction (SIGCHLD, signals, NULL);
+ sigaction (SIGUSR1, signals, NULL);
+ sigaction (SIGUSR2, signals, NULL);
+}
+
+void
+pass_signal_worker (struct workq *workers, int signo)
+{
+ struct rspamd_worker *cur;
+ TAILQ_FOREACH (cur, workers, next) {
+ kill (cur->pid, signo);
+ }
+}
+
+#ifndef HAVE_SETPROCTITLE
+
+static char *title_buffer = 0;
+static size_t title_buffer_size = 0;
+static char *title_progname, *title_progname_full;
+
+int
+setproctitle(const char *fmt, ...)
+{
+ if (!title_buffer || !title_buffer_size) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ memset (title_buffer, '\0', title_buffer_size);
+
+ ssize_t written;
+
+ if (fmt) {
+ ssize_t written2;
+ va_list ap;
+
+ written = snprintf (title_buffer, title_buffer_size, "%s: ", title_progname);
+ if (written < 0 || (size_t) written >= title_buffer_size)
+ return -1;
+
+ va_start (ap, fmt);
+ written2 =
+ vsnprintf (title_buffer + written,
+ title_buffer_size - written, fmt, ap);
+ va_end (ap);
+ if (written2 < 0
+ || (size_t) written2 >= title_buffer_size - written)
+ return -1;
+ } else {
+ written =
+ snprintf (title_buffer, title_buffer_size, "%s",
+ title_progname);
+ if (written < 0 || (size_t) written >= title_buffer_size)
+ return -1;
+ }
+
+ written = strlen (title_buffer);
+ memset (title_buffer + written, '\0', title_buffer_size - written);
+
+ return 0;
+}
+
+/*
+ It has to be _init function, because __attribute__((constructor))
+ functions gets called without arguments.
+*/
+
+int
+init_title(int argc, char *argv[], char *envp[])
+{
+ char *begin_of_buffer = 0, *end_of_buffer = 0;
+ int i;
+
+ for (i = 0; i < argc; ++i) {
+ if (!begin_of_buffer)
+ begin_of_buffer = argv[i];
+ if (!end_of_buffer || end_of_buffer + 1 == argv[i])
+ end_of_buffer = argv[i] + strlen (argv[i]);
+ }
+
+ for (i = 0; envp[i]; ++i) {
+ if (!begin_of_buffer)
+ begin_of_buffer = envp[i];
+ if (!end_of_buffer || end_of_buffer + 1 == envp[i])
+ end_of_buffer = envp[i] + strlen(envp[i]);
+ }
+
+ if (!end_of_buffer)
+ return 0;
+
+ char **new_environ = malloc ((i + 1) * sizeof (envp[0]));
+
+ if (!new_environ)
+ return 0;
+
+ for (i = 0; envp[i]; ++i) {
+ if (!(new_environ[i] = strdup (envp[i])))
+ goto cleanup_enomem;
+ }
+ new_environ[i] = 0;
+
+ if (program_invocation_name) {
+ title_progname_full = strdup (program_invocation_name);
+
+ if (!title_progname_full)
+ goto cleanup_enomem;
+
+ char *p = strrchr (title_progname_full, '/');
+
+ if (p)
+ title_progname = p + 1;
+ else
+ title_progname = title_progname_full;
+
+ program_invocation_name = title_progname_full;
+ program_invocation_short_name = title_progname;
+ }
+
+ environ = new_environ;
+ title_buffer = begin_of_buffer;
+ title_buffer_size = end_of_buffer - begin_of_buffer;
+
+ return 0;
+
+ cleanup_enomem:
+ for (--i; i >= 0; --i) {
+ free(new_environ[i]);
+ }
+ free(new_environ);
+ return 0;
+}
+#endif
+
+#ifndef HAVE_PIDFILE
+extern char * __progname;
+static int _pidfile_remove(struct pidfh *pfh, int freeit);
+
+static int
+pidfile_verify(struct pidfh *pfh)
+{
+ struct stat sb;
+
+ if (pfh == NULL || pfh->pf_fd == -1)
+ return (-1);
+ /*
+ * Check remembered descriptor.
+ */
+ if (fstat(pfh->pf_fd, &sb) == -1)
+ return (errno);
+ if (sb.st_dev != pfh->pf_dev || sb.st_ino != pfh->pf_ino)
+ return (-1);
+ return (0);
+}
+
+static int
+pidfile_read(const char *path, pid_t *pidptr)
+{
+ char buf[16], *endptr;
+ int error, fd, i;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return (errno);
+
+ i = read(fd, buf, sizeof(buf) - 1);
+ error = errno; /* Remember errno in case close() wants to change it. */
+ close(fd);
+ if (i == -1)
+ return (error);
+ else if (i == 0)
+ return (EAGAIN);
+ buf[i] = '\0';
+
+ *pidptr = strtol(buf, &endptr, 10);
+ if (endptr != &buf[i])
+ return (EINVAL);
+
+ return (0);
+}
+
+struct pidfh *
+pidfile_open(const char *path, mode_t mode, pid_t *pidptr)
+{
+ struct pidfh *pfh;
+ struct stat sb;
+ int error, fd, len, count;
+ struct timespec rqtp;
+
+ pfh = malloc(sizeof(*pfh));
+ if (pfh == NULL)
+ return (NULL);
+
+ if (path == NULL)
+ len = snprintf(pfh->pf_path, sizeof(pfh->pf_path),
+ "/var/run/%s.pid", __progname);
+ else
+ len = snprintf(pfh->pf_path, sizeof(pfh->pf_path),
+ "%s", path);
+ if (len >= (int)sizeof(pfh->pf_path)) {
+ free(pfh);
+ errno = ENAMETOOLONG;
+ return (NULL);
+ }
+
+ /*
+ * Open the PID file and obtain exclusive lock.
+ * We truncate PID file here only to remove old PID immediatelly,
+ * PID file will be truncated again in pidfile_write(), so
+ * pidfile_write() can be called multiple times.
+ */
+ fd = open(pfh->pf_path,
+ O_WRONLY | O_CREAT | O_TRUNC | O_NONBLOCK, mode);
+ flock (fd, LOCK_EX | LOCK_NB);
+ if (fd == -1) {
+ count = 0;
+ rqtp.tv_sec = 0;
+ rqtp.tv_nsec = 5000000;
+ if (errno == EWOULDBLOCK && pidptr != NULL) {
+ again:
+ errno = pidfile_read(pfh->pf_path, pidptr);
+ if (errno == 0)
+ errno = EEXIST;
+ else if (errno == EAGAIN) {
+ if (++count <= 3) {
+ nanosleep(&rqtp, 0);
+ goto again;
+ }
+ }
+ }
+ free(pfh);
+ return (NULL);
+ }
+ /*
+ * Remember file information, so in pidfile_write() we are sure we write
+ * to the proper descriptor.
+ */
+ if (fstat(fd, &sb) == -1) {
+ error = errno;
+ unlink(pfh->pf_path);
+ close(fd);
+ free(pfh);
+ errno = error;
+ return (NULL);
+ }
+
+ pfh->pf_fd = fd;
+ pfh->pf_dev = sb.st_dev;
+ pfh->pf_ino = sb.st_ino;
+
+ return (pfh);
+}
+
+int
+pidfile_write(struct pidfh *pfh)
+{
+ char pidstr[16];
+ int error, fd;
+
+ /*
+ * Check remembered descriptor, so we don't overwrite some other
+ * file if pidfile was closed and descriptor reused.
+ */
+ errno = pidfile_verify(pfh);
+ if (errno != 0) {
+ /*
+ * Don't close descriptor, because we are not sure if it's ours.
+ */
+ return (-1);
+ }
+ fd = pfh->pf_fd;
+
+ /*
+ * Truncate PID file, so multiple calls of pidfile_write() are allowed.
+ */
+ if (ftruncate(fd, 0) == -1) {
+ error = errno;
+ _pidfile_remove(pfh, 0);
+ errno = error;
+ return (-1);
+ }
+
+ snprintf(pidstr, sizeof(pidstr), "%u", getpid());
+ if (pwrite(fd, pidstr, strlen(pidstr), 0) != (ssize_t)strlen(pidstr)) {
+ error = errno;
+ _pidfile_remove(pfh, 0);
+ errno = error;
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+pidfile_close(struct pidfh *pfh)
+{
+ int error;
+
+ error = pidfile_verify(pfh);
+ if (error != 0) {
+ errno = error;
+ return (-1);
+ }
+
+ if (close(pfh->pf_fd) == -1)
+ error = errno;
+ free(pfh);
+ if (error != 0) {
+ errno = error;
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+_pidfile_remove(struct pidfh *pfh, int freeit)
+{
+ int error;
+
+ error = pidfile_verify(pfh);
+ if (error != 0) {
+ errno = error;
+ return (-1);
+ }
+
+ if (unlink(pfh->pf_path) == -1)
+ error = errno;
+ if (flock(pfh->pf_fd, LOCK_UN) == -1) {
+ if (error == 0)
+ error = errno;
+ }
+ if (close(pfh->pf_fd) == -1) {
+ if (error == 0)
+ error = errno;
+ }
+ if (freeit)
+ free(pfh);
+ else
+ pfh->pf_fd = -1;
+ if (error != 0) {
+ errno = error;
+ return (-1);
+ }
+ return (0);
+}
+
+int
+pidfile_remove(struct pidfh *pfh)
+{
+
+ return (_pidfile_remove(pfh, 1));
+}
+#endif
diff --git a/util.h b/util.h
new file mode 100644
index 000000000..6340b2194
--- /dev/null
+++ b/util.h
@@ -0,0 +1,53 @@
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <sys/types.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <signal.h>
+
+#include "main.h"
+
+struct config_file;
+
+/* Create socket and bind it to specified address and port */
+int make_socket(const char *, u_short );
+/* Create and bind unix socket */
+int make_unix_socket (const char *, struct sockaddr_un *);
+/* Parse command line arguments using getopt (3) */
+void read_cmd_line (int , char **, struct config_file *);
+/* Write pid to file */
+int write_pid (struct rspamd_main *);
+/* Make specified socket non-blocking */
+int event_make_socket_nonblocking(int);
+/* Init signals */
+void init_signals (struct sigaction *, sig_t);
+/* Send specified signal to each worker */
+void pass_signal_worker (struct workq *, int );
+
+#ifndef HAVE_SETPROCTITLE
+int init_title(int argc, char *argv[], char *envp[]);
+int setproctitle(const char *fmt, ...);
+#endif
+
+#ifndef HAVE_PIDFILE
+struct pidfh {
+ int pf_fd;
+ char pf_path[MAXPATHLEN + 1];
+ __dev_t pf_dev;
+ ino_t pf_ino;
+};
+struct pidfh *pidfile_open(const char *path, mode_t mode, pid_t *pidptr);
+int pidfile_write(struct pidfh *pfh);
+int pidfile_close(struct pidfh *pfh);
+int pidfile_remove(struct pidfh *pfh);
+#endif
+
+#endif
diff --git a/worker.c b/worker.c
new file mode 100644
index 000000000..e5e7c341d
--- /dev/null
+++ b/worker.c
@@ -0,0 +1,52 @@
+
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <netinet/in.h>
+#include <syslog.h>
+#include <fcntl.h>
+#include <netdb.h>
+
+#include "util.h"
+#include "main.h"
+#include "upstream.h"
+#include "cfg_file.h"
+
+static
+void sig_handler (int signo)
+{
+ switch (signo) {
+ case SIGINT:
+ case SIGTERM:
+ _exit (1);
+ break;
+ }
+}
+
+void
+start_worker (struct rspamd_worker *worker, int listen_sock)
+{
+ struct sigaction signals;
+ struct config_file *cfg = worker->srv->cfg;
+ worker->srv->pid = getpid ();
+ worker->srv->type = TYPE_WORKER;
+
+ init_signals (&signals, sig_handler);
+ sigprocmask (SIG_UNBLOCK, &signals.sa_mask, NULL);
+
+ /* Send SIGUSR2 to parent */
+ kill (getppid (), SIGUSR2);
+
+}
+
+/*
+ * vi:ts=4
+ */