]> source.dussan.org Git - rspamd.git/commitdiff
* Add ability to fork multiply lmtp workers
authorVsevolod Stakhov <vsevolod@rambler-co.ru>
Tue, 3 Mar 2009 15:56:10 +0000 (18:56 +0300)
committerVsevolod Stakhov <vsevolod@rambler-co.ru>
Tue, 3 Mar 2009 15:56:10 +0000 (18:56 +0300)
* Add ability to drop privilleges of rspamd processes
* Add ability to install redirector with rspamd
* Add FreeBSD start scripts for rspamd and redirector
* Remove command line parsing from util.c as it has specific usage only in main process

CMakeLists.txt
config.h.in
freebsd/redirector.sh.in [new file with mode: 0755]
freebsd/rspamd.sh.in [new file with mode: 0755]
rspamd.conf.sample
src/cfg_file.h
src/cfg_file.y
src/main.c
src/util.c
src/util.h
utils/redirector.pl.in [new file with mode: 0755]

index 06054151332766fc564c3bcbbdea4400cfa3743c..19172f15dece7a97d34f9ab28eddb42d57b42613 100644 (file)
@@ -11,12 +11,15 @@ SET(RSPAMD_VERSION_PATCH 0)
 
 SET(RSPAMD_VERSION         "${RSPAMD_VERSION_MAJOR}.${RSPAMD_VERSION_MINOR}.${RSPAMD_VERSION_PATCH}")
 SET(RSPAMD_MASTER_SITE_URL "http://cebka.pp.ru/hg/rspamd")
+SET(RSPAMD_USER "nobody")
+SET(RSPAMD_GROUP "nobody")
 
 CMAKE_MINIMUM_REQUIRED(VERSION 2.6.0 FATAL_ERROR)
 
 OPTION(DEBUG_MODE          "Enable debug output [default: ON]"                  ON)
 OPTION(ENABLE_OPTIMIZATION "Enable optimization [default: OFF]"                 OFF)
 OPTION(SKIP_RELINK_RPATH   "Skip relinking and full RPATH for the install tree" OFF)
+OPTION(ENABLE_REDIRECTOR   "Enable redirector install [default: OFF]"           OFF)
 
 # Build optimized code for following CPU (default i386)
 #SET(CPU_TUNE               "i686")
@@ -152,6 +155,8 @@ CHECK_INCLUDE_FILES(arpa/inet.h  HAVE_ARPA_INET_H)
 CHECK_INCLUDE_FILES(netdb.h  HAVE_NETDB_H)
 CHECK_INCLUDE_FILES(syslog.h HAVE_SYSLOG_H)
 CHECK_INCLUDE_FILES(libgen.h HAVE_LIBGEN_H)
+CHECK_INCLUDE_FILES(pwd.h HAVE_PWD_H)
+CHECK_INCLUDE_FILES(grp.h HAVE_GRP_H)
 
 IF(HAVE_SYS_WAIT_H)
        LIST(APPEND CMAKE_REQUIRED_INCLUDES sys/wait.h)
@@ -202,6 +207,20 @@ ENDIF (MD5_INCLUDE)
 
 SET(CMAKE_C_WARN_FLAGS " -Wall -W -Wpointer-arith -Wno-unused-parameter -Wno-unused-function -Wunused-variable -Wno-sign-compare -Wunused-value")
 
+# Platform specific routines
+IF(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
+       CONFIGURE_FILE(freebsd/rspamd.sh.in freebsd/rspamd.sh @ONLY)
+
+       IF(ENABLE_REDIRECTOR MATCHES "ON")
+               CONFIGURE_FILE(freebsd/redirector.sh.in freebsd/redirector.sh @ONLY)
+       ENDIF(ENABLE_REDIRECTOR MATCHES "ON")
+
+ENDIF(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
+
+IF(ENABLE_REDIRECTOR MATCHES "ON")
+       CONFIGURE_FILE(utils/redirector.pl.in utils/redirector.pl @ONLY)
+ENDIF(ENABLE_REDIRECTOR MATCHES "ON")
+
 IF(DEBUG_MODE MATCHES "ON")
        SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ggdb ")
 ENDIF(DEBUG_MODE MATCHES "ON")
@@ -299,7 +318,7 @@ IF(PERL_DYNALOADER)
        TARGET_LINK_LIBRARIES(rspamd dynaloader)
 ENDIF(PERL_DYNALOADER)
 TARGET_LINK_LIBRARIES(rspamd ${GMIME2_LIBRARIES})
-ADD_DEPENDENCIES(rspamd perlxs)
+ADD_DEPENDENCIES(rspamd perlxs ${LEX_OUTPUT} ${YACC_OUTPUT})
 
 ADD_EXECUTABLE(test/rspamd-test ${TESTDEPENDS} ${CONTRIBSRC} ${TESTSRC})
 SET_TARGET_PROPERTIES(test/rspamd-test PROPERTIES LINKER_LANGUAGE C)
@@ -319,3 +338,16 @@ TARGET_LINK_LIBRARIES(utils/url-extracter ${GMIME2_LIBRARIES})
 INSTALL(PROGRAMS rspamd-${RSPAMD_VERSION} DESTINATION bin RENAME rspamd)
 INSTALL(PROGRAMS rspamc.pl DESTINATION bin RENAME rspamc)
 INSTALL(CODE "EXECUTE_PROCESS(COMMAND make install WORKING_DIRECTORY perl)")
+
+IF(ENABLE_REDIRECTOR MATCHES "ON")
+       INSTALL(PROGRAMS utils/redirector.pl DESTINATION bin RENAME rspamd-redirector)
+ENDIF(ENABLE_REDIRECTOR MATCHES "ON")
+
+IF(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
+       INSTALL(PROGRAMS freebsd/rspamd.sh DESTINATION etc/rc.d)
+       IF(ENABLE_REDIRECTOR MATCHES "ON")
+               INSTALL(PROGRAMS freebsd/redirector.sh DESTINATION etc/rc.d)
+       ENDIF(ENABLE_REDIRECTOR MATCHES "ON")
+       INSTALL(CODE "EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E make_directory /var/run/rspamd/)")
+       INSTALL(CODE "EXECUTE_PROCESS(COMMAND chown ${RSPAMD_USER}:${RSPAMD_GROUP} /var/run/rspamd/)")
+ENDIF(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
index a1b3d932a8ae11d1911adaa2217820692d2117bf..97da9c381cf87b0b9d12f91dbf3084bc9ee13564 100644 (file)
@@ -39,6 +39,9 @@
 
 #cmakedefine HAVE_LIBGEN_H       1
 
+#cmakedefine HAVE_GRP_H          1
+#cmakedefine HAVE_PWD_H          1
+
 #cmakedefine HAVE_ENDIAN_H       1
 #cmakedefine HAVE_SYS_ENDIAN_H   1
 #cmakedefine HAVE_MACHINE_ENDIAN_H  1
 #include <libutil.h>
 #endif
 
+/* pwd and grp */
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+
 /* syslog */
 #ifdef HAVE_SYSLOG_H
 #include <syslog.h>
diff --git a/freebsd/redirector.sh.in b/freebsd/redirector.sh.in
new file mode 100755 (executable)
index 0000000..71f6df5
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# $Id$
+#
+# PROVIDE: rspamd_redirector
+# REQUIRE: LOGIN
+# KEYWORD: shutdown
+
+#
+# Add the following line to /etc/rc.conf to enable countd:
+# rspamd-redirector (bool):          Set to "NO" by default.
+#                          Set it to "YES" to enable redirector.
+
+. /etc/rc.subr
+
+name="rspamd_redirector"
+rcvar=`set_rcvar`
+procname="@CMAKE_INSTALL_PREFIX@/bin/rspamd-redirector"
+
+load_rc_config $name
+
+: ${rspamd_redirector_enable="NO"}
+: ${rspamd_redirector_pidfile="/var/run/rspamd/redirector.pid"}
+
+stop_postcmd="rm -f $rspamd_redirector_pidfile"
+
+extra_commands="reload"
+sig_reload="USR1"
+
+command="$procname"
+
+run_rc_command "$1"
diff --git a/freebsd/rspamd.sh.in b/freebsd/rspamd.sh.in
new file mode 100755 (executable)
index 0000000..c19fd3e
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/sh
+#
+# $Id$
+#
+# PROVIDE: rspamd
+# REQUIRE: LOGIN
+# KEYWORD: shutdown
+
+#
+# Add the following line to /etc/rc.conf to enable countd:
+# rspamd (bool):          Set to "NO" by default.
+#                          Set it to "YES" to enable rspamd.
+
+. /etc/rc.subr
+
+name="rspamd"
+rcvar=`set_rcvar`
+procname="@CMAKE_INSTALL_PREFIX@/bin/rspamd"
+
+load_rc_config $name
+
+: ${rspamd_enable="NO"}
+: ${rspamd_pidfile="/var/run/rspamd/rspamd.pid"}
+: ${rspamd_user="@RSPAMD_USER@"}
+: ${rspamd_group="@RSPAMD_GROUP@"}
+
+stop_postcmd="rm -f $rspamd_pidfile"
+
+extra_commands="reload"
+sig_reload="USR1"
+
+command="$procname"
+command_args="-u ${rspamd_user} -g ${rspamd_group} -c @CMAKE_INSTALL_PREFIX@/etc/rspamd.conf"
+
+run_rc_command "$1"
index bac7dc9228e77e657776813a440904cbb578170c..14744c77173f4813f609afa7a00f0345580bf066 100644 (file)
@@ -84,6 +84,8 @@ lmtp {
        # Metric that is considered as main. If we have spam result on
        # this metric, lmtp delivery would be failed
        metric = "default";
+       # Number of lmtp workers
+       workers = 1;
 };
 
 delivery {
index 78ad6e93aff080d727b2e221184b5d67f159e3f5..75c3a8c2f15b9b6fd4ad2d3baf24945679418174 100644 (file)
@@ -136,6 +136,8 @@ struct config_scalar {
  * Structure that stores all config data
  */
 struct config_file {
+       char *rspamd_user;                                                              /**< user to run as                                                                             */
+       char *rspamd_group;                                                             /**< group to run as                                                                    */
        memory_pool_t *cfg_pool;                                                /**< memory pool for config                                                             */
        char *cfg_name;                                                                 /**< name of config file                                                                */
        char *pid_file;                                                                 /**< name of pid file                                                                   */
@@ -156,6 +158,7 @@ struct config_file {
        gboolean no_fork;                                                               /**< if 1 do not call daemon()                                                  */
        gboolean config_test;                                                   /**< if TRUE do only config file test                                   */
        unsigned int workers_number;                                    /**< number of workers                                                                  */
+       unsigned int lmtp_workers_number;                               /**< number of lmtp workers                                                             */
 
        enum rspamd_log_type log_type;                                  /**< log type                                                                                   */
        int log_facility;                                                               /**< log facility in case of syslog                                             */
index 7eee8516c68b212338774245db87418e272c0a88..dd48520f2c7671dbdb7ef9688913f6ec3fdd1c56 100644 (file)
@@ -676,6 +676,7 @@ lmtpcmd:
        lmtpenabled
        | lmtpsock
        | lmtpmetric
+       | lmtpworkers
        ;
 
 lmtpenabled:
@@ -698,6 +699,11 @@ lmtpmetric:
                cfg->lmtp_metric = memory_pool_strdup (cfg->cfg_pool, $3);
        }
        ;
+lmtpworkers:
+       WORKERS EQSIGN NUMBER {
+               cfg->lmtp_workers_number = $3;
+       }
+       ;
 
 delivery:
        DELIVERY OBRACE deliverybody EBRACE
index 1e9ba67889318acfb9bf7a820fa87e319c862155..f0eca39d32531a42d1fda0476436ef3ed3488b36 100644 (file)
@@ -77,6 +77,84 @@ void sig_handler (int signo)
        }
 }
 
+static void 
+read_cmd_line (int argc, char **argv, struct config_file *cfg)
+{
+       int ch;
+       while ((ch = getopt(argc, argv, "thfc:u:g:")) != -1) {
+        switch (ch) {
+            case 'f':
+                cfg->no_fork = 1;
+                break;
+            case 'c':
+                if (optarg && cfg->cfg_name) {
+                    cfg->cfg_name = memory_pool_strdup (cfg->cfg_pool, optarg);
+                }
+                break;
+                       case 't':
+                               cfg->config_test = 1;
+                               break;
+                       case 'u':
+                               if (optarg) {
+                                       cfg->rspamd_user = memory_pool_strdup (cfg->cfg_pool, optarg);
+                               }
+                               break;
+                       case 'g':
+                               if (optarg) {
+                                       cfg->rspamd_group = memory_pool_strdup (cfg->cfg_pool, optarg);
+                               }
+                               break;
+            case 'h':
+            case '?':
+            default:
+                /* Show help message and exit */
+                printf ("Rspamd version " RVERSION "\n"
+                        "Usage: rspamd [-t] [-h] [-n] [-f] [-c config_file]\n"
+                        "-h:        This help message\n"
+                                               "-t:        Do config test and exit\n"
+                        "-f:        Do not daemonize main process\n"
+                        "-c:        Specify config file (./rspamd.conf is used by default)\n"
+                                               "-u:        User to run rspamd as\n"
+                                               "-g:        Group to run rspamd as\n");
+                exit (0);
+                break;
+        }
+    }
+}
+
+static void
+drop_priv (struct config_file *cfg) 
+{
+       struct passwd *pwd;
+       struct group *grp;
+
+       if (geteuid () == 0 && cfg->rspamd_user) {
+               pwd = getpwnam (cfg->rspamd_user);
+               if (pwd == NULL) {
+                       msg_err ("drop_priv: user specified does not exists (%s), aborting", strerror (errno));
+                       exit (-errno);
+               }
+               if (cfg->rspamd_group) {
+                       grp = getgrnam (cfg->rspamd_group);
+                       if (grp == NULL) {
+                               msg_err ("drop_priv: group specified does not exists (%s), aborting", strerror (errno));
+                               exit (-errno);
+                       }
+                       if (setgid (grp->gr_gid) == -1) {
+                               msg_err ("drop_priv: cannot setgid to %d (%s), aborting", (int)grp->gr_gid, strerror (errno));
+                               exit (-errno);
+                       }
+                       if (initgroups(cfg->rspamd_user, grp->gr_gid) == -1) {
+                               msg_err ("drop_priv: initgroups failed (%s), aborting", strerror (errno));
+                               exit (-errno);
+                       }
+               }
+               if (setuid (pwd->pw_uid) == -1) {
+                       msg_err ("drop_priv: cannot setuid to %d (%s), aborting", (int)pwd->pw_uid, strerror (errno));
+                       exit (-errno);
+               }
+       }
+}
 
 static void
 config_logger (struct rspamd_main *rspamd, gboolean is_fatal)
@@ -321,11 +399,6 @@ main (int argc, char **argv, char **env)
                exit (-errno);
        }
 
-       if (write_pid (rspamd) == -1) {
-               msg_err ("main: cannot write pid file %s", rspamd->cfg->pid_file);
-               exit (-errno);
-       }
-
        /* Init C modules */
        for (i = 0; i < MODULES_NUM; i ++) {
                cur_module = memory_pool_alloc (rspamd->cfg->cfg_pool, sizeof (struct module_ctx));
@@ -338,21 +411,8 @@ main (int argc, char **argv, char **env)
        rspamd->type = TYPE_MAIN;
        
        init_signals (&signals, sig_handler);
-       /* Init perl interpreter */
-       dTHXa (perl_interpreter);
-       PERL_SYS_INIT3 (&argc, &argv, &env);
-       perl_interpreter = perl_alloc ();
-       if (perl_interpreter == NULL) {
-               msg_err ("main: cannot allocate perl interpreter, %s", strerror (errno));
-               exit (-errno);
-       }
-
-       PERL_SET_CONTEXT (perl_interpreter);
-       perl_construct (perl_interpreter);
-       perl_parse (perl_interpreter, xs_init, 3, args, NULL);
-       /* Block signals to use sigsuspend in future */
-       sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL);
 
+       /* Create listen socket */
        if (rspamd->cfg->bind_family == AF_INET) {
                if ((listen_sock = make_tcp_socket (&rspamd->cfg->bind_addr, rspamd->cfg->bind_port, TRUE)) == -1) {
                        msg_err ("main: cannot create tcp listen socket. %s", strerror (errno));
@@ -371,6 +431,30 @@ main (int argc, char **argv, char **env)
                msg_err ("main: cannot listen on socket. %s", strerror (errno));
                exit(-errno);
        }
+
+       /* Drop privilleges */
+       drop_priv (cfg);
+
+       if (write_pid (rspamd) == -1) {
+               msg_err ("main: cannot write pid file %s", rspamd->cfg->pid_file);
+               exit (-errno);
+       }
+
+       /* Init perl interpreter */
+       dTHXa (perl_interpreter);
+       PERL_SYS_INIT3 (&argc, &argv, &env);
+       perl_interpreter = perl_alloc ();
+       if (perl_interpreter == NULL) {
+               msg_err ("main: cannot allocate perl interpreter, %s", strerror (errno));
+               exit (-errno);
+       }
+
+       PERL_SET_CONTEXT (perl_interpreter);
+       perl_construct (perl_interpreter);
+       perl_parse (perl_interpreter, xs_init, 3, args, NULL);
+       /* Block signals to use sigsuspend in future */
+       sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL);
+
        
        TAILQ_INIT (&rspamd->workers);
 
@@ -389,7 +473,9 @@ main (int argc, char **argv, char **env)
        
        /* Start lmtp if enabled */
        if (cfg->lmtp_enable) {
-               fork_worker (rspamd, listen_sock, 0, TYPE_LMTP);
+               for (i = 0; i < cfg->lmtp_workers_number; i++) {
+                       fork_worker (rspamd, listen_sock, 0, TYPE_LMTP);
+               }
        }
 
        /* Signal processing cycle */
@@ -443,7 +529,7 @@ main (int argc, char **argv, char **env)
                                /* 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 */
+                       /* Do not start new workers until active worker is not ready for accept */
                }
                if (child_ready) {
                        child_ready = 0;
index 6a107436ad509e093957adde26fa2c627f799884..589d5b5d95b12a97f39b5d407d1a225709d23911 100644 (file)
@@ -203,39 +203,6 @@ make_unix_socket (const char *path, struct sockaddr_un *addr, gboolean is_server
        return (-1);
 }
 
-void 
-read_cmd_line (int argc, char **argv, struct config_file *cfg)
-{
-       int ch;
-       while ((ch = getopt(argc, argv, "thfc:")) != -1) {
-        switch (ch) {
-            case 'f':
-                cfg->no_fork = 1;
-                break;
-            case 'c':
-                if (optarg && cfg->cfg_name) {
-                    cfg->cfg_name = memory_pool_strdup (cfg->cfg_pool, optarg);
-                }
-                break;
-                       case 't':
-                               cfg->config_test = 1;
-                               break;
-            case 'h':
-            case '?':
-            default:
-                /* Show help message and exit */
-                printf ("Rspamd version " RVERSION "\n"
-                        "Usage: rspamd [-t] [-h] [-n] [-f] [-c config_file]\n"
-                        "-h:        This help message\n"
-                                               "-t:        Do config test and exit\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)
 {
index f0764b960ff7f5f62997fc4b84a37ab9b3db7c5e..7267a719b527c4c5bed653f4cbddb2cae8695192 100644 (file)
@@ -12,8 +12,6 @@ int make_tcp_socket (struct in_addr *, u_short, gboolean is_server);
 int accept_from_socket (int listen_sock, struct sockaddr *addr, socklen_t *len);
 /* Create and bind or connect unix socket */
 int make_unix_socket (const char *, struct sockaddr_un *, gboolean is_server);
-/* 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 */
diff --git a/utils/redirector.pl.in b/utils/redirector.pl.in
new file mode 100755 (executable)
index 0000000..cb74b02
--- /dev/null
@@ -0,0 +1,447 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+# Required ports:
+# www/p5-POE-Component-Client-HTTP
+# www/p5-POE-Component-Server-HTTP
+# dns/p5-POE-Component-Client-DNS
+# databases/p5-Cache-Memcached-Fast
+# devel/p5-Proc-Daemon
+# sysutils/p5-Proc-PidUtil
+# security/p5-Digest-SHA256
+
+# POE::Component::Client::HTTP uses HTTP::Request and response
+# objects.
+
+use POSIX qw(strftime);
+use HTTP::Request::Common qw(GET POST);
+
+use POE qw(Component::Server::TCP Filter::HTTPD Component::Client::HTTP); # p5-POE-Component-Client-HTTP
+use HTTP::Response;
+use HTML::HeadParser; 
+use SWF::Element; # p5-SWF-File
+use Cache::Memcached::Fast;
+use Digest::SHA256;
+
+use HTML::HeadParser;
+use Proc::Daemon;
+use Proc::PidUtil;
+use URI::Escape qw(uri_unescape);
+
+my $swf_parser;
+my $saved_swf_url = "";
+
+my %cfg = (
+    port    =>  8080,
+    max_size    =>  102400,
+    http_timeout    =>  5,
+    max_rec     => 5,
+    pidfile =>  '/var/run/rspamd/redirector.pid',
+    logfile =>  '/var/log/rspamd-redirector.log',
+    do_log  =>  1,
+    debug   =>  0,
+    digest_bits => 256,
+    cache_expire => 3600,
+    user    => '@RSPAMD_USER@',
+    group   => '@RSPAMD_GROUP@',
+);
+
+our $do_reopen_log = 0;
+
+die "Process is already started, check $cfg{pidfile}" if Proc::PidUtil::is_running($cfg{pidfile});
+
+die "Cannot write to pidfile $cfg{pidfile}" if ! open(PID, "> $cfg{pidfile}");
+close(PID);
+
+$cfg{do_log} = 0 if ! open(LOG, ">> $cfg{logfile}");
+
+# Do daemonization
+Proc::Daemon::Init if !$cfg{debug};
+
+my $uid = getpwnam($cfg{user}) or die "user $cfg{user} unknown";
+my $gid = getgrnam($cfg{group}) or die "group $cfg{group} unknown";
+setpgrp ($uid, $gid) or die "cannot drop privilleges";
+
+Proc::PidUtil::make_pidfile($cfg{pidfile}, $$) or die "Cannot write pidfile $cfg{pidfile}";
+
+# Init memcached connection
+my $memd = new Cache::Memcached::Fast({
+        servers => [ { address => 'localhost:11211', weight => 2.5 },
+                    ],
+        connect_timeout => 0.2,
+        io_timeout => 0.5,
+        max_failures => 3,
+        failure_timeout => 2,
+        ketama_points => 150,
+        hash_namespace => 1,
+        serialize_methods => [ \&Storable::freeze, \&Storable::thaw ],
+        utf8 => ($^V ge v5.8.1 ? 1 : 0),
+});
+
+# Reopen log on SIGUSR1
+$SIG{USR1} = sub { $do_reopen_log = 1; };
+$SIG{INT} = sub { $poe_kernel->stop(); };
+$SIG{QUIT} = sub { $poe_kernel->stop(); };
+
+write_log ("", "Starting URL resolver");
+
+# POE part
+POE::Component::Client::HTTP->spawn( 
+    Alias => 'cl',
+    MaxSize => $cfg{max_size},    # Remove for unlimited page sizes
+    Timeout => $cfg{http_timeout},
+    ConnectionManager   => POE::Component::Client::Keepalive->new(
+        max_per_host    => 256,
+        max_open        => 1024,
+        keep_alive      => 1,
+        timeout         => $cfg{http_timeout},
+    ), 
+);
+
+sub reopen_log {
+    if ($cfg{do_log}) {
+        close (LOG);
+        $cfg{do_log} = 0 if ! open (LOG, ">> $cfg{logfile}");
+        write_log ("", "Log reopened");
+    }
+}
+
+# Write log line:
+# $remote_ip - remote ip string
+# $str - string to write
+sub write_log {
+    my ( $remote_ip, $str ) = @_;
+    
+    if ($cfg{do_log}) {
+        my $now_string = strftime "%F %T", localtime;
+        LOG->autoflush(1);
+        print LOG "[$now_string]: $remote_ip: $str\n";
+    }
+}
+
+sub swf_init_parser {
+    $swf_parser = SWF::Parser->new('tag-callback' => \&swf_tag_callback);
+}
+
+# Checking for SWF url
+sub swf_search_get_url {
+    my $actions = shift;
+    my $saved_pool_str = "";
+
+    for my $action (@$actions) {
+        if ($action->tag_name eq 'ActionConstantPool') {
+            my $pool = $action->ConstantPool;
+            for my $string (@$pool) {
+                if ($string =~ /^https?:\/\//) {
+                    $saved_pool_str = $string->value;
+                }
+            }
+        }
+        elsif ($action->tag_name eq 'ActionGetURL2') {
+            if ($saved_pool_str ne "") {
+                $saved_swf_url = $saved_pool_str;
+            }
+        }
+        elsif ($action->tag_name =~ 'ActionGetURL') {
+            $saved_swf_url = $action->UrlString->value;
+        }
+    }
+}
+
+# SWF check tag utility
+sub swf_check_tag {
+    my ($t, $stream) = @_;
+    my ($tagname) = $t->tag_name;
+
+    for ($tagname) {
+    (/^Do(Init)?Action$/ or /^DefineButton$/) and do {
+        swf_search_get_url ($t->Actions);
+        last;
+    };
+    /^PlaceObject2$/ and do {
+        for my $ca (@{$t->ClipActions}) {
+            swf_search_get_url ($ca->Actions);
+        }
+        last;
+    };
+    /^DefineButton2$/ and do {
+        for my $ba (@{$t->Actions}) {
+            swf_search_get_url ($ba->Actions);
+        }
+        last;
+    };
+    /^DefineSprite$/ and do {
+        for my $tag (@{$t->ControlTags}) {
+            swf_search_get_url ($tag, $stream);
+        }
+        last;
+    };
+    }
+}
+
+# Callback for swf parser
+sub swf_tag_callback {
+    my ($self, $tag, $length, $stream)=@_;
+    my $t = SWF::Element::Tag->new (Tag=>$tag, Length=>$length);
+    my ($tagname) = $t->tag_name;
+
+    return unless
+          $tagname eq 'DoAction'      or
+          $tagname eq 'DoInitAction'  or
+          $tagname eq 'PlaceObject2'  or
+          $tagname eq 'DefineButton'  or
+          $tagname eq 'DefineButton2' or
+          $tagname eq 'DefineSprite';
+    
+    if ($tagname eq 'DefineSprite') {
+
+        # Tags in the sprite are not unpacked here.
+
+        $t->shallow_unpack ($stream);
+        $t->TagStream->parse (callback => \&swf_tag_callback);
+        return;
+
+    } elsif ($tagname eq 'PlaceObject2') {
+
+        # Most of PlaceObject2 tags don't have ClipActions.
+
+        $t->lookahead_Flags ($stream);
+        return unless $t->PlaceFlagHasClipActions;
+    }
+
+    # unpack the tag and search actions.
+
+    $t->unpack ($stream);
+    swf_check_tag ($t);
+}
+
+# Check url from memcached cache first
+sub memcached_check_url {
+    my ( $url ) = @_;
+
+    my $context = Digest::SHA256::new($cfg{digest_bits});
+
+    if ($cfg{debug}) {
+        write_log ("127.0.0.1", "Check key '". unpack("H*", ($context->hash($url))) . "'");
+    }
+
+    return $memd->get(unpack("H*", ($context->hash($url))));
+}
+
+# Write url to memcached key
+sub memcached_cache_url {
+    my ( $url, $url_real ) = @_;
+
+    my $context = Digest::SHA256::new($cfg{digest_bits});
+
+    if ($cfg{debug}) {
+        write_log ("127.0.0.1", "Cache key '". unpack("H*", ($context->hash($url))) . "' with value '$url_real'");
+    }
+
+    $memd->set(unpack("H*", ($context->hash($url))), $url_real, $cfg{cache_expire});
+}
+
+# POE http client callback
+sub process_client {
+    my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
+    
+    my $http_request = $_[ARG0]->[0];
+    my $rec = $_[ARG0]->[1][0];
+    my $http_response = $_[ARG1]->[0];
+    my $base_url = $_[ARG0]->[1][1];
+    $saved_swf_url = "";
+    
+    if ($rec == 0) {
+        $base_url = $http_request->uri;
+    }
+    else {
+        # Check cache for each url
+        my $redirect = memcached_check_url($http_request->uri);
+        if ($redirect) {
+            write_log ($heap->{remote_ip}, "Memcached redirect from: " . $http_response->base . " to: " . $redirect);
+            my $new_response = HTTP::Response->new(200);
+            $new_response->header("Uri", $redirect); 
+
+            # Avoid sending the response if the client has gone away.
+            $heap->{client}->put($new_response) if defined $heap->{client};
+
+            # Shut down the client's connection when the response is sent.
+            return;
+        }
+    }
+
+    if ($do_reopen_log) {
+        $do_reopen_log = 0;
+        reopen_log();
+    }
+
+    if ($rec > $cfg{max_rec}) {
+        write_log ($heap->{remote_ip}, "Max recursion exceeded: $rec, returning '$base_url' -> '" . $http_request->uri . "'");
+        # Write to cache
+        memcached_cache_url ($base_url, $http_request->uri);
+        my $new_response = HTTP::Response->new(200);
+        $new_response->header("Uri", $http_request->uri); 
+
+        # Avoid sending the response if the client has gone away.
+        $heap->{client}->put($new_response) if defined $heap->{client};
+
+        # Shut down the client's connection when the response is sent.
+        $kernel->yield("shutdown");
+        return;
+    }
+    
+    # Detect HTTP redirects
+    if ($http_response->is_redirect) {
+        my $redirect = $http_response->header('Location');
+        if ($redirect) {
+            if ($redirect =~ /^https?:\/\//) {
+                write_log ($heap->{remote_ip}, "HTTP redirect from: ". $http_response->base . " to: " . $redirect);
+                my $request = HTTP::Request->new('GET', $redirect);
+                $request->header( "Connection",       "close" );
+                $request->header( "Proxy-Connection", "close" );
+                $kernel->post( "cl", "request", "got_response", $request, [$rec + 1, $base_url]);
+                return;
+            }
+            else {
+                write_log ($heap->{remote_ip}, "Internal redirect, ignoring '$redirect', returning '$base_url' -> '" . $http_request->uri . "'");
+                my $new_response = HTTP::Response->new(200);
+                $new_response->header("Uri", $http_request->uri); 
+
+                # Avoid sending the response if the client has gone away.
+                $heap->{client}->put($new_response) if defined $heap->{client};
+
+                # Shut down the client's connection when the response is sent.
+                $kernel->yield("shutdown");
+                return;
+            }
+        }
+    }
+    my $response_type = $http_response->content_type();
+    if ( $response_type =~ /^text/i ) {
+        my $content = $http_response->decoded_content();
+        my $p = HTML::HeadParser->new($http_response);
+        $p->parse($content);
+        my $expire = $http_response->header('Refresh');
+        if ($http_response->is_redirect || $expire) {
+            my $redirect;
+            if ($expire) {
+                $expire =~ /URL=(\S+)/;
+                $redirect = $1;
+            }
+            else {
+                $redirect = $http_response->header('Location');
+            }
+            if ($redirect) {
+                if ($redirect =~ /^https?:\/\//) {
+                    write_log ($heap->{remote_ip}, "HTML redirect from:". $http_response->base . " to: " . $redirect);
+                    my $request = HTTP::Request->new('GET', $redirect);
+                    $request->header( "Connection",       "close" );
+                    $request->header( "Proxy-Connection", "close" );
+                    $kernel->post( "cl", "request", "got_response", $request, [$rec + 1, $base_url]);
+                    return;
+                }
+                else {
+                    write_log ($heap->{remote_ip}, "Internal redirect, ignoring '$redirect'");
+                }
+            }
+        }
+        if ($content =~ /location\s*=\s*["']*(https?:\/\/[^"'\s]+)["']*/im) {
+            my $redir = uri_unescape ($1);
+            write_log ($heap->{remote_ip}, "JavaScript redirect from:". $http_response->base . " to: " . $1);
+            my $request = HTTP::Request->new('GET', $redir);
+            $request->header( "Connection",       "close" );
+            $request->header( "Proxy-Connection", "close" );
+            $kernel->post( "cl", "request", "got_response", $request, [$rec + 1, $base_url]);
+            return;
+        }
+    }
+    elsif ( $response_type eq 'application/x-shockwave-flash' || 
+            ($http_request->uri =~ /\.swf(\?.*)?$/i && $http_response->code == 200)) {
+        my $content = $http_response->decoded_content();
+        $swf_parser->parse( $content );
+        if ($saved_swf_url ne "") {
+            write_log ($heap->{remote_ip}, "Flash redirect from:". $http_response->base . " to: " . $saved_swf_url);
+            my $request = HTTP::Request->new('GET', $saved_swf_url);
+            # Reset swf redirect global variable
+            $saved_swf_url = "";
+            $request->header( "Connection",       "close" );
+            $request->header( "Proxy-Connection", "close" );
+            $kernel->post( "cl", "request", "got_response", $request, [$rec + 1, $base_url]);
+            return;
+        }
+    }
+    else {
+        write_log ($heap->{remote_ip}, "Response wasn't text");
+    }
+    
+    write_log ($heap->{remote_ip}, "Returning '$base_url' -> '" . $http_request->uri . "'");
+    # Write to cache
+    memcached_cache_url ($base_url, $http_request->uri);
+    my $new_response = HTTP::Response->new($http_response->code);
+    $new_response->header("Uri", $http_request->uri); 
+
+    # Avoid sending the response if the client has gone away.
+    $heap->{client}->put($new_response) if defined $heap->{client};
+
+    # Shut down the client's connection when the response is sent.
+    $kernel->yield("shutdown");
+
+}
+
+sub process_input {
+    my ( $kernel, $heap, $request ) = @_[ KERNEL, HEAP, ARG0 ];
+
+    if ($request->isa ("HTTP::Response")) {
+        $heap->{client}->put($request);
+        $kernel->yield("shutdown");
+        return;
+    }
+    
+    # Check cache first
+    my $redirect = memcached_check_url($request->uri);
+    if ($redirect) {
+        write_log ($heap->{remote_ip}, "Memcached redirect from: " . $request->uri . " to: " . $redirect);
+        my $new_response = HTTP::Response->new(200);
+        $new_response->header("Uri", $redirect); 
+        $new_response->header("Connection", "close"); 
+        $new_response->header("Proxy-Connection", "close"); 
+
+        # Avoid sending the response if the client has gone away.
+        $heap->{client}->put($new_response) if defined $heap->{client};
+        $kernel->yield("shutdown");
+
+        # Shut down the client's connection when the response is sent.
+        return;
+    }
+    # Start http request
+    my $new_request = HTTP::Request->new('GET', $request->uri);
+    $new_request->header( "Connection",       "close" );
+    $new_request->header( "Proxy-Connection", "close" );
+    $kernel->post( "cl", "request", "got_response", $new_request, [0, ""]);
+}
+
+POE::Component::Server::TCP->new
+  ( Alias => "",
+    Port         => $cfg{port},
+    ClientFilter => 'POE::Filter::HTTPD',
+
+    ClientInput => \&process_input,
+    InlineStates => { got_response => \&process_client, },
+);
+
+swf_init_parser ();
+
+
+# Start POE.  This will run the server until it exits.
+POE::Kernel->run();
+exit 0;
+
+END {
+    unlink($cfg{pidfile});
+    if ($cfg{do_log}) {
+        write_log ("", "Stopping URL resolver");
+        close (LOG);
+    }
+}