Beispiel #1
0
WATCHDOG *watchdog_create(unsigned timeout, WATCHDOG_FN action, char *context)
{
    const char *myname = "watchdog_create";
    struct sigaction sig_action;
    WATCHDOG *wp;

    wp = (WATCHDOG *) mymalloc(sizeof(*wp));
    if ((wp->timeout = timeout / WATCHDOG_STEPS) == 0)
	msg_panic("%s: timeout %d is too small", myname, timeout);
    wp->action = action;
    wp->context = context;
    wp->saved_watchdog = watchdog_curr;
    wp->saved_time = alarm(0);
    sigemptyset(&sig_action.sa_mask);
#ifdef SA_RESTART
    sig_action.sa_flags = SA_RESTART;
#else
    sig_action.sa_flags = 0;
#endif
    sig_action.sa_handler = watchdog_event;
    if (sigaction(SIGALRM, &sig_action, &wp->saved_action) < 0)
	msg_fatal("%s: sigaction(SIGALRM): %m", myname);
    if (msg_verbose > 1)
	msg_info("%s: %p %d", myname, (void *) wp, timeout);
#ifdef USE_WATCHDOG_PIPE
    if (watchdog_curr == 0) {
	if (pipe(watchdog_pipe) < 0)
	    msg_fatal("%s: pipe: %m", myname);
	non_blocking(watchdog_pipe[0], NON_BLOCKING);
	non_blocking(watchdog_pipe[1], NON_BLOCKING);
	event_enable_read(watchdog_pipe[0], watchdog_read, (void *) 0);
    }
#endif
    return (watchdog_curr = wp);
}
Beispiel #2
0
int     inet_connect(const char *addr, int block_mode, int timeout)
{
    char   *buf;
    char   *host;
    char   *port;
    struct sockaddr_in sin;
    int     sock;

    /*
     * Translate address information to internal form. No host defaults to
     * the local host.
     */
    buf = inet_parse(addr, &host, &port);
    if (*host == 0)
	host = "localhost";
    memset((char *) &sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = find_inet_addr(host);
    sin.sin_port = find_inet_port(port, "tcp");
    myfree(buf);

    /*
     * Create a client socket.
     */
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	msg_fatal("socket: %m");

    /*
     * Timed connect.
     */
    if (timeout > 0) {
	non_blocking(sock, NON_BLOCKING);
	if (timed_connect(sock, (struct sockaddr *) & sin, sizeof(sin), timeout) < 0) {
	    close(sock);
	    return (-1);
	}
	if (block_mode != NON_BLOCKING)
	    non_blocking(sock, block_mode);
	return (sock);
    }

    /*
     * Maybe block until connected.
     */
    else {
	non_blocking(sock, block_mode);
	if (sane_connect(sock, (struct sockaddr *) & sin, sizeof(sin)) < 0
	    && errno != EINPROGRESS) {
	    close(sock);
	    return (-1);
	}
	return (sock);
    }
}
Beispiel #3
0
int     unix_connect(const char *addr, int block_mode, int timeout)
{
#undef sun
    struct sockaddr_un sun;
    int     len = strlen(addr);
    int     sock;

    /*
     * Translate address information to internal form.
     */
    if (len >= (int) sizeof(sun.sun_path))
	msg_fatal("unix-domain name too long: %s", addr);
    memset((char *) &sun, 0, sizeof(sun));
    sun.sun_family = AF_UNIX;
#ifdef HAS_SUN_LEN
    sun.sun_len = len + 1;
#endif
    memcpy(sun.sun_path, addr, len + 1);

    /*
     * Create a client socket.
     */
    if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
	return (-1);

    /*
     * Timed connect.
     */
    if (timeout > 0) {
	non_blocking(sock, NON_BLOCKING);
	if (timed_connect(sock, (struct sockaddr *) & sun, sizeof(sun), timeout) < 0) {
	    close(sock);
	    return (-1);
	}
	if (block_mode != NON_BLOCKING)
	    non_blocking(sock, block_mode);
	return (sock);
    }

    /*
     * Maybe block until connected.
     */
    else {
	non_blocking(sock, block_mode);
	if (sane_connect(sock, (struct sockaddr *) & sun, sizeof(sun)) < 0
	    && errno != EINPROGRESS) {
	    close(sock);
	    return (-1);
	}
	return (sock);
    }
}
Beispiel #4
0
static SMTP_SESSION *smtp_connect_sock(int sock, struct sockaddr *sa,
				               int salen,
				               SMTP_ITERATOR *iter,
				               DSN_BUF *why,
				               int sess_flags)
{
    int     conn_stat;
    int     saved_errno;
    VSTREAM *stream;
    time_t  start_time;
    const char *name = STR(iter->host);
    const char *addr = STR(iter->addr);
    unsigned port = iter->port;

    start_time = time((time_t *) 0);
    if (var_smtp_conn_tmout > 0) {
	non_blocking(sock, NON_BLOCKING);
	conn_stat = timed_connect(sock, sa, salen, var_smtp_conn_tmout);
	saved_errno = errno;
	non_blocking(sock, BLOCKING);
	errno = saved_errno;
    } else {
	conn_stat = sane_connect(sock, sa, salen);
    }
    if (conn_stat < 0) {
	if (port)
	    dsb_simple(why, "4.4.1", "connect to %s[%s]:%d: %m",
		       name, addr, ntohs(port));
	else
	    dsb_simple(why, "4.4.1", "connect to %s[%s]: %m", name, addr);
	close(sock);
	return (0);
    }
    stream = vstream_fdopen(sock, O_RDWR);

    /*
     * Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE.
     */
    if (sa->sa_family == AF_INET
#ifdef AF_INET6
	|| sa->sa_family == AF_INET6
#endif
	)
	vstream_tweak_tcp(stream);

    /*
     * Bundle up what we have into a nice SMTP_SESSION object.
     */
    return (smtp_session_alloc(stream, iter, start_time, sess_flags));
}
Beispiel #5
0
    size_t write_some( AsyncWriteStream& s, const ConstBufferSequence& buf ) {
        detail::non_blocking<AsyncWriteStream> non_blocking;

        if( non_blocking(s) || non_blocking(s,true) ) {
            boost::system::error_code ec;
            size_t r = s.write_some( buf, ec );
            if( !ec ) return r;
            if( ec != boost::asio::error::would_block) {
                BOOST_THROW_EXCEPTION( boost::system::system_error(ec) );
            }
        }
        promise<size_t>::ptr p(new promise<size_t>("fc::asio::write_some"));
        s.async_write_some( buf, boost::bind( detail::read_write_handler, p, _1, _2 ) );
        return p->wait();
    }
Beispiel #6
0
int     stream_connect(const char *path, int block_mode, int unused_timeout)
{
#ifdef STREAM_CONNECTIONS
    const char *myname = "stream_connect";
    int     pair[2];
    int     fifo;

    /*
     * The requested file system object must exist, otherwise we can't reach
     * the server.
     */
    if ((fifo = open(path, O_WRONLY | O_NONBLOCK, 0)) < 0)
	return (-1);

    /*
     * This is for {unix,inet}_connect() compatibility.
     */
    if (block_mode == BLOCKING)
	non_blocking(fifo, BLOCKING);

    /*
     * Create a pipe, and send one pipe end to the server.
     */
    if (pipe(pair) < 0)
	msg_fatal("%s: pipe: %m", myname);
    if (ioctl(fifo, I_SENDFD, pair[1]) < 0)
	msg_fatal("%s: send file descriptor: %m", myname);
    close(pair[1]);

    /*
     * This is for {unix,inet}_connect() compatibility.
     */
    if (block_mode == NON_BLOCKING)
	non_blocking(pair[0], NON_BLOCKING);

    /*
     * Cleanup.
     */
    close(fifo);

    /*
     * Keep the other end of the pipe.
     */
    return (pair[0]);
#else
    msg_fatal("stream connections are not implemented");
#endif
}
Beispiel #7
0
static void psc_service(VSTREAM *smtp_client_stream,
			        char *unused_service,
			        char **unused_argv)
{

    /*
     * For sanity, require that at least one of INET or INET6 is enabled.
     * Otherwise, we can't look up interface information, and we can't
     * convert names or addresses.
     */
    if (inet_proto_info()->ai_family_list[0] == 0)
	msg_fatal("all network protocols are disabled (%s = %s)",
		  VAR_INET_PROTOCOLS, var_inet_protocols);

    /*
     * This program handles all incoming connections, so it must not block.
     * We use event-driven code for all operations that introduce latency.
     * 
     * Note: instead of using VSTREAM-level timeouts, we enforce limits on the
     * total amount of time to receive a complete SMTP command line.
     */
    non_blocking(vstream_fileno(smtp_client_stream), NON_BLOCKING);

    /*
     * Look up the remote SMTP client address and port.
     */
    psc_endpt_lookup(smtp_client_stream, psc_endpt_lookup_done);
}
Beispiel #8
0
static void tlsp_service(VSTREAM *plaintext_stream,
                         char *service,
                         char **argv)
{
    TLSP_STATE *state;
    int     plaintext_fd = vstream_fileno(plaintext_stream);

    /*
     * Sanity check. This service takes no command-line arguments.
     */
    if (argv[0])
        msg_fatal("unexpected command-line argument: %s", argv[0]);

    /*
     * This program handles multiple connections, so it must not block. We
     * use event-driven code for all operations that introduce latency.
     * Except that attribute lists are sent/received synchronously, once the
     * socket is found to be ready for transmission.
     */
    non_blocking(plaintext_fd, NON_BLOCKING);
    vstream_control(plaintext_stream,
                    CA_VSTREAM_CTL_PATH("plaintext"),
                    CA_VSTREAM_CTL_TIMEOUT(5),
                    CA_VSTREAM_CTL_END);

    /*
     * Receive postscreen's remote SMTP client address/port and socket.
     */
    state = tlsp_state_create(service, plaintext_stream);
    event_enable_read(plaintext_fd, tlsp_get_request_event, (void *) state);
    event_request_timer(tlsp_get_request_event, (void *) state,
                        TLSP_INIT_TIMEOUT);
}
Beispiel #9
0
static void start_connect(SESSION *session)
{
    int     fd;
    struct linger linger;

    /*
     * Some systems don't set the socket error when connect() fails early
     * (loopback) so we must deal with the error immediately, rather than
     * retrieving it later with getsockopt(). We can't use MSG_PEEK to
     * distinguish between server disconnect and connection refused.
     */
    if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0)
        msg_fatal("socket: %m");
    (void) non_blocking(fd, NON_BLOCKING);
    linger.l_onoff = 1;
    linger.l_linger = 0;
    if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &linger,
                   sizeof(linger)) < 0)
        msg_warn("setsockopt SO_LINGER %d: %m", linger.l_linger);
    session->stream = vstream_fdopen(fd, O_RDWR);
    event_enable_write(fd, connect_done, (char *) session);
    smtp_timeout_setup(session->stream, var_timeout);
    if (inet_windowsize > 0)
        set_inet_windowsize(fd, inet_windowsize);
    if (sane_connect(fd, sa, sa_length) < 0 && errno != EINPROGRESS)
        fail_connect(session);
}
Beispiel #10
0
static void connect_event(int unused_event, void *context)
{
    int     sock = CAST_ANY_PTR_TO_INT(context);
    struct sockaddr_storage ss;
    SOCKADDR_SIZE len = sizeof(ss);
    struct sockaddr *sa = (struct sockaddr *) &ss;
    SINK_STATE *state;
    int     fd;

    if ((fd = accept(sock, sa, &len)) >= 0) {
	if (msg_verbose)
	    msg_info("connect (%s)",
#ifdef AF_LOCAL
		     sa->sa_family == AF_LOCAL ? "AF_LOCAL" :
#else
		     sa->sa_family == AF_UNIX ? "AF_UNIX" :
#endif
		     sa->sa_family == AF_INET ? "AF_INET" :
#ifdef AF_INET6
		     sa->sa_family == AF_INET6 ? "AF_INET6" :
#endif
		     "unknown protocol family");
	non_blocking(fd, NON_BLOCKING);
	state = (SINK_STATE *) mymalloc(sizeof(*state));
	state->stream = vstream_fdopen(fd, O_RDWR);
	vstream_tweak_sock(state->stream);
	netstring_setup(state->stream, var_tmout);
	event_enable_read(fd, read_length, (void *) state);
    }
}
Beispiel #11
0
static void connect_done(int unused_event, char *context)
{
    SESSION *session = (SESSION *) context;
    int     fd = vstream_fileno(session->stream);

    /*
     * Try again after some delay when the connection failed, in case they
     * run a Mickey Mouse protocol stack.
     */
    if (socket_error(fd) < 0) {
        fail_connect(session);
    } else {
        non_blocking(fd, BLOCKING);
        /* Disable write events. */
        event_disable_readwrite(fd);
        event_enable_read(fd, read_banner, (char *) session);
        dequeue_connect(session);
        /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */
        if (sa->sa_family == AF_INET
#ifdef AF_INET6
                || sa->sa_family == AF_INET6
#endif
           )
            vstream_tweak_tcp(session->stream);
    }
}
Beispiel #12
0
int     fifo_listen(const char *path, int permissions, int block_mode)
{
    char    buf[BUF_LEN];
    static int open_mode = 0;
    char   *myname = "fifo_listen";
    struct stat st;
    int     fd;
    int     count;

    /*
     * Create a named pipe (fifo). Do whatever we can so we don't run into
     * trouble when this process is restarted after crash.  Make sure that we
     * open a fifo and not something else, then change permissions to what we
     * wanted them to be, because mkfifo() is subject to umask settings.
     * Instead we could zero the umask temporarily before creating the FIFO,
     * but that would cost even more system calls. Figure out if the fifo
     * needs to be opened O_RDWR or O_RDONLY. Some systems need one, some
     * need the other. If we choose the wrong mode, the fifo will stay
     * readable, causing the program to go into a loop.
     */
    if (unlink(path) && errno != ENOENT)
	msg_fatal("%s: remove %s: %m", myname, path);
    if (mkfifo(path, permissions) < 0)
	msg_fatal("%s: create fifo %s: %m", myname, path);
    switch (open_mode) {
    case 0:
	if ((fd = open(path, O_RDWR | O_NONBLOCK, 0)) < 0)
	    msg_fatal("%s: open %s: %m", myname, path);
	if (readable(fd) == 0) {
	    open_mode = O_RDWR | O_NONBLOCK;
	    break;
	} else {
	    open_mode = O_RDONLY | O_NONBLOCK;
	    if (msg_verbose)
		msg_info("open O_RDWR makes fifo readable - trying O_RDONLY");
	    (void) close(fd);
	    /* FALLTRHOUGH */
	}
    default:
	if ((fd = open(path, open_mode, 0)) < 0)
	    msg_fatal("%s: open %s: %m", myname, path);
	break;
    }

    /*
     * Make sure we opened a FIFO and skip any cruft that might have
     * accumulated before we opened it.
     */
    if (fstat(fd, &st) < 0)
	msg_fatal("%s: fstat %s: %m", myname, path);
    if (S_ISFIFO(st.st_mode) == 0)
	msg_fatal("%s: not a fifo: %s", myname, path);
    if (fchmod(fd, permissions) < 0)
	msg_fatal("%s: fchmod %s: %m", myname, path);
    non_blocking(fd, block_mode);
    while ((count = peekfd(fd)) > 0
	   && read(fd, buf, BUF_LEN < count ? BUF_LEN : count) > 0)
	 /* void */ ;
    return (fd);
}
Beispiel #13
0
int     inet_listen(const char *addr, int backlog, int block_mode)
{
    struct sockaddr_in sin;
    int     sock;
    int     t = 1;
    char   *buf;
    char   *host;
    char   *port;

    /*
     * Translate address information to internal form.
     */
    buf = inet_parse(addr, &host, &port);
    memset((char *) &sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = find_inet_port(port, "tcp");
    sin.sin_addr.s_addr = (*host ? find_inet_addr(host) : INADDR_ANY);
    myfree(buf);

    /*
     * Create a listener socket.
     */
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	msg_fatal("socket: %m");
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &t, sizeof(t)) < 0)
	msg_fatal("setsockopt: %m");
    if (bind(sock, (struct sockaddr *) & sin, sizeof(sin)) < 0)
	msg_fatal("bind %s port %d: %m", sin.sin_addr.s_addr == INADDR_ANY ?
	       "INADDR_ANY" : inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
    non_blocking(sock, block_mode);
    if (listen(sock, backlog) < 0)
	msg_fatal("listen: %m");
    return (sock);
}
Beispiel #14
0
    size_t read_some( AsyncReadStream& s, const MutableBufferSequence& buf ) {
        detail::non_blocking<AsyncReadStream> non_blocking;

        // TODO: determine if non_blocking query results in a system call that
        // will slow down every read... 
        if( non_blocking(s) || non_blocking(s,true) ) {
            boost::system::error_code ec;
            size_t r = s.read_some( buf, ec );
            if( !ec ) return r;
            if( ec != boost::asio::error::would_block ) 
                  BOOST_THROW_EXCEPTION( boost::system::system_error(ec) );
        }
        
        promise<size_t>::ptr p(new promise<size_t>("fc::asio::read_some"));
        s.async_read_some( buf, boost::bind( detail::read_write_handler, p, _1, _2 ) );
        return p->wait();
    }
Beispiel #15
0
void    master_sigsetup(void)
{
    const char *myname = "master_sigsetup";
    struct sigaction action;
    static int sigs[] = {
	SIGINT, SIGQUIT, SIGILL, SIGBUS, SIGSEGV, SIGTERM,
    };
    unsigned i;

    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;

    /*
     * Prepare to kill our children when we receive any of the above signals.
     */
    action.sa_handler = master_sigdeath;
    for (i = 0; i < sizeof(sigs) / sizeof(sigs[0]); i++)
	if (sigaction(sigs[i], &action, (struct sigaction *) 0) < 0)
	    msg_fatal("%s: sigaction(%d): %m", myname, sigs[i]);

#ifdef USE_SIG_PIPE
    if (pipe(master_sig_pipe))
	msg_fatal("pipe: %m");
    non_blocking(SIG_PIPE_WRITE_FD, NON_BLOCKING);
    non_blocking(SIG_PIPE_READ_FD, NON_BLOCKING);
    close_on_exec(SIG_PIPE_WRITE_FD, CLOSE_ON_EXEC);
    close_on_exec(SIG_PIPE_READ_FD, CLOSE_ON_EXEC);
    event_enable_read(SIG_PIPE_READ_FD, master_sig_event, (void *) 0);
#endif

    /*
     * Intercept SIGHUP (re-read config file) and SIGCHLD (child exit).
     */
#ifdef SA_RESTART
    action.sa_flags |= SA_RESTART;
#endif
    action.sa_handler = master_sighup;
    if (sigaction(SIGHUP, &action, (struct sigaction *) 0) < 0)
	msg_fatal("%s: sigaction(%d): %m", myname, SIGHUP);

    action.sa_flags |= SA_NOCLDSTOP;
    action.sa_handler = master_sigchld;
    if (sigaction(SIGCHLD, &action, (struct sigaction *) 0) < 0)
	msg_fatal("%s: sigaction(%d): %m", myname, SIGCHLD);
}
Beispiel #16
0
static void tlsp_get_fd_event(int event, void *context)
{
    const char *myname = "tlsp_get_fd_event";
    TLSP_STATE *state = (TLSP_STATE *) context;
    int     plaintext_fd = vstream_fileno(state->plaintext_stream);

    /*
     * At this point we still manually manage plaintext read/write/timeout
     * events. Disable I/O and timer events. Don't assume that the first
     * plaintext request will be a read.
     */
    event_disable_readwrite(plaintext_fd);
    if (event != EVENT_TIME)
        event_cancel_timer(tlsp_get_fd_event, (void *) state);
    else
        errno = ETIMEDOUT;

    /*
     * Initialize plaintext-related session state.  Once we have this behind
     * us, the TLSP_STATE destructor will automagically clean up requests for
     * read/write/timeout events, which makes error recovery easier.
     *
     * Register the plaintext event handler for timer cleanup in the TLSP_STATE
     * destructor. Insert the NBBIO event-driven I/O layer between the
     * postscreen(8) server and the TLS engine.
     */
    if (event != EVENT_READ
            || (state->ciphertext_fd = LOCAL_RECV_FD(plaintext_fd)) < 0) {
        msg_warn("%s: receive SMTP client file descriptor: %m", myname);
        tlsp_state_free(state);
        return;
    }
    non_blocking(state->ciphertext_fd, NON_BLOCKING);
    state->ciphertext_timer = tlsp_ciphertext_event;
    state->plaintext_buf = nbbio_create(plaintext_fd,
                                        VSTREAM_BUFSIZE, "postscreen",
                                        tlsp_plaintext_event,
                                        (void *) state);

    /*
     * Perform the TLS layer before-handshake initialization. We perform the
     * remainder after the TLS handshake completes.
     */
    tlsp_start_tls(state);

    /*
     * Trigger the initial proxy server I/Os.
     */
    tlsp_strategy(state);
}
Beispiel #17
0
int main(int argc, char **argv) {
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    // Queue of lambda expressions modifying the world object
    auto worldQueue = std::make_shared<std::queue<std::function<void()>>>();

    // Mutex controlling pushes to the end of the world queue
    auto worldQueueMutex = std::make_shared<std::mutex>();

    // Queue of buffers received by external hosts
    auto incomingBufferQueue = std::make_shared<std::queue<std::pair<boost::asio::ip::udp::endpoint, std::shared_ptr<boost::asio::streambuf>>>>();

    // Queue of buffers waiting to be sent to clients
    auto outgoingBufferQueue = std::make_shared<std::queue<std::pair<boost::asio::ip::udp::endpoint, std::shared_ptr<boost::asio::streambuf>>>>();

    // Controls send/receive cycle of server
    auto socketMutex = std::make_shared<std::mutex>();

    // Set up socket for network communication
    boost::asio::io_service ioService;
    auto socket = std::make_shared<boost::asio::ip::udp::socket>(ioService, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), PORT_NUMBER));
    socket->non_blocking(true);

    unsigned int threadCount = std::thread::hardware_concurrency();
    if (threadCount == 0) {
        std::cout << "Unable to detect the number of threads supported." << std::endl;
        threadCount = 4;
    }

    std::cout << "Thread count: " << threadCount << std::endl;

    // Starts listener thread
    //std::thread socketListener(listenOnSocket, incomingBufferQueue, socketMutex, socket);
    //std::thread socketSender(sendOnSocket, outgoingBufferQueue, socketMutex, socket);

    threadCount -= 2;

    // Starts worker threads
    // TODO: add mutexes and stuff for worker threads
    std::vector<std::thread> workers;
    //for (int i = 0; i < threadCount - 1; i++)
        //workers.emplace_back(processMessages);

    //socketListener.join();
    //socketSender.join();
}
Beispiel #18
0
static void multi_server_wakeup(int fd, HTABLE *attr)
{
    VSTREAM *stream;
    char   *tmp;

#if defined(F_DUPFD) && (EVENTS_STYLE != EVENTS_STYLE_SELECT)
#ifndef THRESHOLD_FD_WORKAROUND
#define THRESHOLD_FD_WORKAROUND 128
#endif
    int     new_fd;

    /*
     * Leave some handles < FD_SETSIZE for DBMS libraries, in the unlikely
     * case of a multi-server with a thousand clients.
     */
    if (fd < THRESHOLD_FD_WORKAROUND) {
	if ((new_fd = fcntl(fd, F_DUPFD, THRESHOLD_FD_WORKAROUND)) < 0)
	    msg_fatal("fcntl F_DUPFD: %m");
	(void) close(fd);
	fd = new_fd;
    }
#endif
    if (msg_verbose)
	msg_info("connection established fd %d", fd);
    non_blocking(fd, BLOCKING);
    close_on_exec(fd, CLOSE_ON_EXEC);
    client_count++;
    stream = vstream_fdopen(fd, O_RDWR);
    tmp = concatenate(multi_server_name, " socket", (char *) 0);
    vstream_control(stream,
                    VSTREAM_CTL_PATH, tmp,
                    VSTREAM_CTL_CONTEXT, (char *) attr,
                    VSTREAM_CTL_END);
    myfree(tmp);
    timed_ipc_setup(stream);
    multi_server_saved_flags = vstream_flags(stream);
    if (multi_server_in_flow_delay && mail_flow_get(1) < 0)
	event_request_timer(multi_server_enable_read, (char *) stream,
			    var_in_flow_delay);
    else
	multi_server_enable_read(0, (char *) stream);
}
Beispiel #19
0
static void single_server_wakeup(int fd, HTABLE *attr)
{
    VSTREAM *stream;
    char   *tmp;

    /*
     * If the accept() succeeds, be sure to disable non-blocking I/O, because
     * the application is supposed to be single-threaded. Notice the master
     * of our (un)availability to service connection requests. Commit suicide
     * when the master process disconnected from us. Don't drop the already
     * accepted client request after "postfix reload"; that would be rude.
     */
    if (msg_verbose)
	msg_info("connection established");
    non_blocking(fd, BLOCKING);
    close_on_exec(fd, CLOSE_ON_EXEC);
    stream = vstream_fdopen(fd, O_RDWR);
    tmp = concatenate(single_server_name, " socket", (char *) 0);
    vstream_control(stream,
		    CA_VSTREAM_CTL_PATH(tmp),
		    CA_VSTREAM_CTL_CONTEXT((void *) attr),
		    CA_VSTREAM_CTL_END);
    myfree(tmp);
    timed_ipc_setup(stream);
    if (master_notify(var_pid, single_server_generation, MASTER_STAT_TAKEN) < 0)
	 /* void */ ;
    if (single_server_in_flow_delay && mail_flow_get(1) < 0)
	doze(var_in_flow_delay * 1000000);
    single_server_service(stream, single_server_name, single_server_argv);
    (void) vstream_fclose(stream);
    if (master_notify(var_pid, single_server_generation, MASTER_STAT_AVAIL) < 0)
	single_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT);
    if (msg_verbose)
	msg_info("connection closed");
    /* Avoid integer wrap-around in a persistent process.  */
    if (use_count < INT_MAX)
	use_count++;
    if (var_idle_limit > 0)
	event_request_timer(single_server_timeout, (void *) 0, var_idle_limit);
    if (attr)
	htable_free(attr, myfree);
}
Beispiel #20
0
int     unix_listen(const char *addr, int backlog, int block_mode)
{
#undef sun
    struct sockaddr_un sun;
    int     len = strlen(addr);
    int     sock;

    /*
     * Translate address information to internal form.
     */
    if (len >= (int) sizeof(sun.sun_path))
	msg_fatal("unix-domain name too long: %s", addr);
    memset((char *) &sun, 0, sizeof(sun));
    sun.sun_family = AF_UNIX;
#ifdef HAS_SUN_LEN
    sun.sun_len = len + 1;
#endif
    memcpy(sun.sun_path, addr, len + 1);

    /*
     * Create a listener socket. Do whatever we can so we don't run into
     * trouble when this process is restarted after crash.
     */
    if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
	msg_fatal("socket: %m");
    if (unlink(addr) < 0 && errno != ENOENT)
	msg_fatal("remove %s: %m", addr);
    if (bind(sock, (struct sockaddr *) & sun, sizeof(sun)) < 0)
	msg_fatal("bind: %s: %m", addr);
#ifdef FCHMOD_UNIX_SOCKETS
    if (fchmod(sock, 0666) < 0)
	msg_fatal("fchmod socket %s: %m", addr);
#else
    if (chmod(addr, 0666) < 0)
	msg_fatal("chmod socket %s: %m", addr);
#endif
    non_blocking(sock, block_mode);
    if (listen(sock, backlog) < 0)
	msg_fatal("listen: %m");
    return (sock);
}
Beispiel #21
0
static void qmgr_transport_event(int unused_event, void *context)
{
    QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context;

    /*
     * This routine notifies the application when the request given to
     * qmgr_transport_alloc() completes.
     */
    if (msg_verbose)
	msg_info("transport_event: %s", alloc->transport->name);

    /*
     * Connection request completed. Stop the watchdog timer.
     */
    event_cancel_timer(qmgr_transport_abort, context);

    /*
     * Disable further read events that end up calling this function, and
     * free up this pending connection pipeline slot.
     */
    if (alloc->stream) {
	event_disable_readwrite(vstream_fileno(alloc->stream));
	non_blocking(vstream_fileno(alloc->stream), BLOCKING);
    }
    alloc->transport->pending -= 1;

    /*
     * Notify the requestor.
     */
    if (alloc->transport->xport_rate_delay > 0) {
	if ((alloc->transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) == 0)
	    msg_panic("transport_event: missing rate lock for transport %s",
		      alloc->transport->name);
	event_request_timer(qmgr_transport_rate_event, (void *) alloc,
			    alloc->transport->xport_rate_delay);
    } else {
	alloc->notify(alloc->transport, alloc->stream);
	myfree((void *) alloc);
    }
}
Beispiel #22
0
int     stream_pass_connect(const char *path, int block_mode, int unused_timeout)
{
#ifdef STREAM_CONNECTIONS
    const char *myname = "stream_pass_connect";
    int     fifo;

    /*
     * The requested file system object must exist, otherwise we can't reach
     * the server.
     */
    if ((fifo = open(path, O_WRONLY | O_NONBLOCK, 0)) < 0)
	return (-1);

    /*
     * This is for {unix,inet}_connect() compatibility.
     */
    non_blocking(fifo, block_mode);

    return (fifo);
#else
    msg_fatal("stream connections are not implemented");
#endif
}
Beispiel #23
0
void    master_status_init(MASTER_SERV *serv)
{
    const char *myname = "master_status_init";

    /*
     * Sanity checks.
     */
    if (serv->status_fd[0] >= 0 || serv->status_fd[1] >= 0)
	msg_panic("%s: status events already enabled", myname);
    if (msg_verbose)
	msg_info("%s: %s", myname, serv->name);

    /*
     * Make the read end of this service's status pipe non-blocking so that
     * we can detect partial writes on the child side. We use a duplex pipe
     * so that the child side becomes readable when the master goes away.
     */
    if (duplex_pipe(serv->status_fd) < 0)
	msg_fatal("pipe: %m");
    non_blocking(serv->status_fd[0], BLOCKING);
    close_on_exec(serv->status_fd[0], CLOSE_ON_EXEC);
    close_on_exec(serv->status_fd[1], CLOSE_ON_EXEC);
    event_enable_read(serv->status_fd[0], master_status_event, (char *) serv);
}
Beispiel #24
0
 /*
  * This is the actual startup routine for the connection. We expect that the
  * buffers are flushed and the "220 Ready to start TLS" was received by us,
  * so that we can immediately start the TLS handshake process.
  */
TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props)
{
    int     sts;
    int     protomask;
    const char *cipher_list;
    SSL_SESSION *session = 0;
    SSL_CIPHER_const SSL_CIPHER *cipher;
    X509   *peercert;
    TLS_SESS_STATE *TLScontext;
    TLS_APPL_STATE *app_ctx = props->ctx;
    char   *myserverid;
    int     log_mask = app_ctx->log_mask;

    /*
     * When certificate verification is required, log trust chain validation
     * errors even when disabled by default for opportunistic sessions. For
     * DANE this only applies when using trust-anchor associations.
     */
    if (TLS_MUST_TRUST(props->tls_level)
      && (!TLS_DANE_BASED(props->tls_level) || TLS_DANE_HASTA(props->dane)))
	log_mask |= TLS_LOG_UNTRUSTED;

    if (log_mask & TLS_LOG_VERBOSE)
	msg_info("setting up TLS connection to %s", props->namaddr);

    /*
     * First make sure we have valid protocol and cipher parameters
     * 
     * Per-session protocol restrictions must be applied to the SSL connection,
     * as restrictions in the global context cannot be cleared.
     */
    protomask = tls_protocol_mask(props->protocols);
    if (protomask == TLS_PROTOCOL_INVALID) {
	/* tls_protocol_mask() logs no warning. */
	msg_warn("%s: Invalid TLS protocol list \"%s\": aborting TLS session",
		 props->namaddr, props->protocols);
	return (0);
    }
    /* DANE requires SSLv3 or later, not SSLv2. */
    if (TLS_DANE_BASED(props->tls_level))
	protomask |= TLS_PROTOCOL_SSLv2;

    /*
     * Per session cipher selection for sessions with mandatory encryption
     * 
     * The cipherlist is applied to the global SSL context, since it is likely
     * to stay the same between connections, so we make use of a 1-element
     * cache to return the same result for identical inputs.
     */
    cipher_list = tls_set_ciphers(app_ctx, "TLS", props->cipher_grade,
				  props->cipher_exclusions);
    if (cipher_list == 0) {
	msg_warn("%s: %s: aborting TLS session",
		 props->namaddr, vstring_str(app_ctx->why));
	return (0);
    }
    if (log_mask & TLS_LOG_VERBOSE)
	msg_info("%s: TLS cipher list \"%s\"", props->namaddr, cipher_list);

    /*
     * OpenSSL will ignore cached sessions that use the wrong protocol. So we
     * do not need to filter out cached sessions with the "wrong" protocol,
     * rather OpenSSL will simply negotiate a new session.
     * 
     * We salt the session lookup key with the protocol list, so that sessions
     * found in the cache are plausibly acceptable.
     * 
     * By the time a TLS client is negotiating ciphers it has already offered to
     * re-use a session, it is too late to renege on the offer. So we must
     * not attempt to re-use sessions whose ciphers are too weak. We salt the
     * session lookup key with the cipher list, so that sessions found in the
     * cache are always acceptable.
     * 
     * With DANE, (more generally any TLScontext where we specified explicit
     * trust-anchor or end-entity certificates) the verification status of
     * the SSL session depends on the specified list.  Since we verify the
     * certificate only during the initial handshake, we must segregate
     * sessions with different TA lists.  Note, that TA re-verification is
     * not possible with cached sessions, since these don't hold the complete
     * peer trust chain.  Therefore, we compute a digest of the sorted TA
     * parameters and append it to the serverid.
     */
    myserverid = tls_serverid_digest(props, protomask, cipher_list);

    /*
     * Allocate a new TLScontext for the new connection and get an SSL
     * structure. Add the location of TLScontext to the SSL to later retrieve
     * the information inside the tls_verify_certificate_callback().
     * 
     * If session caching was enabled when TLS was initialized, the cache type
     * is stored in the client SSL context.
     */
    TLScontext = tls_alloc_sess_context(log_mask, props->namaddr);
    TLScontext->cache_type = app_ctx->cache_type;

    TLScontext->serverid = myserverid;
    TLScontext->stream = props->stream;
    TLScontext->mdalg = props->mdalg;

    /* Alias DANE digest info from props */
    TLScontext->dane = props->dane;

    if ((TLScontext->con = SSL_new(app_ctx->ssl_ctx)) == NULL) {
	msg_warn("Could not allocate 'TLScontext->con' with SSL_new()");
	tls_print_errors();
	tls_free_context(TLScontext);
	return (0);
    }
    if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) {
	msg_warn("Could not set application data for 'TLScontext->con'");
	tls_print_errors();
	tls_free_context(TLScontext);
	return (0);
    }

    /*
     * Apply session protocol restrictions.
     */
    if (protomask != 0)
	SSL_set_options(TLScontext->con, TLS_SSL_OP_PROTOMASK(protomask));

    /*
     * XXX To avoid memory leaks we must always call SSL_SESSION_free() after
     * calling SSL_set_session(), regardless of whether or not the session
     * will be reused.
     */
    if (TLScontext->cache_type) {
	session = load_clnt_session(TLScontext);
	if (session) {
	    SSL_set_session(TLScontext->con, session);
	    SSL_SESSION_free(session);		/* 200411 */
	}
    }
#ifdef TLSEXT_MAXLEN_host_name
    if (TLS_DANE_BASED(props->tls_level)
	&& strlen(props->host) <= TLSEXT_MAXLEN_host_name) {

	/*
	 * With DANE sessions, send an SNI hint.  We don't care whether the
	 * server reports finding a matching certificate or not, so no
	 * callback is required to process the server response.  Our use of
	 * SNI is limited to giving servers that are (mis)configured to use
	 * SNI the best opportunity to find the certificate they promised via
	 * the associated TLSA RRs.  (Generally, server administrators should
	 * avoid SNI, and there are no plans to support SNI in the Postfix
	 * SMTP server).
	 * 
	 * Since the hostname is DNSSEC-validated, it must be a DNS FQDN and
	 * thererefore valid for use with SNI.  Failure to set a valid SNI
	 * hostname is a memory allocation error, and thus transient.  Since
	 * we must not cache the session if we failed to send the SNI name,
	 * we have little choice but to abort.
	 */
	if (!SSL_set_tlsext_host_name(TLScontext->con, props->host)) {
	    msg_warn("%s: error setting SNI hostname to: %s", props->namaddr,
		     props->host);
	    tls_free_context(TLScontext);
	    return (0);
	}
	if (log_mask & TLS_LOG_DEBUG)
	    msg_info("%s: SNI hostname: %s", props->namaddr, props->host);
    }
#endif

    /*
     * Before really starting anything, try to seed the PRNG a little bit
     * more.
     */
    tls_int_seed();
    (void) tls_ext_seed(var_tls_daemon_rand_bytes);

    /*
     * Initialize the SSL connection to connect state. This should not be
     * necessary anymore since 0.9.3, but the call is still in the library
     * and maintaining compatibility never hurts.
     */
    SSL_set_connect_state(TLScontext->con);

    /*
     * Connect the SSL connection with the network socket.
     */
    if (SSL_set_fd(TLScontext->con, vstream_fileno(props->stream)) != 1) {
	msg_info("SSL_set_fd error to %s", props->namaddr);
	tls_print_errors();
	uncache_session(app_ctx->ssl_ctx, TLScontext);
	tls_free_context(TLScontext);
	return (0);
    }

    /*
     * Turn on non-blocking I/O so that we can enforce timeouts on network
     * I/O.
     */
    non_blocking(vstream_fileno(props->stream), NON_BLOCKING);

    /*
     * If the debug level selected is high enough, all of the data is dumped:
     * TLS_LOG_TLSPKTS will dump the SSL negotiation, TLS_LOG_ALLPKTS will
     * dump everything.
     * 
     * We do have an SSL_set_fd() and now suddenly a BIO_ routine is called?
     * Well there is a BIO below the SSL routines that is automatically
     * created for us, so we can use it for debugging purposes.
     */
    if (log_mask & TLS_LOG_TLSPKTS)
	BIO_set_callback(SSL_get_rbio(TLScontext->con), tls_bio_dump_cb);

    tls_dane_set_callback(app_ctx->ssl_ctx, TLScontext);

    /*
     * Start TLS negotiations. This process is a black box that invokes our
     * call-backs for certificate verification.
     * 
     * Error handling: If the SSL handhake fails, we print out an error message
     * and remove all TLS state concerning this session.
     */
    sts = tls_bio_connect(vstream_fileno(props->stream), props->timeout,
			  TLScontext);
    if (sts <= 0) {
	if (ERR_peek_error() != 0) {
	    msg_info("SSL_connect error to %s: %d", props->namaddr, sts);
	    tls_print_errors();
	} else if (errno != 0) {
	    msg_info("SSL_connect error to %s: %m", props->namaddr);
	} else {
	    msg_info("SSL_connect error to %s: lost connection",
		     props->namaddr);
	}
	uncache_session(app_ctx->ssl_ctx, TLScontext);
	tls_free_context(TLScontext);
	return (0);
    }
    /* Turn off packet dump if only dumping the handshake */
    if ((log_mask & TLS_LOG_ALLPKTS) == 0)
	BIO_set_callback(SSL_get_rbio(TLScontext->con), 0);

    /*
     * The caller may want to know if this session was reused or if a new
     * session was negotiated.
     */
    TLScontext->session_reused = SSL_session_reused(TLScontext->con);
    if ((log_mask & TLS_LOG_CACHE) && TLScontext->session_reused)
	msg_info("%s: Reusing old session", TLScontext->namaddr);

    /*
     * Do peername verification if requested and extract useful information
     * from the certificate for later use.
     */
    if ((peercert = SSL_get_peer_certificate(TLScontext->con)) != 0) {
	TLScontext->peer_status |= TLS_CERT_FLAG_PRESENT;

	/*
	 * Peer name or fingerprint verification as requested.
	 * Unconditionally set peer_CN, issuer_CN and peer_cert_fprint. Check
	 * fingerprint first, and avoid logging verified as untrusted in the
	 * call to verify_extract_name().
	 */
	verify_extract_print(TLScontext, peercert, props);
	verify_extract_name(TLScontext, peercert, props);

	if (TLScontext->log_mask &
	    (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT))
	    msg_info("%s: subject_CN=%s, issuer_CN=%s, "
		     "fingerprint=%s, pkey_fingerprint=%s", props->namaddr,
		     TLScontext->peer_CN, TLScontext->issuer_CN,
		     TLScontext->peer_cert_fprint,
		     TLScontext->peer_pkey_fprint);
	X509_free(peercert);
    } else {
	TLScontext->issuer_CN = mystrdup("");
	TLScontext->peer_CN = mystrdup("");
	TLScontext->peer_cert_fprint = mystrdup("");
	TLScontext->peer_pkey_fprint = mystrdup("");
    }

    /*
     * Finally, collect information about protocol and cipher for logging
     */
    TLScontext->protocol = SSL_get_version(TLScontext->con);
    cipher = SSL_get_current_cipher(TLScontext->con);
    TLScontext->cipher_name = SSL_CIPHER_get_name(cipher);
    TLScontext->cipher_usebits = SSL_CIPHER_get_bits(cipher,
					     &(TLScontext->cipher_algbits));

    /*
     * The TLS engine is active. Switch to the tls_timed_read/write()
     * functions and make the TLScontext available to those functions.
     */
    tls_stream_start(props->stream, TLScontext);

    /*
     * All the key facts in a single log entry.
     */
    if (log_mask & TLS_LOG_SUMMARY)
	msg_info("%s TLS connection established to %s: %s with cipher %s "
		 "(%d/%d bits)",
		 !TLS_CERT_IS_PRESENT(TLScontext) ? "Anonymous" :
		 TLS_CERT_IS_MATCHED(TLScontext) ? "Verified" :
		 TLS_CERT_IS_TRUSTED(TLScontext) ? "Trusted" : "Untrusted",
	      props->namaddr, TLScontext->protocol, TLScontext->cipher_name,
		 TLScontext->cipher_usebits, TLScontext->cipher_algbits);

    tls_int_seed();

    return (TLScontext);
}
Beispiel #25
0
int AttachGalley(OBJECT hd, OBJECT *inners, OBJECT *suspend_pt)
{ OBJECT hd_index;		/* the index of hd in the enclosing galley   */
  OBJECT hd_inners;		/* inner galleys of hd, if unsized           */
  OBJECT dest;			/* the target @Galley hd empties into        */
  OBJECT dest_index;		/* the index of dest                         */
  OBJECT target;		/* the target indefinite containing dest     */
  OBJECT target_index;		/* the index of target                       */
  OBJECT target_galley;		/* the body of target, made into a galley    */
  OBJECT tg_inners;		/* inner galleys of target_galley            */
  BOOLEAN need_precedes = FALSE;/* true if destination lies before galley    */
  OBJECT recs;			/* list of recursive definite objects        */
  OBJECT link, y = nilobj;	/* for scanning through the components of hd */
  CONSTRAINT c;			/* temporary variable holding a constraint   */
  OBJECT env, n1, tmp, zlink, z, sym;	/* placeholders and temporaries	     */
  BOOLEAN was_sized;		/* true if sized(hd) initially               */
  int dim;			/* the galley direction                      */
  FULL_LENGTH perp_back, perp_fwd;
  OBJECT why, junk;

  debug2(DGA, D, "[ AttachGalley(Galley %s into %s)",
	SymName(actual(hd)), SymName(whereto(hd)));
  ifdebug(DGA, DD, DebugGalley(hd, nilobj, 4));
  assert( Up(hd) != hd, "AttachGalley: no index!" );
  Parent(hd_index, Up(hd));
  assert( type(hd_index) == UNATTACHED, "AttachGalley: not UNATTACHED!" );
  hd_inners = tg_inners = nilobj;
  was_sized = sized(hd);
  dim = gall_dir(hd);

  for(;;)
  {
    /*************************************************************************/
    /*                                                                       */
    /*  Search for a destination for hd.  If hd is unsized, search for       */
    /*  inner galleys preceding it first of all, then for receptive objects  */
    /*  following it, possibly in inner galleys.  If no luck, exit.          */
    /*  If hd is sized, search only for receptive objects in the current     */
    /*  galley below the current spot, and fail if cannot find any.          */
    /*                                                                       */
    /*************************************************************************/

    sym = whereto(hd);
    if( sized(hd) )
    {
      /* sized galley case: search on from current spot */
      target_index = SearchGalley(Up(hd_index), sym, TRUE, FALSE, TRUE, TRUE);
      if( target_index == nilobj )
      {	
	/* search failed to find any new target, so kill the galley */
	for( link = Down(hd); link != hd; link = NextDown(link) )
	{ Child(y, link);
	  if( type(y) == SPLIT )  Child(y, DownDim(y, dim));
	  if( is_definite(type(y)) )  break;
	}
	if( link != hd )
	  Error(19, 1, "galley %s deleted from here (no target)",
	    WARN, &fpos(y), SymName(actual(hd)));
	if( hd_inners != nilobj )  DisposeObject(hd_inners), hd_inners=nilobj;
	if( tg_inners != nilobj )  DisposeObject(tg_inners), tg_inners=nilobj;
	KillGalley(hd, FALSE);
	*inners = nilobj;
	debug0(DGA, D, "] AttachGalley returning ATTACH_KILLED");
	return ATTACH_KILLED;
      }
      else if( actual(actual(target_index)) == InputSym )
      {
	/* search found input object, so suspend on that */
	DeleteNode(hd_index);
	Link(target_index, hd);
	*inners = nilobj;
	debug0(DGA, D, "] AttachGalley returning ATTACH_INPUT");
	return ATTACH_INPUT;
      }

    }
    else /* unsized galley, either backwards or normal */
    {
      if( foll_or_prec(hd) == GALL_PREC )
      {	target_index= SearchGalley(Up(hd_index), sym, FALSE, TRUE,TRUE,FALSE);
	need_precedes = FALSE;
      }
      else
      {	target_index = SearchGalley(Up(hd_index), sym, FALSE,TRUE,FALSE,FALSE);
	need_precedes = (target_index != nilobj);
	if( target_index == nilobj )
	  target_index = SearchGalley(Up(hd_index), sym, TRUE,TRUE,TRUE,FALSE);
      }

      /* if no luck, exit without error */
      if( target_index == nilobj )
      {	*inners = nilobj;
	debug0(DGA, D, "] AttachGalley returning ATTACH_NOTARGET");
	return ATTACH_NOTARGET;
      }
    }
    assert( type(target_index) == RECEPTIVE, "AttachGalley: target_index!" );
    target = actual(target_index);
    assert( type(target) == CLOSURE, "AttachGalley: target!" );

    /* set target_galley to the expanded value of target */
    debug1(DYY, D, "[ EnterErrorBlock(FALSE) (expanding target %s)",
      SymName(actual(target)));
    EnterErrorBlock(FALSE);
    New(target_galley, HEAD);
    force_gall(target_galley) = FALSE;
    enclose_obj(target_galley) = limiter(target_galley) = nilobj;
    ClearHeaders(target_galley);
    opt_components(target_galley) = opt_constraints(target_galley) = nilobj;
    gall_dir(target_galley) = external_hor(target) ? COLM : ROWM;
    FposCopy(fpos(target_galley), fpos(target));
    actual(target_galley) = actual(target);
    whereto(target_galley) = ready_galls(target_galley) = nilobj;
    foll_or_prec(target_galley) = GALL_FOLL;
    must_expand(target_galley) = FALSE;
    sized(target_galley) = FALSE;

    /* get perpendicular constraint (none if horizontal galley) */
    if( dim == ROWM )
    {
      Constrained(target, &c, 1-dim, &junk);
      if( !constrained(c) )
        Error(19, 2, "receptive symbol %s has unconstrained width",
	  FATAL, &fpos(target), SymName(actual(target)));
      debug2(DSC, DD, "Constrained( %s, 1-dim ) = %s",
	EchoObject(target), EchoConstraint(&c));
      if( !FitsConstraint(0, 0, c) )
      { debug0(DGA, D, "  reject: target_galley horizontal constraint is -1");
	y = nilobj;
        goto REJECT;
      }
    }
    else /* actually unused */
      SetConstraint(c, MAX_FULL_LENGTH, MAX_FULL_LENGTH, MAX_FULL_LENGTH);

    debug1(DGA, DDD, "  expanding %s", EchoObject(target));
    tmp = CopyObject(target, no_fpos);
    Link(target_galley, tmp);
    env = DetachEnv(tmp);
    debug4(DGM, D, "  external_ver(%s) = %s, external_hor(%s) = %s",
      SymName(actual(target)), bool(external_ver(target)),
      SymName(actual(target)), bool(external_hor(target)));
    SizeGalley(target_galley, env,
	external_ver(target) || external_hor(target),
	threaded(target), non_blocking(target_index),
	trigger_externs(target_index), &save_style(target),
	&c, whereto(hd), &dest_index, &recs, &tg_inners,
	enclose_obj(hd) != nilobj ? CopyObject(enclose_obj(hd), no_fpos):nilobj);
    debug1(DGA, DD, "  SizeGalley tg_inners: %s", DebugInnersNames(tg_inners));
    if( recs != nilobj )  ExpandRecursives(recs);
    dest = actual(dest_index);
    if( underline(dest) == UNDER_UNDEF )  underline(dest) = UNDER_OFF;

    /* verify that hd satisfies any horizontal constraint on dest */
    if( dim == ROWM )
    {
      debug1(DGA, DDD, "  checking hor fit of hd in %s",SymName(actual(dest)));
      Constrained(dest, &c, 1-dim, &junk);
      debug3(DSC, DD, "Constrained( %s, %s ) = %s",
	EchoObject(dest), dimen(1-dim), EchoConstraint(&c));
      assert( constrained(c), "AttachGalley: dest unconstrained!" );
      if( !FitsConstraint(0, 0, c) )
      { debug0(DGA, D, "  reject: hd horizontal constraint is -1");
	y = nilobj;
        goto REJECT;
      }
    }

    /* manifest and size the galley if not done yet */
    if( !sized(hd) )
    {
      debug2(DYY, D, "[ EnterErrorBlock(TRUE) (sizing galley %s into %s)",
	SymName(actual(hd)), SymName(whereto(hd)));
      EnterErrorBlock(TRUE);
      n1 = nilobj;
      Child(y, Down(hd));
      env = DetachEnv(y);
      /*** threaded() only defined in ROWM case
      SizeGalley(hd, env, TRUE, threaded(dest), non_blocking(target_index),
	TRUE, &save_style(dest), &c, nilobj, &n1, &recs, &hd_inners);
      *** */
      SizeGalley(hd, env, TRUE, dim == ROWM ? threaded(dest) : FALSE,
	non_blocking(target_index), TRUE, &save_style(dest), &c, nilobj,
	&n1, &recs, &hd_inners, nilobj);
      debug1(DGA,DD,"  SizeGalley hd_inners: %s", DebugInnersNames(hd_inners));
      if( recs != nilobj )  ExpandRecursives(recs);
      if( need_precedes )		/* need an ordering constraint */
      {	OBJECT index1, index2;
        New(index1, PRECEDES);
	New(index2, FOLLOWS);
	blocked(index2) = FALSE;
	tmp = MakeWord(WORD, STR_EMPTY, no_fpos);
	Link(index1, tmp);  Link(index2, tmp);
	Link(Up(hd_index), index1);
	Link(Down(hd), index2);
	debug0(DGA, D, "  inserting PRECEDES and FOLLOWS");
      }
      LeaveErrorBlock(TRUE);
      debug0(DYY, D, "] LeaveErrorBlock(TRUE) (finished sizing galley)");
    }

    if( dim == ROWM )
    { if( !FitsConstraint(back(hd, 1-dim), fwd(hd, 1-dim), c) )
      { debug3(DGA, D, "  reject: hd %s,%s does not fit target_galley %s",
	  EchoLength(back(hd, 1-dim)), EchoLength(fwd(hd, 1-dim)),
	  EchoConstraint(&c));
        Error(19, 3, "too little horizontal space for galley %s at %s",
	  WARN, &fpos(hd), SymName(actual(hd)), SymName(actual(dest)));
        goto REJECT;
      }
    }

    /* check status of first component of hd */
    debug0(DGA, DDD, "  now ready to attach; hd =");
    ifdebug(DGA, DDD, DebugObject(hd));
    for( link = Down(hd);  link != hd;  link = NextDown(link) )
    {
      Child(y, link);
      debug1(DGA, DDD, "  examining %s", EchoIndex(y));
      if( type(y) == SPLIT )  Child(y, DownDim(y, dim));
      switch( type(y) )
      {

	case EXPAND_IND:
	case SCALE_IND:
	case COVER_IND:
	case GALL_PREC:
	case GALL_FOLL:
	case GALL_FOLL_OR_PREC:
	case GALL_TARG:
	case CROSS_PREC:
	case CROSS_FOLL:
	case CROSS_FOLL_OR_PREC:
	case CROSS_TARG:
	case PAGE_LABEL_IND:
	    
	  break;


	case PRECEDES:
	case UNATTACHED:
	    
	  if( was_sized )
	  { /* SizeGalley was not called, so hd_inners was not set by it */
	    if( hd_inners == nilobj )  New(hd_inners, ACAT);
	    Link(hd_inners, y);
	  }
	  break;


	case RECEPTIVE:

	  goto SUSPEND;


	case RECEIVING:
	    
	  goto SUSPEND;


	case FOLLOWS:
	    
	  Child(tmp, Down(y));
	  if( Up(tmp) == LastUp(tmp) )
	  { link = pred(link, CHILD);
	    debug0(DGA, DD, "  disposing FOLLOWS");
	    DisposeChild(NextDown(link));
	    break;
	  }
	  Parent(tmp, Up(tmp));
	  assert(type(tmp) == PRECEDES, "Attach: PRECEDES!");
	  switch( CheckComponentOrder(tmp, target_index) )
	  {
	    case CLEAR:		DeleteNode(tmp);
				link = pred(link, CHILD);
				DisposeChild(NextDown(link));
				break;

	    case PROMOTE:	break;

	    case BLOCK:		debug0(DGA, DD, "CheckContraint: BLOCK");
				goto SUSPEND;

	    case CLOSE:		debug0(DGA, D, "  reject: CheckContraint");
				goto REJECT;
	  }
	  break;


	case GAP_OBJ:

	  underline(y) = underline(dest);
	  if( !join(gap(y)) )  seen_nojoin(hd) = TRUE;
	  break;


	case BEGIN_HEADER:
	case END_HEADER:
	case SET_HEADER:
	case CLEAR_HEADER:

	  /* do nothing until actually promoted out of here */
	  underline(y) = underline(dest);
	  break;


	case CLOSURE:
	case CROSS:
	case FORCE_CROSS:
	case NULL_CLOS:
	case PAGE_LABEL:

	  underline(y) = underline(dest);
	  break;


	case WORD:
	case QWORD:
	case ONE_COL:
	case ONE_ROW:
	case WIDE:
	case HIGH:
	case HSHIFT:
	case VSHIFT:
	case HMIRROR:
	case VMIRROR:
	case HSCALE:
	case VSCALE:
	case HCOVER:
	case VCOVER:
	case HCONTRACT:
	case VCONTRACT:
	case HLIMITED:
	case VLIMITED:
	case HEXPAND:
	case VEXPAND:
	case START_HVSPAN:
	case START_HSPAN:
	case START_VSPAN:
	case HSPAN:
	case VSPAN:
	case ROTATE:
	case BACKGROUND:
	case SCALE:
	case KERN_SHRINK:
	case INCGRAPHIC:
	case SINCGRAPHIC:
	case PLAIN_GRAPHIC:
	case GRAPHIC:
	case LINK_SOURCE:
	case LINK_DEST:
	case LINK_DEST_NULL:
	case LINK_URL:
	case ACAT:
	case HCAT:
	case VCAT:
	case ROW_THR:
	case COL_THR:
	    

	  underline(y) = underline(dest);
	  if( dim == ROWM )
	  {
	    /* make sure y is not joined to a target below (vertical only) */
	    for( zlink = NextDown(link); zlink != hd; zlink = NextDown(zlink) )
	    { Child(z, zlink);
	      switch( type(z) )
	      {
	        case RECEPTIVE:
		
		  if( non_blocking(z) )
		  { zlink = PrevDown(zlink);
		    DeleteNode(z);
		  }
		  else
		  { y = z;
		    goto SUSPEND;
		  }
		  break;


	        case RECEIVING:
		
		  if( non_blocking(z) )
		  { zlink = PrevDown(zlink);
		    while( Down(z) != z )
		    { Child(tmp, Down(y));
		      if( opt_components(tmp) != nilobj )
		      { DisposeObject(opt_components(tmp));
		        opt_components(tmp) = nilobj;
		        debug3(DOG, D, "AttachGalley(%s) de-optimizing %s %s",
			  SymName(actual(hd)), SymName(actual(tmp)), "(join)");
		      }
		      DetachGalley(tmp);
		      KillGalley(tmp, FALSE);
		    }
		    DeleteNode(z);
		  }
		  else
		  { y = z;
		    goto SUSPEND;
		  }
		  break;


	        case GAP_OBJ:
		
		  if( !join(gap(z)) )  zlink = PrevDown(hd);
		  break;


	        default:	break;
	      }
	    }

	    /* if HCAT, try vertical hyphenation (vertical galleys only) */
	    if( type(y) == HCAT )  VerticalHyphenate(y);
	  }


	  /* check availability of parallel space for the first component */
	  why = nilobj;
	  Constrained(dest, &c, dim, &why);
	  debug3(DGF, DD, "  dest parallel Constrained(%s, %s) = %s",
	    EchoObject(dest), dimen(dim), EchoConstraint(&c));
	  if( !FitsConstraint(back(y, dim), fwd(y, dim), c) )
	  { BOOLEAN scaled;

	    /* if forcing galley doesn't fit, try scaling first component */
	    scaled = FALSE;
	    if( force_gall(hd) && size(y, dim) > 0 )
	    { int scale_factor;
	      scale_factor = ScaleToConstraint(back(y,dim), fwd(y,dim), &c);
	      if( scale_factor > 0.5 * SF )
	      {	char num1[20], num2[20];
		sprintf(num1, "%.1fc", (float) size(y, dim) / CM);
		sprintf(num2, "%.1fc", (float) bfc(c) / CM);
		if( dim == ROWM )
		  Error(19, 4, "%s object too high for %s space; %s inserted",
		    WARN, &fpos(y), num1, num2, KW_SCALE);
		else
		  Error(19, 5, "%s object too wide for %s space; %s inserted",
		    WARN, &fpos(y), num1, num2, KW_SCALE);
		y = InterposeScale(y, scale_factor, dim);
		scaled = TRUE;
	      }
	    }

	    /* otherwise we must reject, and warn the user */
	    if( !scaled )
	    { char num1[20], num2[20];
	      debug3(DGA, D, "  reject: vsize %s,%s in %s; y=",
		EchoLength(back(y, dim)), EchoLength(fwd(y, dim)),
		EchoConstraint(&c));
	      ifdebug(DGA, D, DebugObject(y));
	      if( size(y, dim) > 0 )
	      { sprintf(num1, "%.1fc", (float) size(y, dim) / CM);
	        sprintf(num2, "%.1fc", (float) bfc(c) / CM);
	        if( dim == ROWM )
		  Error(19, 12, "%s object too high for %s space; will try elsewhere",
		    WARN, &fpos(y), num1, num2);
	        else
		  Error(19, 13, "%s object too wide for %s space; will try elsewhere",
		    WARN, &fpos(y), num1, num2);
	      }
	      goto REJECT;
	    }

	  }

	  /* check availability of perpendicular space for first component */
	  if( dim == ROWM )
	  { perp_back = back(hd, 1-dim);  perp_fwd = fwd(hd, 1-dim);
	  }
	  else
	  { perp_back = back(y, 1-dim);  perp_fwd = fwd(y, 1-dim);
	  }
	  Constrained(dest, &c, 1-dim, &junk);
	  debug3(DGF, DD, "  dest perpendicular Constrained(%s, %s) = %s",
	    EchoObject(dest), dimen(1-dim), EchoConstraint(&c));
	  if( !FitsConstraint(perp_back, perp_fwd, c) )
	  { BOOLEAN scaled;

	    /* if forcing galley doesn't fit, try scaling first component */
	    scaled = FALSE;
	    if( force_gall(hd) && perp_back + perp_fwd > 0 )
	    { int scale_factor;
	      scale_factor = ScaleToConstraint(perp_back, perp_fwd, &c);
	      if( scale_factor > 0.5 * SF )
	      {	char num1[20], num2[20];
		sprintf(num1, "%.1fc", (float) (perp_back + perp_fwd) / CM);
		sprintf(num2, "%.1fc", (float) bfc(c) / CM);
		if( 1-dim == ROWM )
		  Error(19, 6, "%s object too high for %s space; %s inserted",
		    WARN, &fpos(y), num1, num2, KW_SCALE);
		else
		  Error(19, 7, "%s object too wide for %s space; %s inserted",
		    WARN, &fpos(y), num1, num2, KW_SCALE);
		y = InterposeScale(y, scale_factor, 1-dim);
		scaled = TRUE;
	      }
	    }

	    /* otherwise we must reject, and warn the user */
	    if( !scaled )
	    {
	      debug3(DGA, D, "  reject: vsize %s,%s in %s; y=",
		EchoLength(perp_back), EchoLength(perp_fwd),
		EchoConstraint(&c));
	      ifdebug(DGA, D, DebugObject(y));
	      goto REJECT;
	    }

	  }

	  /* dest seems OK, so perform its size adjustments */
	  debug0(DSA, D, "calling AdjustSize from AttachGalley (a)");
	  AdjustSize(dest, back(y, dim), fwd(y, dim), dim);
	  debug0(DSA, D, "calling AdjustSize from AttachGalley (b)");
	  AdjustSize(dest, perp_back, perp_fwd, 1-dim);


	  /* now check parallel space for target_galley in target */
	  Constrained(target, &c, dim, &why);
	  debug3(DGF, DD, "  target parallel Constrained(%s, %s) = %s",
	    EchoObject(target), dimen(dim), EchoConstraint(&c));
	  Child(z, LastDown(target_galley));  /* works in all cases? */
	  assert( !is_index(type(z)), "AttachGalley: is_index(z)!" );
	  assert( back(z, dim)>=0 && fwd(z, dim)>=0, "AttachGalley: z size!" );
	  if( !FitsConstraint(back(z, dim), fwd(z, dim), c) )
	  { BOOLEAN scaled;

	    debug2(DGA, D, "  why     = %d %s", (int) why, EchoObject(why));
	    debug2(DGA, D, "  limiter = %d %s", (int) limiter(hd),
	      EchoObject(limiter(hd)));

	    /* if forcing galley doesn't fit, try scaling z */
	    scaled = FALSE;
	    if( force_gall(hd) && size(z, dim) > 0 && limiter(hd) != why )
	    { int scale_factor;
	      scale_factor = ScaleToConstraint(back(z,dim), fwd(z,dim), &c);
	      if( scale_factor > 0.5 * SF )
	      {	char num1[20], num2[20];
		sprintf(num1, "%.1fc", (float) size(z, dim) / CM);
		sprintf(num2, "%.1fc", (float) bfc(c) / CM);
		if( dim == ROWM )
		  Error(19, 8, "%s object too high for %s space; %s inserted",
		    WARN, &fpos(y), num1, num2, KW_SCALE);
		else
		  Error(19, 9, "%s object too wide for %s space; %s inserted",
		    WARN, &fpos(y), num1, num2, KW_SCALE);
		z = InterposeWideOrHigh(z, dim);
		z = InterposeScale(z, scale_factor, dim);
		scaled = TRUE;
	      }
	    }

	    if( !scaled )
	    { char num1[20], num2[20];
	      limiter(hd) = why;
	      debug3(DGA, D, "  set limiter(%s) = %d %s", SymName(actual(hd)),
		(int) limiter(hd), EchoObject(limiter(hd)));
	      debug3(DGA, D, "  reject: size was %s,%s in %s; y =",
		EchoLength(back(z, dim)), EchoLength(fwd(z, dim)),
		EchoConstraint(&c));
	      ifdebug(DGA, D, DebugObject(y));
	      if( size(z, dim) > 0 )
	      { sprintf(num1, "%.1fc", (float) size(z, dim) / CM);
	        sprintf(num2, "%.1fc", (float) bfc(c) / CM);
	        if( dim == ROWM )
		  Error(19, 14, "%s object too high for %s space; will try elsewhere",
		    WARN, &fpos(y), num1, num2);
	        else
		  Error(19, 15, "%s object too wide for %s space; will try elsewhere",
		    WARN, &fpos(y), num1, num2);
	      }
	      goto REJECT;
	    }
	  }
	  limiter(hd) = why;
	  debug3(DGA, D, "  set limiter(%s) = %d %s", SymName(actual(hd)),
	    (int) limiter(hd), EchoObject(limiter(hd)));

	  /* now check perpendicular space for target_galley in target */
	  Constrained(target, &c, 1-dim, &junk);
	  debug3(DGF, DD, "  target perpendicular Constrained(%s, %s) = %s",
	    EchoObject(target), dimen(1-dim), EchoConstraint(&c));
	  Child(z, LastDown(target_galley));  /* works in all cases? */
	  assert( !is_index(type(z)), "AttachGalley: is_index(z)!" );
	  assert( back(z, 1-dim)>=0 && fwd(z, 1-dim)>=0,
	    "AttachGalley: z size (perpendicular)!" );
	  if( !FitsConstraint(back(z, 1-dim), fwd(z, 1-dim), c) )
	  { BOOLEAN scaled;

	    /* if forcing galley doesn't fit, try scaling z */
	    scaled = FALSE;
	    if( force_gall(hd) && size(z, 1-dim) > 0 )
	    { int scale_factor;
	      scale_factor = ScaleToConstraint(back(z,1-dim), fwd(z,1-dim), &c);
	      if( scale_factor > 0.5 * SF )
	      {	char num1[20], num2[20];
		sprintf(num1, "%.1fc", (float) size(z, 1-dim) / CM);
		sprintf(num2, "%.1fc", (float) bfc(c) / CM);
		if( 1-dim == ROWM )
		  Error(19, 10, "%s object too high for %s space; %s inserted",
		    WARN, &fpos(y), num1, num2, KW_SCALE);
		else
		  Error(19, 11, "%s object too wide for %s space; %s inserted",
		    WARN, &fpos(y), num1, num2, KW_SCALE);
		z = InterposeWideOrHigh(z, 1-dim);
		z = InterposeScale(z, scale_factor, 1-dim);
		scaled = TRUE;
	      }
	    }

	    if( !scaled )
	    {
	      debug3(DGA, D, "  reject: size was %s,%s in %s; y =",
		EchoLength(back(z, 1-dim)), EchoLength(fwd(z, 1-dim)),
		EchoConstraint(&c));
	      ifdebug(DGA, D, DebugObject(y));
	      goto REJECT;
	    }
	  }

	  /* target seems OK, so adjust sizes and accept */
	  if( external_hor(target) )
	  {
	    /* don't adjust any sizes, none to adjust */
	    debug0(DSA, D, "not calling AdjustSize from AttachGalley (c)");
	  }
	  else if( external_ver(target) )
	  {
	    /* adjust perp size only, to galley size */
	    debug0(DSA, D, "calling AdjustSize from AttachGalley (d)");
	    AdjustSize(target, back(target_galley, 1-dim),
	      fwd(target_galley, 1-dim), 1-dim);
	  }
	  else
	  {
	    /* adjust both directions, using z (last component) */
	    Child(z, LastDown(target_galley));
	    debug0(DSA, D, "AttachGalley AdjustSize using z =");
	    ifdebug(DSA, D, DebugObject(z));
	    debug0(DSA, D, "calling AdjustSize from AttachGalley (e)");
	    AdjustSize(target, back(z, dim), fwd(z, dim), dim);
	    debug0(DSA, D, "calling AdjustSize from AttachGalley (f)");
	    AdjustSize(target, back(z, 1-dim), fwd(z, 1-dim), 1-dim);
	  }

	  goto ACCEPT;


	default:
	    
	  assert1(FALSE, "AttachGalley:", Image(type(y)));
	  break;

      } /* end switch */
    } /* end for */

    /* null galley: promote whole galley without expanding the target */
    debug0(DGA, D, "  null galley");
    if( tg_inners != nilobj )  DisposeObject(tg_inners), tg_inners = nilobj;
    DisposeObject(target_galley);
    LeaveErrorBlock(FALSE);
    debug0(DYY, D, "] LeaveErrorBlock(FALSE) (null galley)");

    /* kill off any null objects within the galley, then transfer it */
    /* don't use Promote() since it does extra unwanted things here  */
    for( link = Down(hd);  link != hd;  link = NextDown(link) )
    { Child(y, link);
      switch( type(y) )
      {

	case GAP_OBJ:
	case CLOSURE:
	case CROSS:
	case FORCE_CROSS:
	case NULL_CLOS:
	case PAGE_LABEL:
	
	  link = PrevDown(link);
	  debug1(DGA, D, "  null galley, disposing %s", Image(type(y)));
	  DisposeChild(NextDown(link));
	  break;

	
	default:
	
	  break;
      }
    }
    TransferLinks(NextDown(hd), hd, Up(target_index));

    /* attach hd temporarily to target_index */
    MoveLink(Up(hd), target_index, PARENT);
    assert( type(hd_index) == UNATTACHED, "AttachGalley: type(hd_index)!" );
    DeleteNode(hd_index);

    /* return; only hd_inners needs to be flushed now */
    *inners = hd_inners;
    debug0(DGA, D, "] AttachGalley returning ATTACH_NULL");
    return ATTACH_NULL;


    REJECT:
	
      /* reject first component */
      /* debug1(DGA, D, "  reject %s", EchoObject(y)); */
      debug0(DGA, D, "  reject first component");
      LeaveErrorBlock(TRUE);
      debug0(DYY, D, "] LeaveErrorBlock(TRUE) (REJECT)");
      if( tg_inners != nilobj )  DisposeObject(tg_inners), tg_inners = nilobj;
      DisposeObject(target_galley);
      if( foll_or_prec(hd) == GALL_PREC && !sized(hd) )
      {
	/* move to just before the failed target */
	MoveLink(Up(hd_index), Up(target_index), PARENT);
      }
      else
      {
	/* move to just after the failed target */
	MoveLink(Up(hd_index), NextDown(Up(target_index)), PARENT);
      }
      continue;


    SUSPEND:
	
      /* suspend at first component */
      debug1(DGA, D, "  suspend %s", EchoIndex(y));
      blocked(y) = TRUE;
      LeaveErrorBlock(FALSE);
      debug0(DYY, D, "] LeaveErrorBlock(FALSE) (SUSPEND)");
      if( tg_inners != nilobj )  DisposeObject(tg_inners), tg_inners = nilobj;
      DisposeObject(target_galley);
      MoveLink(Up(hd_index), Up(target_index), PARENT);
      if( was_sized )
      { /* nothing new to flush if suspending and already sized */
	if( hd_inners != nilobj )  DisposeObject(hd_inners), hd_inners=nilobj;
	*inners = nilobj;
      }
      else
      { /* flush newly discovered inners if not sized before */
	*inners = hd_inners;
      }
      debug0(DGA, D, "] AttachGalley returning ATTACH_SUSPEND");
      *suspend_pt = y;
      return ATTACH_SUSPEND;


    ACCEPT:
	
      /* accept first component; now committed to the attach */
      debug3(DGA, D, "  accept %s %s %s", Image(type(y)), EchoObject(y),
	EchoFilePos(&fpos(y)));
      LeaveErrorBlock(TRUE);
      debug0(DYY, D, "] LeaveErrorBlock(TRUE) (ACCEPT)");

      /* attach hd to dest */
      MoveLink(Up(hd), dest_index, PARENT);
      assert( type(hd_index) == UNATTACHED, "AttachGalley: type(hd_index)!" );
      DeleteNode(hd_index);

      /* move first component of hd into dest */
      /* nb Interpose must be done after all AdjustSize calls */
      if( dim == ROWM && !external_ver(dest) )
	Interpose(dest, VCAT, hd, y);
      else if( dim == COLM && !external_hor(dest) )
      { Interpose(dest, ACAT, y, y);
	Parent(junk, Up(dest));
	assert( type(junk) == ACAT, "AttachGalley: type(junk) != ACAT!" );
	StyleCopy(save_style(junk), save_style(dest));
	adjust_cat(junk) = padjust(save_style(junk));
      }
      debug1(DGS, D, "calling Promote(hd, %s) from AttachGalley/ACCEPT",
	link == hd ? "hd" : "NextDown(link)");
      Promote(hd, link == hd ? hd : NextDown(link), dest_index, TRUE);

      /* move target_galley into target */
      /* nb Interpose must be done after all AdjustSize calls */
      if( !(external_ver(target) || external_hor(target)) )
      {	Child(z, LastDown(target_galley));
	Interpose(target, VCAT, z, z);
      }
      debug0(DGS, D, "calling Promote(target_galley) from AttachGalley/ACCEPT");
      Promote(target_galley, target_galley, target_index, TRUE);
      DeleteNode(target_galley);
      assert(Down(target_index)==target_index, "AttachGalley: target_ind");
      if( blocked(target_index) )  blocked(dest_index) = TRUE;
      DeleteNode(target_index);

      /* return; both tg_inners and hd_inners need to be flushed now;        */
      /* if was_sized, hd_inners contains the inners of the first component; */
      /* otherwise it contains the inners of all components, from SizeGalley */
      if( tg_inners == nilobj ) *inners = hd_inners;
      else if( hd_inners == nilobj ) *inners = tg_inners;
      else
      {	TransferLinks(Down(hd_inners), hd_inners, tg_inners);
	DeleteNode(hd_inners);
	*inners = tg_inners;
      }
      debug0(DGA, D, "] AttachGalley returning ATTACH_ACCEPT");
      ifdebug(DGA, D,
	if( dim == COLM && !external_hor(dest) )
	{ OBJECT z;
	  Parent(z, Up(dest));
	  debug2(DGA, D, "  COLM dest_encl on exit = %s %s",
	    Image(type(z)), EchoObject(z));
	}
      )
      return ATTACH_ACCEPT;

  } /* end for */
Beispiel #26
0
static int
select_mode(struct conf *cfp, int sock, int *pipefd, int parentfd)
{
	char sockbuf[BUFSIZ], pipebuf[PIPE_BUF];
	struct timeval timeout, *tp = NULL;
	ssize_t pcc = 0, scc = 0, n;
	char *pbp = NULL, *sbp = NULL;
	int rsel, maxfd;
	int bye = 0;
	
	(void)non_blocking(sock);
	(void)non_blocking(parentfd);

	maxfd = sock > parentfd ? sock : parentfd;

	if (pipefd[0] != -1) {
		(void)non_blocking(pipefd[0]);
		if (pipefd[0] > maxfd)
			maxfd = pipefd[0];
	}		

	if (pipefd[1] != -1) {
		(void)non_blocking(pipefd[1]);
		if (pipefd[1] > maxfd)
			maxfd = pipefd[1];
	}

	maxfd += 1;

	if (cfp->mode == READ)
                (void)shutdown(sock, SHUT_RD);
        if (cfp->mode == BLIND)
                (void)shutdown(sock, SHUT_WR);

	for (;;) {
		fd_set rset, wset, *omask;

		FD_ZERO(&rset);
		FD_ZERO(&wset);

		omask = (fd_set *) NULL;

		switch (cfp->mode) {
		case READWRITE:
			if (!bye) {
				if (scc) {
					FD_SET(pipefd[1], &wset);
					omask = &wset;
				} else	 
					FD_SET(sock, &rset);
			}

                	if (pcc >= 0) {
                        	if (pcc) {
                                	FD_SET(sock, &wset);
                                	omask = &wset;
                        	} else
                        		FD_SET(pipefd[0], &rset);
			}
			break;
		case BLIND:
			if (bye)
				return 0;

			if (scc) {
				FD_SET(pipefd[1], &wset);
				omask = &wset;
			} else 
				FD_SET(sock, &rset);

			break;
		case READ:
			if (pcc >= 0) {
				if (pcc) {
					FD_SET(sock, &wset);
					omask = &wset;
				} else
					FD_SET(pipefd[0], &rset);
			}
			break;
		default:
			return -1;	
		}

		FD_SET(parentfd, &rset);

		/*
		 * Posix.1g defines timeout parameter to const
		 * Linux uses a value-result timeout
		 */
		if (cfp->timeout) {
			timeout.tv_sec  = cfp->timeout;
			timeout.tv_usec = 0;
			tp = &timeout;
		}

                if ((rsel = select(maxfd, &rset, omask, NULL, tp)) == -1) {
                        if (errno == EINTR)
                                continue;
                        else {
                                syslog(LOG_ERR, "selectfd %m");
                                return -1;
                        }
                }

                if (rsel == 0) {
                        if (tp) {
				syslog(LOG_INFO, 
					"[%s] session time expired for %s",
					cfp->session, 
					get_pwentry(cfp->setuser)->pw_name);
                                return 0;
                        } else
                                continue;
                }

                if (FD_ISSET(parentfd, &rset)) {
                        char c;

                        (void)read(parentfd, &c, 1);

                        return 0;
                }

                if (cfp->mode != BLIND && FD_ISSET(pipefd[0], &rset)) {
                        pcc = read(pipefd[0], pipebuf, sizeof(pipebuf));
		    
			if (pcc < 0 && errno == EWOULDBLOCK)
                                pcc = 0;
                        else {
                                if (pcc <= 0)
                                        return (pcc) ? -1 : 0;

                                pbp = pipebuf;

                                FD_SET(sock, &wset);
                        }
                }

                if (FD_ISSET(sock, &wset) && pcc > 0) {
                        n = write(sock, pbp, (size_t)pcc);

                        if (n < 0 && errno == EWOULDBLOCK)
                                continue;
                        else if (n < 0) 
                                return -1;

                        if (n > 0)
				pcc -= n, pbp += n;
		}
		
                if (cfp->mode != READ && FD_ISSET(sock, &rset)) {
                        scc = read(sock, sockbuf, sizeof(sockbuf));

                        if (scc < 0 && errno == EWOULDBLOCK)
                               	scc = 0;
                        else {
                                if (scc < 0)
                                        return -1;

				/*
				 * client write-shutdown or disconnection
				 */
				if (scc == 0) {
					bye = 1;
					(void)close(pipefd[1]);
					continue;
				} 

                                sbp = sockbuf;

                                FD_SET(pipefd[1], &wset);
                        }
                }

                if (cfp->mode != READ && FD_ISSET(pipefd[1], &wset)) {
                        n = write(pipefd[1], sbp, (size_t)scc);

                        if (n < 0 && errno == EWOULDBLOCK)
                                continue;
                        else if (n < 0)
                                return -1;

                        if (n > 0)
                                scc -= n, sbp += n;
                }
        }
}
Beispiel #27
0
    socket1.set_option(settable_socket_option2);
    socket1.set_option(settable_socket_option2, ec);
    socket1.set_option(settable_socket_option3);
    socket1.set_option(settable_socket_option3, ec);

    socket1.get_option(gettable_socket_option1);
    socket1.get_option(gettable_socket_option1, ec);
    socket1.get_option(gettable_socket_option2);
    socket1.get_option(gettable_socket_option2, ec);
    socket1.get_option(gettable_socket_option3);
    socket1.get_option(gettable_socket_option3, ec);

    socket1.io_control(io_control_command);
    socket1.io_control(io_control_command, ec);

    bool non_blocking1 = socket1.non_blocking();
    (void)non_blocking1;
    socket1.non_blocking(true);
    socket1.non_blocking(false, ec);

    bool non_blocking2 = socket1.native_non_blocking();
    (void)non_blocking2;
    socket1.native_non_blocking(true);
    socket1.native_non_blocking(false, ec);

    ip::udp::endpoint endpoint1 = socket1.local_endpoint();
    ip::udp::endpoint endpoint2 = socket1.local_endpoint(ec);

    ip::udp::endpoint endpoint3 = socket1.remote_endpoint();
    ip::udp::endpoint endpoint4 = socket1.remote_endpoint(ec);
Beispiel #28
0
static int
select_fd(struct conf *cfp, int sock, int master, int ctrls, int parentfd)
{
	char sockbuf[1024], ptybuf[1024];
	struct timeval timeout, *tp = NULL;
	char *pbp = NULL, *sbp = NULL;
	ssize_t pcc = 0, scc = 0, n;
	int rsel, maxfd;

	(void)non_blocking(sock);
	(void)non_blocking(master);
	(void)non_blocking(ctrls);
	(void)non_blocking(parentfd);

	(void)signal(SIGTTOU, SIG_IGN);

	maxfd = sock    > master  	? sock    	: master;
	if (maxfd < ctrls)
		maxfd = ctrls;
	if (maxfd < pipechld[0])
		maxfd = pipechld[0];
	if (maxfd < parentfd)
		maxfd = parentfd;
	maxfd += 1;

	for (;;) {
		fd_set rset, wset, *omask;	

		FD_ZERO(&rset);
		FD_ZERO(&wset);

		omask = (fd_set *) NULL;

		if (scc) {
			FD_SET(master, &wset);
			omask = &wset;
		} else
			FD_SET(sock, &rset);

		if (pcc >= 0) {
			if (pcc) {
				FD_SET(sock, &wset);
				omask = &wset;
			} else
				FD_SET(master, &rset);
		}

		FD_SET(ctrls, &rset);
		FD_SET(pipechld[0], &rset);
		FD_SET(parentfd, &rset);
	
		/*
		 * Posix.1g defines timeout parameter to const
		 * Linux uses a value-result timeout
		 */
		if (cfp->timeout) {
			timeout.tv_sec  = cfp->timeout;
			timeout.tv_usec = 0;
			tp = &timeout;
        	}

		if ((rsel = select(maxfd, &rset, omask, NULL, tp)) == -1) {
			if (errno == EINTR)
				continue;
			else {
				syslog(LOG_ERR, "selectfd %m"); 
				return -1;
			}
		}

		if (rsel == 0) {
			if (tp) {
				syslog(LOG_INFO, 
					"[%s] session time expired for %s",
					cfp->session,
					get_pwentry(cfp->setuser)->pw_name);
				/*
				 * giving a chance to know timeout
				 * XXX: writen()?
				 */
				(void)write(sock, "\r\nsession timeout\r\n", 
					(size_t)19); 
				return 0;
			} else 
				continue;
		}

		if (FD_ISSET(parentfd, &rset)) {
			char c;

			(void)read(parentfd, &c, 1);

			return 0;
		}

		if (FD_ISSET(pipechld[0], &rset)) {
			pid_t pid;
			int st;
			char c;

			if (pipechld[0] != -1)
				(void)read(pipechld[0], &c, 1);

			while ((pid = waitpid(-1, &st, WNOHANG|WUNTRACED))) {
				if (pid < 0) {
					if (errno == EINTR)
						continue;
					else
						break;
				}

				if (WIFSTOPPED(st)) {
					(void)kill(pid, SIGCONT);
					continue;
				}
			}
		} 

		if (FD_ISSET(ctrls, &rset)) {
			struct schck sck;
	
			n = read(ctrls, &sck, sizeof(sck));
		
			if (n == sizeof(sck))
				(void)control_check(ctrls, &sck, master);
                }

		if (FD_ISSET(sock, &rset)) {
			scc = read(sock, sockbuf, sizeof(sockbuf));
			if (scc < 0 && errno == EWOULDBLOCK)
				scc = 0;
			else {
				if (scc <= 0)
					return (scc) ? -1 : 0;
	
				sbp = sockbuf;
			
				FD_SET(master, &wset);
			}			
		}

		if (FD_ISSET(master, &wset) && scc > 0) {
			n = write(master, sbp, (size_t)scc);
		
			if (n < 0 && errno == EWOULDBLOCK) 
				continue;
			else if (n < 0) 
				return -1;

			if (n > 0) 
				scc -= n, sbp += n;
		}

		if (FD_ISSET(master, &rset)) {
			pcc = read(master, ptybuf, sizeof(ptybuf));
			if (pcc < 0 && errno == EWOULDBLOCK) 
				pcc = 0;
			else {
				if (pcc <= 0) 
					return (pcc) ? -1 : 0;

				pbp = ptybuf;

				FD_SET(sock, &wset);
			}
		}

		if (FD_ISSET(sock, &wset) && pcc > 0) {
			n = write(sock, pbp, (size_t)pcc);

			if (n < 0 && errno == EWOULDBLOCK)
				continue;
			else if (n < 0) 
				return -1;

			if (n > 0) 
				pcc -= n, pbp += n;
		}
	}

	/*
	 * never reached
	 */
	return -1;
}
Beispiel #29
0
void
exec_shell(struct conf *cfp, int fd, int parentfd)
{
	int master, slave;
	pid_t pid;
	char line[MAXPATHLEN];	
	char *tty;
#ifdef HAVE_UTMP_H
	struct utmp ut;
#elif HAVE_UTMPX_H
	struct utmpx ut;
	struct timeval tv;
#endif

#if defined(HAVE_UTMP_H) || defined(HAVE_UTMPX_H)
	memset(&ut, 0, sizeof(ut));
#endif

	if (pipe(pipechld) < 0) {
		syslog(LOG_ERR, "exec_shell() pipe %m");
		_exit(1);
	}

	(void)non_blocking(pipechld[0]);
	(void)non_blocking(pipechld[1]);

	if (openpty(&master, &slave, line, NULL, NULL) == -1) {
		syslog(LOG_ERR, "openpty %m");
		_exit(1) ;
	}

	/*
	 * pts/x compatible
	 */
	if ((tty = strstr(line, "/dev/"))) 
        	tty += 5;
       	else
       		tty = line;
#if defined(HAVE_UTMP_H) || defined(HAVE_UTMPX_H)
	if (cfp->utmp) {
		if (cfp->utname) {
#ifdef HAVE_UTMP_H
                	(void)strncpy(ut.ut_name, cfp->utname, 
				sizeof(ut.ut_name)-1);
			ut.ut_name[sizeof(ut.ut_name)-1] = '\0';
#elif HAVE_UTMPX_H
			(void)strncpy(ut.ut_user, cfp->utname,
				sizeof(ut.ut_user)-1);
			ut.ut_user[sizeof(ut.ut_user)-1] = '\0';
#endif
		} else {
			struct passwd *pw;
			pw = get_pwentry(cfp->havesetuser ? cfp->setuser : \
				0);
#ifdef HAVE_UTMP_H
			(void)strncpy(ut.ut_name, pw->pw_name, 
				sizeof(ut.ut_name)-1); 

			ut.ut_name[sizeof(ut.ut_name)-1] = '\0';
#elif HAVE_UTMPX_H
			(void)strncpy(ut.ut_user, pw->pw_name,
				sizeof(ut.ut_user)-1);

			ut.ut_user[sizeof(ut.ut_user)-1] = '\0';
#endif
		}
    
		(void)strncpy(ut.ut_line, tty, sizeof(ut.ut_line)-1);
		ut.ut_line[sizeof(ut.ut_line)-1] = '\0';

		if (cfp->uthost) {
			(void)strncpy(ut.ut_host, cfp->uthost, 
				sizeof(ut.ut_host)-1);
			ut.ut_host[sizeof(ut.ut_host)-1] = '\0';
		}
#ifdef HAVE_UTMP_H	
		(void)time(&ut.ut_time);	
#elif HAVE_UTMPX_H 
		(void)gettimeofday(&tv, NULL);
		ut.ut_tv.tv_sec = tv.tv_sec;
		ut.ut_tv.tv_usec = tv.tv_usec;

		(void)strncpy(ut.ut_id, ut.ut_line, sizeof(ut.ut_id)-1);
		ut.ut_line[sizeof(ut.ut_line)-1] = '\0';

		ut.ut_pid = getpid();
		ut.ut_type = USER_PROCESS;
#endif
	}
#endif

        /*
         * overwriting signal disposition
         */
        (void)signal(SIGCHLD, sig_chld);

	switch (pid = fork()) {
	case -1:
		syslog(LOG_ERR, "forkpty: %m");
		_exit(1);	
	case 0:
		(void)close(parentfd);
		(void)close(pipechld[0]);
		(void)close(pipechld[1]);
		(void)close(master);
               	(void)close(fd);	
		(void)login_tty(slave);
#ifdef HAVE_UTMP_H
		login(&ut);
#elif HAVE_UTMPX_H
		setutxent();
		(void)pututxline(&ut);
#endif

		set_privileges(cfp);		

		/*
		 * SUIP PROGRAM HERE
		 */
#ifdef __NetBSD__
		(void)execl(_PATH_BSHELL,"sh", "-c",cfp->suipfile,(char *)NULL);
#else
		(void)execl(_PATH_BSHELL, "sh", "-p", "-c", cfp->suipfile, 
			(char *)NULL);
#endif
		_exit(127);
	default:
	{
		int ctrls;	
		int exit_status = 0;
	
		(void)close(slave);
	
		/*
		 * trying to open a control channel
		 * control_create() returns the number of bytes which were 
		 * written
		 * select_fd() returns -1 if errors exist you can check errno
		 */
		if (control_create(&ctrls, fd) == 1) { 
			if (select_fd(cfp, fd, master, ctrls, parentfd) < 0)
				exit_status = 1; 
		} else {
			syslog(LOG_ERR, "can't open ctrl chan");
			exit_status = 1;
		}
#if defined(HAVE_UTMP_H) || defined(HAVE_UTMPX_H)
		if (cfp->utmp) {
#ifdef HAVE_UTMP_H
			if (!logout(tty)) { 
				syslog(LOG_ERR, "unable to logout on %s", tty);
				exit_status = 1;
			} else
				logwtmp(tty, "", "");
#elif HAVE_UTMPX_H
			ut.ut_type = DEAD_PROCESS;
			(void)gettimeofday(&tv, NULL);
			ut.ut_tv.tv_sec = tv.tv_sec;
			ut.ut_tv.tv_usec = tv.tv_usec;

			(void)memset(&ut.ut_user, 0, sizeof(ut.ut_user));	
			setutxent();
			if (pututxline(&ut) == NULL) {
				syslog(LOG_ERR, 
					"unable to logout on %s (utmpx)",
					tty);
				exit_status = 1;
			}	
			endutxent();
#endif
		}
#endif

		cleanup(line);
		_exit(exit_status);
	}}	

	/*
	 * never reached
	 */
	_exit(1);
}
Beispiel #30
0
static void psc_service(VSTREAM *smtp_client_stream,
			        char *unused_service,
			        char **unused_argv)
{
    const char *myname = "psc_service";
    PSC_STATE *state;
    struct sockaddr_storage addr_storage;
    SOCKADDR_SIZE addr_storage_len = sizeof(addr_storage);
    MAI_HOSTADDR_STR smtp_client_addr;
    MAI_SERVPORT_STR smtp_client_port;
    MAI_HOSTADDR_STR smtp_server_addr;
    MAI_SERVPORT_STR smtp_server_port;
    int     aierr;
    const char *stamp_str;
    int     saved_flags;

    /*
     * For sanity, require that at least one of INET or INET6 is enabled.
     * Otherwise, we can't look up interface information, and we can't
     * convert names or addresses.
     */
    if (inet_proto_info()->ai_family_list[0] == 0)
	msg_fatal("all network protocols are disabled (%s = %s)",
		  VAR_INET_PROTOCOLS, var_inet_protocols);

    /*
     * This program handles all incoming connections, so it must not block.
     * We use event-driven code for all operations that introduce latency.
     * 
     * Note: instead of using VSTREAM-level timeouts, we enforce limits on the
     * total amount of time to receive a complete SMTP command line.
     */
    non_blocking(vstream_fileno(smtp_client_stream), NON_BLOCKING);

    /*
     * We use the event_server framework. This means we get already-accepted
     * connections so we have to invoke getpeername() to find out the remote
     * address and port.
     */

    /* Best effort - if this non-blocking write(2) fails, so be it. */
#define PSC_SERVICE_DISCONNECT_AND_RETURN(stream) do { \
	(void) write(vstream_fileno(stream), \
		     "421 4.3.2 No system resources\r\n", \
		     sizeof("421 4.3.2 No system resources\r\n") - 1); \
	event_server_disconnect(stream); \
	return; \
    } while (0);

    /*
     * Look up the remote SMTP client address and port.
     */
    if (getpeername(vstream_fileno(smtp_client_stream), (struct sockaddr *)
		    & addr_storage, &addr_storage_len) < 0) {
	msg_warn("getpeername: %m -- dropping this connection");
	PSC_SERVICE_DISCONNECT_AND_RETURN(smtp_client_stream);
    }

    /*
     * Convert the remote SMTP client address and port to printable form for
     * logging and access control.
     */
    if ((aierr = sockaddr_to_hostaddr((struct sockaddr *) & addr_storage,
				      addr_storage_len, &smtp_client_addr,
				      &smtp_client_port, 0)) != 0) {
	msg_warn("cannot convert client address/port to string: %s"
		 " -- dropping this connection",
		 MAI_STRERROR(aierr));
	PSC_SERVICE_DISCONNECT_AND_RETURN(smtp_client_stream);
    }
    if (strncasecmp("::ffff:", smtp_client_addr.buf, 7) == 0)
	memmove(smtp_client_addr.buf, smtp_client_addr.buf + 7,
		sizeof(smtp_client_addr.buf) - 7);
    if (msg_verbose > 1)
	msg_info("%s: sq=%d cq=%d connect from [%s]:%s",
		 myname, psc_post_queue_length, psc_check_queue_length,
		 smtp_client_addr.buf, smtp_client_port.buf);

    /*
     * Look up the local SMTP server address and port.
     */
    if (getsockname(vstream_fileno(smtp_client_stream), (struct sockaddr *)
		    & addr_storage, &addr_storage_len) < 0) {
	msg_warn("getsockname: %m -- dropping this connection");
	PSC_SERVICE_DISCONNECT_AND_RETURN(smtp_client_stream);
    }

    /*
     * Convert the local SMTP server address and port to printable form for
     * logging and access control.
     */
    if ((aierr = sockaddr_to_hostaddr((struct sockaddr *) & addr_storage,
				      addr_storage_len, &smtp_server_addr,
				      &smtp_server_port, 0)) != 0) {
	msg_warn("cannot convert server address/port to string: %s"
		 " -- dropping this connection",
		 MAI_STRERROR(aierr));
	PSC_SERVICE_DISCONNECT_AND_RETURN(smtp_client_stream);
    }
    if (strncasecmp("::ffff:", smtp_server_addr.buf, 7) == 0)
	memmove(smtp_server_addr.buf, smtp_server_addr.buf + 7,
		sizeof(smtp_server_addr.buf) - 7);

    msg_info("CONNECT from [%s]:%s to [%s]:%s",
	     smtp_client_addr.buf, smtp_client_port.buf,
	     smtp_server_addr.buf, smtp_server_port.buf);

    /*
     * Bundle up all the loose session pieces. This zeroes all flags and time
     * stamps.
     */
    state = psc_new_session_state(smtp_client_stream, smtp_client_addr.buf,
				  smtp_client_port.buf);

    /*
     * Reply with 421 when the client has too many open connections.
     */
    if (var_psc_cconn_limit > 0
	&& state->client_concurrency > var_psc_cconn_limit) {
	msg_info("NOQUEUE: reject: CONNECT from [%s]:%s: too many connections",
		 state->smtp_client_addr, state->smtp_client_port);
	PSC_DROP_SESSION_STATE(state,
			       "421 4.7.0 Error: too many connections\r\n");
	return;
    }

    /*
     * Reply with 421 when we can't forward more connections.
     */
    if (var_psc_post_queue_limit > 0
	&& psc_post_queue_length >= var_psc_post_queue_limit) {
	msg_info("NOQUEUE: reject: CONNECT from [%s]:%s: all server ports busy",
		 state->smtp_client_addr, state->smtp_client_port);
	PSC_DROP_SESSION_STATE(state,
			       "421 4.3.2 All server ports are busy\r\n");
	return;
    }

    /*
     * The permanent white/blacklist has highest precedence.
     */
    if (psc_acl != 0) {
	switch (psc_acl_eval(state, psc_acl, VAR_PSC_ACL)) {

	    /*
	     * Permanently blacklisted.
	     */
	case PSC_ACL_ACT_BLACKLIST:
	    msg_info("BLACKLISTED [%s]:%s", PSC_CLIENT_ADDR_PORT(state));
	    PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_BLIST_FAIL);
	    switch (psc_blist_action) {
	    case PSC_ACT_DROP:
		PSC_DROP_SESSION_STATE(state,
			     "521 5.3.2 Service currently unavailable\r\n");
		return;
	    case PSC_ACT_ENFORCE:
		PSC_ENFORCE_SESSION_STATE(state,
			     "550 5.3.2 Service currently unavailable\r\n");
		break;
	    case PSC_ACT_IGNORE:
		PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_BLIST_FAIL);

		/*
		 * Not: PSC_PASS_SESSION_STATE. Repeat this test the next
		 * time.
		 */
		break;
	    default:
		msg_panic("%s: unknown blacklist action value %d",
			  myname, psc_blist_action);
	    }
	    break;

	    /*
	     * Permanently whitelisted.
	     */
	case PSC_ACL_ACT_WHITELIST:
	    msg_info("WHITELISTED [%s]:%s", PSC_CLIENT_ADDR_PORT(state));
	    psc_conclude(state);
	    return;

	    /*
	     * Other: dunno (don't know) or error.
	     */
	default:
	    break;
	}
    }

    /*
     * The temporary whitelist (i.e. the postscreen cache) has the lowest
     * precedence. This cache contains information about the results of prior
     * tests. Whitelist the client when all enabled test results are still
     * valid.
     */
    if ((state->flags & PSC_STATE_MASK_ANY_FAIL) == 0
	&& psc_cache_map != 0
	&& (stamp_str = psc_cache_lookup(psc_cache_map, state->smtp_client_addr)) != 0) {
	saved_flags = state->flags;
	psc_parse_tests(state, stamp_str, event_time());
	state->flags |= saved_flags;
	if (msg_verbose)
	    msg_info("%s: cached + recent flags: %s",
		     myname, psc_print_state_flags(state->flags, myname));
	if ((state->flags & PSC_STATE_MASK_ANY_TODO_FAIL) == 0) {
	    msg_info("PASS OLD [%s]:%s", PSC_CLIENT_ADDR_PORT(state));
	    psc_conclude(state);
	    return;
	}
    } else {
	saved_flags = state->flags;
	psc_new_tests(state);
	state->flags |= saved_flags;
	if (msg_verbose)
	    msg_info("%s: new + recent flags: %s",
		     myname, psc_print_state_flags(state->flags, myname));
    }

    /*
     * Don't whitelist clients that connect to backup MX addresses. Fail
     * "closed" on error.
     */
    if (addr_match_list_match(psc_wlist_if, smtp_server_addr.buf) == 0) {
	state->flags |= (PSC_STATE_FLAG_WLIST_FAIL | PSC_STATE_FLAG_NOFORWARD);
	msg_info("WHITELIST VETO [%s]:%s", PSC_CLIENT_ADDR_PORT(state));
    }

    /*
     * Reply with 421 when we can't analyze more connections. That also means
     * no deep protocol tests when the noforward flag is raised.
     */
    if (var_psc_pre_queue_limit > 0
	&& psc_check_queue_length - psc_post_queue_length
	>= var_psc_pre_queue_limit) {
	msg_info("reject: connect from [%s]:%s: all screening ports busy",
		 state->smtp_client_addr, state->smtp_client_port);
	PSC_DROP_SESSION_STATE(state,
			       "421 4.3.2 All screening ports are busy\r\n");
	return;
    }

    /*
     * If the client has no up-to-date results for some tests, do those tests
     * first. Otherwise, skip the tests and hand off the connection.
     */
    if (state->flags & PSC_STATE_MASK_EARLY_TODO)
	psc_early_tests(state);
    else if (state->flags & (PSC_STATE_MASK_SMTPD_TODO | PSC_STATE_FLAG_NOFORWARD))
	psc_smtpd_tests(state);
    else
	psc_conclude(state);
}