]> source.dussan.org Git - rspamd.git/commitdiff
* Add skeleton
authorVsevolod Stakhov <vsevolod@rambler-co.ru>
Thu, 8 May 2008 15:05:30 +0000 (19:05 +0400)
committerVsevolod Stakhov <vsevolod@rambler-co.ru>
Thu, 8 May 2008 15:05:30 +0000 (19:05 +0400)
20 files changed:
Makefile.in [new file with mode: 0644]
cfg_file.h [new file with mode: 0644]
cfg_file.l [new file with mode: 0644]
cfg_file.y [new file with mode: 0644]
cfg_utils.c [new file with mode: 0644]
compat/md5.c [new file with mode: 0644]
compat/md5.h [new file with mode: 0644]
compat/queue.h [new file with mode: 0644]
compat/strlcpy.c [new file with mode: 0644]
compat/strlcpy.h [new file with mode: 0644]
configure [new file with mode: 0755]
main.c [new file with mode: 0644]
main.h [new file with mode: 0644]
memcached.c [new file with mode: 0644]
memcached.h [new file with mode: 0644]
upstream.c [new file with mode: 0644]
upstream.h [new file with mode: 0644]
util.c [new file with mode: 0644]
util.h [new file with mode: 0644]
worker.c [new file with mode: 0644]

diff --git a/Makefile.in b/Makefile.in
new file mode 100644 (file)
index 0000000..5685d6c
--- /dev/null
@@ -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 (file)
index 0000000..53e0a76
--- /dev/null
@@ -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 (file)
index 0000000..453cb69
--- /dev/null
@@ -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 (file)
index 0000000..b4675f0
--- /dev/null
@@ -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 (file)
index 0000000..d682310
--- /dev/null
@@ -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 (file)
index 0000000..76c1f37
--- /dev/null
@@ -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 (file)
index 0000000..b07c6f0
--- /dev/null
@@ -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 (file)
index 0000000..d62afcc
--- /dev/null
@@ -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 (file)
index 0000000..1c08055
--- /dev/null
@@ -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 (file)
index 0000000..74d772d
--- /dev/null
@@ -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 (executable)
index 0000000..e967bb0
--- /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 (file)
index 0000000..1b7a00f
--- /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 (file)
index 0000000..fa56855
--- /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 (file)
index 0000000..91aa21f
--- /dev/null
@@ -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 (file)
index 0000000..c0ae896
--- /dev/null
@@ -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 (file)
index 0000000..87aab85
--- /dev/null
@@ -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 (file)
index 0000000..ad2ac4f
--- /dev/null
@@ -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 (file)
index 0000000..65cafb9
--- /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 (file)
index 0000000..6340b21
--- /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 (file)
index 0000000..e5e7c34
--- /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 
+ */