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