示例#1
0
文件: ctx.c 项目: Jimdo/stunnel
NOEXPORT int servername_cb(SSL *ssl, int *ad, void *arg) {
    SERVICE_OPTIONS *section=(SERVICE_OPTIONS *)arg;
    const char *servername=SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
    SERVERNAME_LIST *list;
    CLI *c;
#ifdef USE_LIBWRAP
    char *accepted_address;
#endif /* USE_LIBWRAP */

    /* leave the alert type at SSL_AD_UNRECOGNIZED_NAME */
    (void)ad; /* skip warning about unused parameter */
    if(!section->servername_list_head) { /* no virtual services defined */
        s_log(LOG_DEBUG, "SNI: no virtual services defined");
        return SSL_TLSEXT_ERR_OK;
    }
    if(!servername) { /* no SNI extension received from the client */
        s_log(LOG_NOTICE, "SNI: extension not received from the client");
        return SSL_TLSEXT_ERR_NOACK;
    }
    s_log(LOG_DEBUG, "SNI: searching service for servername: %s", servername);

    for(list=section->servername_list_head; list; list=list->next)
        if(matches_wildcard((char *)servername, list->servername)) {
            s_log(LOG_DEBUG, "SNI: matched pattern: %s", list->servername);
            c=SSL_get_ex_data(ssl, cli_index);
            c->opt=list->opt;
            SSL_set_SSL_CTX(ssl, c->opt->ctx);
            SSL_set_verify(ssl, SSL_CTX_get_verify_mode(c->opt->ctx),
                SSL_CTX_get_verify_callback(c->opt->ctx));
            s_log(LOG_INFO, "SNI: switched to service [%s]", c->opt->servname);
#ifdef USE_LIBWRAP
            accepted_address=s_ntop(&c->peer_addr, c->peer_addr_len);
            libwrap_auth(c, accepted_address); /* retry on a service switch */
            str_free(accepted_address);
#endif /* USE_LIBWRAP */
            return SSL_TLSEXT_ERR_OK;
        }
    s_log(LOG_ERR, "SNI: no pattern matched servername: %s", servername);
    return SSL_TLSEXT_ERR_ALERT_FATAL;
}
示例#2
0
static int init_local(CLI *c) {
    SOCKADDR_UNION addr;
    socklen_t addrlen;

    addrlen=sizeof(SOCKADDR_UNION);
    if(getpeername(c->local_rfd.fd, &addr.sa, &addrlen)<0) {
        strcpy(c->accepting_address, "NOT A SOCKET");
        c->local_rfd.is_socket=0;
        c->local_wfd.is_socket=0; /* TODO: It's not always true */
#ifdef USE_WIN32
        if(get_last_socket_error()!=ENOTSOCK) {
#else
        if(c->opt->option.transparent || get_last_socket_error()!=ENOTSOCK) {
#endif
            sockerror("getpeerbyname");
            return -1;
        }
        /* Ignore ENOTSOCK error so 'local' doesn't have to be a socket */
    } else { /* success */
        /* copy addr to c->peer_addr */
        memcpy(&c->peer_addr.addr[0], &addr, sizeof(SOCKADDR_UNION));
        c->peer_addr.num=1;
        s_ntop(c->accepting_address, &c->peer_addr.addr[0]);
        c->local_rfd.is_socket=1;
        c->local_wfd.is_socket=1; /* TODO: It's not always true */
        /* It's a socket: lets setup options */
        if(set_socket_options(c->local_rfd.fd, 1)<0)
            return -1;
        if(auth_libwrap(c)<0)
            return -1;
        if(auth_user(c)<0) {
            s_log(LOG_WARNING, "Connection from %s REFUSED by IDENT",
                c->accepting_address);
            return -1;
        }
        s_log(LOG_NOTICE, "%s connected from %s",
            c->opt->servname, c->accepting_address);
    }
    return 0; /* OK */
}

static int init_remote(CLI *c) {
    int fd;

    /* create connection to host/service */
    if(c->opt->source_addr.num)
        memcpy(&c->bind_addr, &c->opt->source_addr, sizeof(SOCKADDR_LIST));
#ifndef USE_WIN32
    else if(c->opt->option.transparent)
        memcpy(&c->bind_addr, &c->peer_addr, sizeof(SOCKADDR_LIST));
#endif
    else {
        c->bind_addr.num=0; /* don't bind connecting socket */
    }
    /* Setup c->remote_fd, now */
    if(c->opt->option.remote) {
        fd=connect_remote(c);
    } else /* NOT in remote mode */
        fd=connect_local(c);
    if(fd<0) {
        s_log(LOG_ERR, "Failed to initialize remote connection");
        return -1;
    }
#ifndef USE_WIN32
    if(fd>=max_fds) {
        s_log(LOG_ERR, "Remote file descriptor out of range (%d>=%d)",
            fd, max_fds);
        closesocket(fd);
        return -1;
    }
#endif
    s_log(LOG_DEBUG, "Remote FD=%d initialized", fd);
    c->remote_fd.fd=fd;
    c->remote_fd.is_socket=1; /* Always! */
    if(set_socket_options(fd, 2)<0)
        return -1;
    return 0; /* OK */
}
示例#3
0
static void daemon_loop(void) {
    SOCKADDR_UNION addr;
    s_poll_set fds;
    LOCAL_OPTIONS *opt;

    get_limits();
    s_poll_zero(&fds);
#ifndef USE_WIN32
    s_poll_add(&fds, signal_pipe_init(), 1, 0);
#endif

    if(!local_options.next) {
        s_log(LOG_ERR, "No connections defined in config file");
        exit(1);
    }

    num_clients=0;

    /* bind local ports */
    for(opt=local_options.next; opt; opt=opt->next) {
        if(!opt->option.accept) /* no need to bind this service */
            continue;
        memcpy(&addr, &opt->local_addr.addr[0], sizeof(SOCKADDR_UNION));
        if((opt->fd=socket(addr.sa.sa_family, SOCK_STREAM, 0))<0) {
            sockerror("local socket");
            exit(1);
        }
        if(alloc_fd(opt->fd))
            exit(1);
        if(set_socket_options(opt->fd, 0)<0)
            exit(1);
        s_ntop(opt->local_address, &addr);
        if(bind(opt->fd, &addr.sa, addr_len(addr))) {
            s_log(LOG_ERR, "Error binding %s to %s",
                opt->servname, opt->local_address);
            sockerror("bind");
            exit(1);
        }
        s_log(LOG_DEBUG, "%s bound to %s", opt->servname, opt->local_address);
        if(listen(opt->fd, 5)) {
            sockerror("listen");
            exit(1);
        }
#ifdef FD_CLOEXEC
        fcntl(opt->fd, F_SETFD, FD_CLOEXEC); /* close socket in child execvp */
#endif
        s_poll_add(&fds, opt->fd, 1, 0);
    }

#if !defined (USE_WIN32) && !defined (__vms)
    if(!(options.option.foreground))
        daemonize();
    drop_privileges();
    create_pid();
#endif /* !defined USE_WIN32 && !defined (__vms) */

    /* create exec+connect services */
    for(opt=local_options.next; opt; opt=opt->next) {
        if(opt->option.accept) /* skip ordinary (accepting) services */
            continue;
        enter_critical_section(CRIT_CLIENTS); /* for multi-cpu machines */
        num_clients++;
        leave_critical_section(CRIT_CLIENTS);
        create_client(-1, -1, alloc_client_session(opt, -1, -1), client);
    }

    while(1) {
        if(s_poll_wait(&fds, -1)<0) /* non-critical error */
            log_error(LOG_INFO, get_last_socket_error(),
                "daemon_loop: s_poll_wait");
        else 
            for(opt=local_options.next; opt; opt=opt->next)
                if(s_poll_canread(&fds, opt->fd))
                    accept_connection(opt);
    }
    s_log(LOG_ERR, "INTERNAL ERROR: End of infinite loop 8-)");
}
示例#4
0
文件: client.c 项目: l7s/stunnel
static void init_local(CLI *c) {
    SOCKADDR_UNION addr;
    socklen_t addrlen;

    addrlen=sizeof addr;
    if(getpeername(c->local_rfd.fd, &addr.sa, &addrlen)<0) {
        strcpy(c->accepted_address, "NOT A SOCKET");
        c->local_rfd.is_socket=0;
        c->local_wfd.is_socket=0; /* TODO: It's not always true */
#ifdef USE_WIN32
        if(get_last_socket_error()!=ENOTSOCK) {
#else
        if(c->opt->option.transparent_src || get_last_socket_error()!=ENOTSOCK) {
#endif
            sockerror("getpeerbyname");
            longjmp(c->err, 1);
        }
        /* ignore ENOTSOCK error so 'local' doesn't have to be a socket */
    } else { /* success */
        /* copy addr to c->peer_addr */
        memcpy(&c->peer_addr.addr[0], &addr, sizeof addr);
        c->peer_addr.num=1;
        s_ntop(c->accepted_address, &c->peer_addr.addr[0]);
        c->local_rfd.is_socket=1;
        c->local_wfd.is_socket=1; /* TODO: It's not always true */
        /* it's a socket: lets setup options */
        if(set_socket_options(c->local_rfd.fd, 1)<0)
            longjmp(c->err, 1);
#ifdef USE_LIBWRAP
        libwrap_auth(c);
#endif /* USE_LIBWRAP */
        auth_user(c);
        s_log(LOG_NOTICE, "Service %s accepted connection from %s",
            c->opt->servname, c->accepted_address);
    }
}

static void init_remote(CLI *c) {
    /* create connection to host/service */
    if(c->opt->source_addr.num)
        memcpy(&c->bind_addr, &c->opt->source_addr, sizeof(SOCKADDR_LIST));
#ifndef USE_WIN32
    else if(c->opt->option.transparent_src)
        memcpy(&c->bind_addr, &c->peer_addr, sizeof(SOCKADDR_LIST));
#endif
    else {
        c->bind_addr.num=0; /* don't bind connecting socket */
    }

    /* setup c->remote_fd, now */
    if(c->opt->option.remote)
        c->remote_fd.fd=connect_remote(c);
#ifdef SO_ORIGINAL_DST
    else if(c->opt->option.transparent_dst)
        c->remote_fd.fd=connect_transparent(c);
#endif /* SO_ORIGINAL_DST */
    else /* NOT in remote mode */
        c->remote_fd.fd=connect_local(c);
    c->remote_fd.is_socket=1; /* always! */
    s_log(LOG_DEBUG, "Remote FD=%d initialized", c->remote_fd.fd);
    if(set_socket_options(c->remote_fd.fd, 2)<0)
        longjmp(c->err, 1);
}

static void init_ssl(CLI *c) {
    int i, err;
    SSL_SESSION *old_session;

    if(!(c->ssl=SSL_new(c->opt->ctx))) {
        sslerror("SSL_new");
        longjmp(c->err, 1);
    }
    SSL_set_ex_data(c->ssl, cli_index, c); /* for callbacks */
    SSL_set_session_id_context(c->ssl, (unsigned char *)sid_ctx,
        strlen(sid_ctx));
    if(c->opt->option.client) {
#ifndef OPENSSL_NO_TLSEXT
        if(c->opt->host_name) {
            s_log(LOG_DEBUG, "SNI: host name: %s", c->opt->host_name);
            if(!SSL_set_tlsext_host_name(c->ssl, c->opt->host_name)) {
                sslerror("SSL_set_tlsext_host_name");
                longjmp(c->err, 1);
            }
        }
#endif
        if(c->opt->session) {
            enter_critical_section(CRIT_SESSION);
            SSL_set_session(c->ssl, c->opt->session);
            leave_critical_section(CRIT_SESSION);
        }
        SSL_set_fd(c->ssl, c->remote_fd.fd);
        SSL_set_connect_state(c->ssl);
    } else {
        if(c->local_rfd.fd==c->local_wfd.fd)
            SSL_set_fd(c->ssl, c->local_rfd.fd);
        else {
           /* does it make sence to have SSL on STDIN/STDOUT? */
            SSL_set_rfd(c->ssl, c->local_rfd.fd);
            SSL_set_wfd(c->ssl, c->local_wfd.fd);
        }
        SSL_set_accept_state(c->ssl);
    }

    /* setup some values for transfer() function */
    if(c->opt->option.client) {
        c->sock_rfd=&(c->local_rfd);
        c->sock_wfd=&(c->local_wfd);
        c->ssl_rfd=c->ssl_wfd=&(c->remote_fd);
    } else {
        c->sock_rfd=c->sock_wfd=&(c->remote_fd);
        c->ssl_rfd=&(c->local_rfd);
        c->ssl_wfd=&(c->local_wfd);
    }

    while(1) {
#if OPENSSL_VERSION_NUMBER<0x1000002f
        /* this critical section is a crude workaround for CVE-2010-3864 *
         * see http://www.securityfocus.com/bid/44884 for details        *
         * NOTE: this critical section also covers callbacks (e.g. OCSP) */
        enter_critical_section(CRIT_SSL);
#endif /* OpenSSL version < 1.0.0b */
        if(c->opt->option.client)
            i=SSL_connect(c->ssl);
        else
            i=SSL_accept(c->ssl);
#if OPENSSL_VERSION_NUMBER<0x1000002f
        leave_critical_section(CRIT_SSL);
#endif /* OpenSSL version < 1.0.0b */
        err=SSL_get_error(c->ssl, i);
        if(err==SSL_ERROR_NONE)
            break; /* ok -> done */
        if(err==SSL_ERROR_WANT_READ || err==SSL_ERROR_WANT_WRITE) {
            s_poll_init(&c->fds);
            s_poll_add(&c->fds, c->ssl_rfd->fd,
                err==SSL_ERROR_WANT_READ,
                err==SSL_ERROR_WANT_WRITE);
            switch(s_poll_wait(&c->fds, c->opt->timeout_busy, 0)) {
            case -1:
                sockerror("init_ssl: s_poll_wait");
                longjmp(c->err, 1);
            case 0:
                s_log(LOG_INFO, "init_ssl: s_poll_wait:"
                    " TIMEOUTbusy exceeded: sending reset");
                longjmp(c->err, 1);
            case 1:
                break; /* OK */
            default:
                s_log(LOG_ERR, "init_ssl: s_poll_wait: unknown result");
                longjmp(c->err, 1);
            }
            continue; /* ok -> retry */
        }
        if(err==SSL_ERROR_SYSCALL) {
            switch(get_last_socket_error()) {
            case EINTR:
            case EAGAIN:
                continue;
            }
        }
        if(c->opt->option.client)
            sslerror("SSL_connect");
        else
            sslerror("SSL_accept");
        longjmp(c->err, 1);
    }
    if(SSL_session_reused(c->ssl)) {
        s_log(LOG_INFO, "SSL %s: previous session reused",
            c->opt->option.client ? "connected" : "accepted");
    } else { /* a new session was negotiated */
        if(c->opt->option.client) {
            s_log(LOG_INFO, "SSL connected: new session negotiated");
            enter_critical_section(CRIT_SESSION);
            old_session=c->opt->session;
            c->opt->session=SSL_get1_session(c->ssl); /* store it */
            if(old_session)
                SSL_SESSION_free(old_session); /* release the old one */
            leave_critical_section(CRIT_SESSION);
        } else
            s_log(LOG_INFO, "SSL accepted: new session negotiated");
        print_cipher(c);
    }
}
示例#5
0
static void init_local(CLI *c) {
    SOCKADDR_UNION addr;
    socklen_t addr_len;
    char *accepted_address;

    /* check if local_rfd is a socket and get peer address */
    addr_len=sizeof(SOCKADDR_UNION);
    c->local_rfd.is_socket=!getpeername(c->local_rfd.fd, &addr.sa, &addr_len);
    if(c->local_rfd.is_socket) {
        memcpy(&c->peer_addr.sa, &addr.sa, addr_len);
        c->peer_addr_len=addr_len;
        if(set_socket_options(c->local_rfd.fd, 1))
            s_log(LOG_WARNING, "Failed to set local socket options");
    } else {
        if(get_last_socket_error()!=S_ENOTSOCK) {
            sockerror("getpeerbyname (local_rfd)");
            longjmp(c->err, 1);
        }
    }

    /* check if local_wfd is a socket and get peer address */
    if(c->local_rfd.fd==c->local_wfd.fd) {
        c->local_wfd.is_socket=c->local_rfd.is_socket;
    } else {
        addr_len=sizeof(SOCKADDR_UNION);
        c->local_wfd.is_socket=!getpeername(c->local_wfd.fd, &addr.sa, &addr_len);
        if(c->local_wfd.is_socket) {
            if(!c->local_rfd.is_socket) { /* already retrieved */
                memcpy(&c->peer_addr.sa, &addr.sa, addr_len);
                c->peer_addr_len=addr_len;
            }
            if(set_socket_options(c->local_wfd.fd, 1))
                s_log(LOG_WARNING, "Failed to set local socket options");
        } else {
            if(get_last_socket_error()!=S_ENOTSOCK) {
                sockerror("getpeerbyname (local_wfd)");
                longjmp(c->err, 1);
            }
        }
    }

    /* neither of local descriptors is a socket */
    if(!c->local_rfd.is_socket && !c->local_rfd.is_socket) {
#ifndef USE_WIN32
        if(c->opt->option.transparent_src) {
            s_log(LOG_ERR, "Transparent source needs a socket");
            longjmp(c->err, 1);
        }
#endif
        s_log(LOG_NOTICE, "Service [%s] accepted connection", c->opt->servname);
        return;
    }

    /* authenticate based on retrieved IP address of the client */
    accepted_address=s_ntop(&c->peer_addr, c->peer_addr_len);
#ifdef USE_LIBWRAP
    libwrap_auth(c, accepted_address);
#endif /* USE_LIBWRAP */
    auth_user(c, accepted_address);
    s_log(LOG_NOTICE, "Service [%s] accepted connection from %s",
        c->opt->servname, accepted_address);
    str_free(accepted_address);
}
示例#6
0
/* open new ports, update fds */
int bind_ports(void) {
    SERVICE_OPTIONS *opt;
    char *local_address;
    int listening_section;

#ifdef USE_LIBWRAP
    /* execute after options_cmdline() to know service_options.next,
     * but as early as possible to avoid leaking file descriptors */
    /* retry on each bind_ports() in case stunnel.conf was reloaded
       without "libwrap = no" */
    libwrap_init();
#endif /* USE_LIBWRAP */

    s_poll_init(fds);
    s_poll_add(fds, signal_pipe[0], 1, 0);

    /* allow clean unbind_ports() even though
       bind_ports() was not fully performed */
    for(opt=service_options.next; opt; opt=opt->next)
        if(opt->option.accept)
            opt->fd=INVALID_SOCKET;

    listening_section=0;
    for(opt=service_options.next; opt; opt=opt->next) {
        if(opt->option.accept) {
            if(listening_section<systemd_fds) {
                opt->fd=(SOCKET)(listen_fds_start+listening_section);
                s_log(LOG_DEBUG,
                    "Listening file descriptor received from systemd (FD=%d)",
                    opt->fd);
            } else {
                opt->fd=s_socket(opt->local_addr.sa.sa_family,
                    SOCK_STREAM, 0, 1, "accept socket");
                if(opt->fd==INVALID_SOCKET)
                    return 1;
                s_log(LOG_DEBUG, "Listening file descriptor created (FD=%d)",
                    opt->fd);
            }
            if(set_socket_options(opt->fd, 0)<0) {
                closesocket(opt->fd);
                opt->fd=INVALID_SOCKET;
                return 1;
            }
            /* local socket can't be unnamed */
            local_address=s_ntop(&opt->local_addr, addr_len(&opt->local_addr));
            /* we don't bind or listen on a socket inherited from systemd */
            if(listening_section>=systemd_fds) {
                if(bind(opt->fd, &opt->local_addr.sa, addr_len(&opt->local_addr))) {
                    s_log(LOG_ERR, "Error binding service [%s] to %s",
                        opt->servname, local_address);
                    sockerror("bind");
                    closesocket(opt->fd);
                    opt->fd=INVALID_SOCKET;
                    str_free(local_address);
                    return 1;
                }
                if(listen(opt->fd, SOMAXCONN)) {
                    sockerror("listen");
                    closesocket(opt->fd);
                    opt->fd=INVALID_SOCKET;
                    str_free(local_address);
                    return 1;
                }
            }
            s_poll_add(fds, opt->fd, 1, 0);
            s_log(LOG_DEBUG, "Service [%s] (FD=%d) bound to %s",
                opt->servname, opt->fd, local_address);
            str_free(local_address);
            ++listening_section;
        } else if(opt->exec_name && opt->connect_addr.names) {
            /* create exec+connect services */
            /* FIXME: needs to be delayed on reload with opt->option.retry set */
            create_client(INVALID_SOCKET, INVALID_SOCKET,
                alloc_client_session(opt, INVALID_SOCKET, INVALID_SOCKET),
                client_thread);
        }
    }
    if(listening_section<systemd_fds) {
        s_log(LOG_ERR,
            "Too many listening file descriptors received from systemd, got %d",
            systemd_fds);
        return 1;
    }
    return 0; /* OK */
}
示例#7
0
void child_status(void) { /* dead libwrap or 'exec' process detected */
    int pid, status;
    char *sig_name;

#ifdef HAVE_WAIT_FOR_PID
    while((pid=wait_for_pid(-1, &status, WNOHANG))>0) {
#else
    if((pid=wait(&status))>0) {
#endif
#ifdef WIFSIGNALED
        if(WIFSIGNALED(status)) {
            sig_name=signal_name(WTERMSIG(status));
            s_log(LOG_INFO, "Child process %d terminated on %s",
                pid, sig_name);
            str_free(sig_name);
        } else {
            s_log(LOG_INFO, "Child process %d finished with code %d",
                pid, WEXITSTATUS(status));
        }
#else
        s_log(LOG_INFO, "Child process %d finished with status %d",
            pid, status);
#endif
    }
}

#endif /* !defined(USE_OS2) */

#endif /* !defined(USE_WIN32) */

/**************************************** main loop accepting connections */

void daemon_loop(void) {
    while(1) {
        int temporary_lack_of_resources=0;
        int num=s_poll_wait(fds, -1, -1);
        if(num>=0) {
            SERVICE_OPTIONS *opt;
            s_log(LOG_DEBUG, "Found %d ready file descriptor(s)", num);
            if(service_options.log_level>=LOG_DEBUG) /* performance optimization */
                s_poll_dump(fds, LOG_DEBUG);
            if(s_poll_canread(fds, signal_pipe[0]))
                if(signal_pipe_dispatch()) /* SIGNAL_TERMINATE or error */
                    break; /* terminate daemon_loop */
            for(opt=service_options.next; opt; opt=opt->next)
                if(opt->option.accept && s_poll_canread(fds, opt->fd))
                    if(accept_connection(opt))
                        temporary_lack_of_resources=1;
        } else {
            log_error(LOG_NOTICE, get_last_socket_error(),
                "daemon_loop: s_poll_wait");
            temporary_lack_of_resources=1;
        }
        if(temporary_lack_of_resources) {
            s_log(LOG_NOTICE,
                "Accepting new connections suspended for 1 second");
            sleep(1); /* to avoid log trashing */
        }
    }
}

    /* return 1 when a short delay is needed before another try */
NOEXPORT int accept_connection(SERVICE_OPTIONS *opt) {
    SOCKADDR_UNION addr;
    char *from_address;
    SOCKET s;
    socklen_t addrlen;

    addrlen=sizeof addr;
    for(;;) {
        s=s_accept(opt->fd, &addr.sa, &addrlen, 1, "local socket");
        if(s!=INVALID_SOCKET) /* success! */
            break;
        switch(get_last_socket_error()) {
            case S_EINTR: /* interrupted by a signal */
                break; /* retry now */
            case S_EMFILE:
#ifdef S_ENFILE
            case S_ENFILE:
#endif
#ifdef S_ENOBUFS
            case S_ENOBUFS:
#endif
#ifdef S_ENOMEM
            case S_ENOMEM:
#endif
                return 1; /* temporary lack of resources */
            default:
                return 0; /* any other error */
        }
    }
    from_address=s_ntop(&addr, addrlen);
    s_log(LOG_DEBUG, "Service [%s] accepted (FD=%d) from %s",
        opt->servname, s, from_address);
    str_free(from_address);
#ifdef USE_FORK
    RAND_add("", 1, 0.0); /* each child needs a unique entropy pool */
#else
    if(max_clients && num_clients>=max_clients) {
        s_log(LOG_WARNING, "Connection rejected: too many clients (>=%ld)",
            max_clients);
        closesocket(s);
        return 0;
    }
#endif
    if(create_client(opt->fd, s,
            alloc_client_session(opt, s, s), client_thread)) {
        s_log(LOG_ERR, "Connection rejected: create_client failed");
        closesocket(s);
        return 0;
    }
    return 0;
}
示例#8
0
/****************************** transfer data */
static int transfer(CLI *c) {
    int num, err;
    int check_SSL_pending;
    enum {CL_OPEN, CL_INIT, CL_RETRY, CL_CLOSED} ssl_closing=CL_OPEN;
    int watchdog=0; /* a counter to detect an infinite loop */

	int stunnel_hdr_is_sent=0;
	int insert_new_hdr=0;
	int is_http=-1;	// -1:not set, 0:not http header, 1:is http header
	int space_cnt=0;
	char* space=NULL;
	char* first_hdr_end=NULL;

    c->sock_ptr=c->ssl_ptr=0;
    sock_rd=sock_wr=ssl_rd=ssl_wr=1;
    c->sock_bytes=c->ssl_bytes=0;

    do { /* main loop */
        /* set flag to try and read any buffered SSL data
         * if we made room in the buffer by writing to the socket */
        check_SSL_pending=0;

        /****************************** setup c->fds structure */
        s_poll_zero(&c->fds); /* Initialize the structure */
        if(sock_rd && c->sock_ptr<BUFFSIZE) /* socket input buffer not full*/
            s_poll_add(&c->fds, c->sock_rfd->fd, 1, 0);
        if((ssl_rd && c->ssl_ptr<BUFFSIZE) || /* SSL input buffer not full */
                ((c->sock_ptr || ssl_closing==CL_RETRY) && want_rd))
                /* want to SSL_write or SSL_shutdown but read from the
                 * underlying socket needed for the SSL protocol */
            s_poll_add(&c->fds, c->ssl_rfd->fd, 1, 0);
        if(c->ssl_ptr) /* SSL input buffer not empty */
            s_poll_add(&c->fds, c->sock_wfd->fd, 0, 1);
        if(c->sock_ptr || /* socket input buffer not empty */
                ssl_closing==CL_INIT /* need to send close_notify */ ||
                ((c->ssl_ptr<BUFFSIZE || ssl_closing==CL_RETRY) && want_wr))
                /* want to SSL_read or SSL_shutdown but write to the
                 * underlying socket needed for the SSL protocol */
            s_poll_add(&c->fds, c->ssl_wfd->fd, 0, 1);

        /****************************** wait for an event */
        err=s_poll_wait(&c->fds, (sock_rd && ssl_rd) /* both peers open */ ||
            c->ssl_ptr /* data buffered to write to socket */ ||
            c->sock_ptr /* data buffered to write to SSL */ ?
            c->opt->timeout_idle : c->opt->timeout_close);
        switch(err) {
        case -1:
            sockerror("transfer: s_poll_wait");
            return -1;
        case 0: /* timeout */
            if((sock_rd && ssl_rd) || c->ssl_ptr || c->sock_ptr) {
                s_log(LOG_INFO, "s_poll_wait timeout: connection reset");
                return -1;
            } else { /* already closing connection */
                s_log(LOG_INFO, "s_poll_wait timeout: connection close");
                return 0; /* OK */
            }
        }
        if(!(sock_can_rd || sock_can_wr || ssl_can_rd || ssl_can_wr)) {
            s_log(LOG_ERR, "INTERNAL ERROR: "
                "s_poll_wait returned %d, but no descriptor is ready", err);
            return -1;
        }

        /****************************** send SSL close_notify message */
        if(ssl_closing==CL_INIT || (ssl_closing==CL_RETRY &&
                ((want_rd && ssl_can_rd) || (want_wr && ssl_can_wr)))) {
            switch(SSL_shutdown(c->ssl)) { /* Send close_notify */
            case 1: /* the shutdown was successfully completed */
                s_log(LOG_INFO, "SSL_shutdown successfully sent close_notify");
                ssl_closing=CL_CLOSED; /* done! */
                break;
            case 0: /* the shutdown is not yet finished */
                s_log(LOG_DEBUG, "SSL_shutdown retrying");
                ssl_closing=CL_RETRY; /* retry next time */
                break;
            case -1: /* a fatal error occurred */
                sslerror("SSL_shutdown");
                return -1;
            }
        }

        /****************************** write to socket */
        if(sock_wr && sock_can_wr) {

		/* for stunnel to tell web server the remote ip address */
		int write_len;
		char real_remote_addr[IPLEN+2];
		char addr_header[64];
		char* colon;

		if(is_http == -1 && !stunnel_hdr_is_sent)
		{
			space = c->ssl_buff;
			for(; space_cnt < 2; space_cnt++)
			{
				space = strchr(space, ' ');
				if(space == NULL)
					break;
				else if(space - c->ssl_buff > c->ssl_ptr - 1)
				{
					space = NULL;
					break;
				}

				space++;
			}

			if(space_cnt == 2)
			{
				if(strncmp(space, "HTTP/", strlen("HTTP/"))==0)
					is_http = 1;
				else
					is_http = 0;
			}
		}

		if(is_http == 1 && !stunnel_hdr_is_sent)
		{
			first_hdr_end = strstr(c->ssl_buff, "\r\n");
			if(first_hdr_end - c->ssl_buff <= c->ssl_ptr - strlen("\r\n"))
				insert_new_hdr = 1;
			
		}

		if(insert_new_hdr == 1)
		{
			first_hdr_end += 2;
			write_len = (int)first_hdr_end - (int)c->ssl_buff;
			num = writesocket(c->sock_wfd->fd, c->ssl_buff, write_len);

			//fprintf(stderr, "1: %d, %d\n", num, write_len);	
			s_ntop(real_remote_addr, &c->peer_addr.addr[0]);
			colon = strchr(real_remote_addr, ':');
			real_remote_addr[(int)colon - (int)real_remote_addr] = '\0';
			sprintf(addr_header, "StunnelRemoteIP: %s\r\n", real_remote_addr);
			writesocket(c->sock_wfd->fd, addr_header, strlen(addr_header));
			//fprintf(stderr, "2: %d, %d, %s\n", num, strlen(addr_header), addr_header);	
			write_len = c->ssl_ptr - write_len;
			num += writesocket(c->sock_wfd->fd, first_hdr_end, write_len);
			//fprintf(stderr, "3: %d, %d\n", num, write_len);

			stunnel_hdr_is_sent = 1;
			insert_new_hdr = 0;
		}
		else
		{
			num = writesocket(c->sock_wfd->fd, c->ssl_buff, c->ssl_ptr);
		}


            switch(num) {
            case -1: /* error */
                if(parse_socket_error("writesocket"))
                    return -1;
                break;
            case 0:
                s_log(LOG_DEBUG, "No data written to the socket: retrying");
                break;
            default:
                memmove(c->ssl_buff, c->ssl_buff+num, c->ssl_ptr-num);
                if(c->ssl_ptr==BUFFSIZE) /* buffer was previously full */
                    check_SSL_pending=1; /* check for data buffered by SSL */
                c->ssl_ptr-=num;
                c->sock_bytes+=num;
                watchdog=0; /* reset watchdog */
            }
        }

        /****************************** write to SSL */
        if(ssl_wr && c->sock_ptr && ( /* output buffer not empty */
                ssl_can_wr || (want_rd && ssl_can_rd)
                /* SSL_write wants to read from the underlying descriptor */
                )) {
            num=SSL_write(c->ssl, c->sock_buff, c->sock_ptr);
            switch(err=SSL_get_error(c->ssl, num)) {
            case SSL_ERROR_NONE:
                memmove(c->sock_buff, c->sock_buff+num, c->sock_ptr-num);
                c->sock_ptr-=num;
                c->ssl_bytes+=num;
                watchdog=0; /* reset watchdog */
                break;
            case SSL_ERROR_WANT_WRITE:
                s_log(LOG_DEBUG, "SSL_write returned WANT_WRITE: retrying");
                break;
            case SSL_ERROR_WANT_READ:
                s_log(LOG_DEBUG, "SSL_write returned WANT_READ: retrying");
                break;
            case SSL_ERROR_WANT_X509_LOOKUP:
                s_log(LOG_DEBUG,
                    "SSL_write returned WANT_X509_LOOKUP: retrying");
                break;
            case SSL_ERROR_SYSCALL: /* really an error */
                if(num && parse_socket_error("SSL_write"))
                    return -1;
                break;
            case SSL_ERROR_ZERO_RETURN: /* close_notify received */
                s_log(LOG_DEBUG, "SSL closed on SSL_write");
                ssl_rd=0;
                break;
            case SSL_ERROR_SSL:
                sslerror("SSL_write");
                return -1;
            default:
                s_log(LOG_ERR, "SSL_write/SSL_get_error returned %d", err);
                return -1;
            }
        }

        /****************************** read from socket */
        if(sock_rd && sock_can_rd) {
            num=readsocket(c->sock_rfd->fd,
                c->sock_buff+c->sock_ptr, BUFFSIZE-c->sock_ptr);
            switch(num) {
            case -1:
                if(parse_socket_error("readsocket"))
                    return -1;
                break;
            case 0: /* close */
                s_log(LOG_DEBUG, "Socket closed on read");
                sock_rd=0;
                break;
            default:
                c->sock_ptr+=num;
                watchdog=0; /* reset watchdog */
            }
        }

        /****************************** read from SSL */
        if(ssl_rd && c->ssl_ptr<BUFFSIZE  && ( /* input buffer not full */
                ssl_can_rd || (want_wr && ssl_can_wr) ||
                /* SSL_read wants to write to the underlying descriptor */
                (check_SSL_pending && SSL_pending(c->ssl))
                /* write made space from full buffer */
                )) {
            num=SSL_read(c->ssl, c->ssl_buff+c->ssl_ptr, BUFFSIZE-c->ssl_ptr);
            switch(err=SSL_get_error(c->ssl, num)) {
            case SSL_ERROR_NONE:
                c->ssl_ptr+=num;
                watchdog=0; /* reset watchdog */
                break;
            case SSL_ERROR_WANT_WRITE:
                s_log(LOG_DEBUG, "SSL_read returned WANT_WRITE: retrying");
                break;
            case SSL_ERROR_WANT_READ:
                s_log(LOG_DEBUG, "SSL_read returned WANT_READ: retrying");
                break;
            case SSL_ERROR_WANT_X509_LOOKUP:
                s_log(LOG_DEBUG,
                    "SSL_read returned WANT_X509_LOOKUP: retrying");
                break;
            case SSL_ERROR_SYSCALL:
                if(!num) { /* EOF */
                    if(c->sock_ptr) {
                        s_log(LOG_ERR,
                            "SSL socket closed with %d byte(s) in buffer",
                            c->sock_ptr);
                        return -1; /* reset the socket */
                    }
                    s_log(LOG_DEBUG, "SSL socket closed on SSL_read");
                    ssl_rd=ssl_wr=0; /* buggy or SSLv2 peer: no close_notify */
                    ssl_closing=CL_CLOSED; /* don't try to send it back */
                } else if(parse_socket_error("SSL_read"))
                    return -1;
                break;
            case SSL_ERROR_ZERO_RETURN: /* close_notify received */
                s_log(LOG_DEBUG, "SSL closed on SSL_read");
                ssl_rd=0;
                break;
            case SSL_ERROR_SSL:
                sslerror("SSL_read");
                return -1;
            default:
                s_log(LOG_ERR, "SSL_read/SSL_get_error returned %d", err);
                return -1;
            }
        }

        /****************************** check write shutdown conditions */
        if(sock_wr && !ssl_rd && !c->ssl_ptr) {
            s_log(LOG_DEBUG, "Socket write shutdown");
            sock_wr=0; /* no further write allowed */
            shutdown(c->sock_wfd->fd, SHUT_WR); /* send TCP FIN */
        }
        if(ssl_wr && (!sock_rd || SSL_get_shutdown(c->ssl)) && !c->sock_ptr) {
            s_log(LOG_DEBUG, "SSL write shutdown");
            ssl_wr=0; /* no further write allowed */
            if(strcmp(SSL_get_version(c->ssl), "SSLv2")) { /* SSLv3, TLSv1 */
                ssl_closing=CL_INIT; /* initiate close_notify */
            } else { /* no alerts in SSLv2 including close_notify alert */
                shutdown(c->sock_rfd->fd, SHUT_RD); /* notify the kernel */
                shutdown(c->sock_wfd->fd, SHUT_WR); /* send TCP FIN */
                SSL_set_shutdown(c->ssl, /* notify the OpenSSL library */
                    SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);
                ssl_rd=0; /* no further read allowed */
                ssl_closing=CL_CLOSED; /* closed */
            }
        }
        if(ssl_closing==CL_RETRY) { /* SSL shutdown */
            if(!want_rd && !want_wr) { /* close_notify alert was received */
                s_log(LOG_DEBUG, "SSL doesn't need to read or write");
                ssl_closing=CL_CLOSED;
            }
            if(watchdog>5) {
                s_log(LOG_NOTICE, "Too many retries on SSL shutdown");
                ssl_closing=CL_CLOSED;
            }
        }

        /****************************** check watchdog */
        if(++watchdog>100) { /* loop executes without transferring any data */
            s_log(LOG_ERR,
                "transfer() loop executes not transferring any data");
            s_log(LOG_ERR,
                "please report the problem to [email protected]");
            s_log(LOG_ERR, "socket open: rd=%s wr=%s, ssl open: rd=%s wr=%s",
                sock_rd ? "yes" : "no", sock_wr ? "yes" : "no",
                ssl_rd ? "yes" : "no", ssl_wr ? "yes" : "no");
            s_log(LOG_ERR, "socket ready: rd=%s wr=%s, ssl ready: rd=%s wr=%s",
                sock_can_rd ? "yes" : "no", sock_can_wr ? "yes" : "no",
                ssl_can_rd ? "yes" : "no", ssl_can_wr ? "yes" : "no");
            s_log(LOG_ERR, "ssl want: rd=%s wr=%s",
                want_rd ? "yes" : "no", want_wr ? "yes" : "no");
            s_log(LOG_ERR, "socket input buffer: %d byte(s), "
                "ssl input buffer: %d byte(s)", c->sock_ptr, c->ssl_ptr);
            s_log(LOG_ERR, "check_SSL_pending=%d, ssl_closing=%d",
                check_SSL_pending, ssl_closing);
            return -1;
        }

    } while(sock_wr || ssl_closing!=CL_CLOSED);

    return 0; /* OK */
}