/* * Forks a bsdtcp to the host listed in rc->hostname * Returns negative on error, with an errmsg in rc->errmsg. */ static int runbsdtcp( struct sec_handle * rh, in_port_t port) { int server_socket; in_port_t my_port; struct tcp_conn * rc = rh->rc; set_root_privs(1); server_socket = stream_client_privileged(rc->hostname, port, STREAM_BUFSIZE, STREAM_BUFSIZE, &my_port, 0); set_root_privs(0); if(server_socket < 0) { security_seterror(&rh->sech, "%s", strerror(errno)); return -1; } if(my_port >= IPPORT_RESERVED) { security_seterror(&rh->sech, _("did not get a reserved port: %d"), my_port); } rc->read = rc->write = server_socket; return 0; }
/* * Forks a bsdtcp to the host listed in rc->hostname * Returns negative on error, with an errmsg in rc->errmsg. */ static int runbsdtcp( struct sec_handle * rh, const char *src_ip, in_port_t port) { int server_socket; in_port_t my_port; struct tcp_conn * rc = rh->rc; server_socket = stream_client_addr(src_ip, rh->next_res, port, STREAM_BUFSIZE, STREAM_BUFSIZE, &my_port, 0, 1); rh->next_res = rh->next_res->ai_next; if(server_socket < 0) { security_seterror(&rh->sech, "%s", strerror(errno)); return -1; } if(my_port >= IPPORT_RESERVED) { security_seterror(&rh->sech, _("did not get a reserved port: %d"), my_port); } rc->read = rc->write = server_socket; return 0; }
/* * Return a connected stream */ static void * bsd_stream_client( void * h, int id) { struct sec_stream *bs = NULL; struct sec_handle *bh = h; #ifdef DUMPER_SOCKET_BUFFERING int rcvbuf = sizeof(bs->databuf) * 2; #endif assert(bh != NULL); bs = g_new0(struct sec_stream, 1); security_streaminit(&bs->secstr, &bsd_security_driver); bs->fd = stream_client(bh->hostname, (in_port_t)id, STREAM_BUFSIZE, STREAM_BUFSIZE, &bs->port, 0); if (bs->fd < 0) { security_seterror(&bh->sech, _("can't connect stream to %s port %d: %s"), bh->hostname, id, strerror(errno)); amfree(bs->secstr.error); amfree(bs); return (NULL); } bs->socket = -1; /* we're a client */ bs->ev_read = NULL; #ifdef DUMPER_SOCKET_BUFFERING setsockopt(bs->fd, SOL_SOCKET, SO_RCVBUF, (void *)&rcvbuf, sizeof(rcvbuf)); #endif return (bs); }
/* * The request send state. Here, the packet is actually transmitted * across the network. After setting up timeouts, the request * moves to the acknowledgement wait state. We return from the state * machine at this point, and let the request be received from the network. */ static p_action_t s_sendreq( proto_t * p, p_action_t action, pkt_t * pkt) { assert(p != NULL); (void)action; /* Quiet unused parameter warning */ (void)pkt; /* Quiet unused parameter warning */ if (security_sendpkt(p->security_handle, &p->req) < 0) { /* XXX should retry */ security_seterror(p->security_handle, _("error sending REQ: %s"), security_geterror(p->security_handle)); return (PA_ABORT); } /* * Remember when this request was first sent */ p->curtime = CURTIME; /* * Move to the ackwait state */ p->state = s_ackwait; p->timeout = ACK_WAIT; return (PA_PENDING); }
/* * Create the server end of a stream. For bsd, this means setup a tcp * socket for receiving a connection. */ static void * bsd_stream_server( void * h) { struct sec_stream *bs = NULL; struct sec_handle *bh = h; assert(bh != NULL); bs = g_new0(struct sec_stream, 1); security_streaminit(&bs->secstr, &bsd_security_driver); bs->socket = stream_server(SU_GET_FAMILY(&bh->udp->peer), &bs->port, (size_t)STREAM_BUFSIZE, (size_t)STREAM_BUFSIZE, 0); if (bs->socket < 0) { security_seterror(&bh->sech, _("can't create server stream: %s"), strerror(errno)); amfree(bs->secstr.error); amfree(bs); return (NULL); } bs->fd = -1; bs->ev_read = NULL; return (bs); }
/* * Forks a krb5 to the host listed in rc->hostname * Returns negative on error, with an errmsg in rc->errmsg. */ static int runkrb5( struct sec_handle * rh) { struct servent * sp; int server_socket; in_port_t my_port, port; struct tcp_conn * rc = rh->rc; const char *err; if ((sp = getservbyname(AMANDA_KRB5_SERVICE_NAME, "tcp")) == NULL) port = htons(AMANDA_KRB5_DEFAULT_PORT); else port = sp->s_port; if ((err = get_tgt(keytab_name, principal_name)) != NULL) { security_seterror(&rh->sech, "%s: could not get TGT: %s", rc->hostname, err); return -1; } set_root_privs(1); server_socket = stream_client(rc->hostname, (in_port_t)(ntohs(port)), STREAM_BUFSIZE, STREAM_BUFSIZE, &my_port, 0); set_root_privs(0); if(server_socket < 0) { security_seterror(&rh->sech, "%s", strerror(errno)); return -1; } rc->read = rc->write = server_socket; if (gss_client(rh) < 0) { return -1; } return 0; }
/* * This is a callback for security_connect. After the security layer * has initiated a connection to the given host, this will be called * with a security_handle_t. * * On error, the security_status_t arg will reflect errors which can * be had via security_geterror on the handle. */ static void connect_callback( void *cookie) { proto_t *p = cookie; assert(p != NULL); if (p->event_handle) { event_release(p->event_handle); p->event_handle = 0; } proto_debug(1, _("protocol: connect_callback: p %p\n"), p); switch (p->status) { case S_OK: state_machine(p, PA_START, NULL); break; case S_TIMEOUT: security_seterror(p->security_handle, _("timeout during connect")); /* FALLTHROUGH */ case S_ERROR: /* * For timeouts or errors, retry a few times, waiting CONNECT_WAIT * seconds between each attempt. If they all fail, just return * an error back to the caller. */ if (--p->connecttries == 0) { state_machine(p, PA_ABORT, NULL); } else { proto_debug(1, _("protocol: connect_callback: p %p: retrying %s\n"), p, p->hostname); security_close(p->security_handle); /* XXX overload p->security handle to hold the event handle */ p->security_handle = (security_handle_t *)event_create(CONNECT_WAIT, EV_TIME, connect_wait_callback, p); event_activate((event_handle_t *) p->security_handle); } break; default: assert(0); break; } }
/* * Negotiate a krb5 gss context from the client end. */ static int gss_client( struct sec_handle *rh) { struct sec_stream *rs = rh->rs; struct tcp_conn *rc = rs->rc; gss_buffer_desc send_tok, recv_tok, AA; gss_OID doid; OM_uint32 maj_stat, min_stat; unsigned int ret_flags; int rval = -1; int rvalue; gss_name_t gss_name; char *errmsg = NULL; auth_debug(1, "gss_client\n"); send_tok.value = vstralloc("host/", rs->rc->hostname, NULL); send_tok.length = strlen(send_tok.value) + 1; maj_stat = gss_import_name(&min_stat, &send_tok, GSS_C_NULL_OID, &gss_name); if (maj_stat != (OM_uint32)GSS_S_COMPLETE) { security_seterror(&rh->sech, _("can't import name %s: %s"), (char *)send_tok.value, gss_error(maj_stat, min_stat)); amfree(send_tok.value); return (-1); } amfree(send_tok.value); rc->gss_context = GSS_C_NO_CONTEXT; maj_stat = gss_display_name(&min_stat, gss_name, &AA, &doid); dbprintf(_("gss_name %s\n"), (char *)AA.value); /* * Perform the context-establishement loop. * * Every generated token is stored in send_tok which is then * transmitted to the server; every received token is stored in * recv_tok (empty on the first pass) to be processed by * the next call to gss_init_sec_context. * * GSS-API guarantees that send_tok's length will be non-zero * if and only if the server is expecting another token from us, * and that gss_init_sec_context returns GSS_S_CONTINUE_NEEDED if * and only if the server has another token to send us. */ recv_tok.value = NULL; for (recv_tok.length = 0;;) { min_stat = 0; maj_stat = gss_init_sec_context(&min_stat, GSS_C_NO_CREDENTIAL, &rc->gss_context, gss_name, GSS_C_NULL_OID, (OM_uint32)GSS_C_MUTUAL_FLAG|GSS_C_REPLAY_FLAG, 0, NULL, /* no channel bindings */ (recv_tok.length == 0 ? GSS_C_NO_BUFFER : &recv_tok), NULL, /* ignore mech type */ &send_tok, &ret_flags, NULL); /* ignore time_rec */ if (recv_tok.length != 0) { amfree(recv_tok.value); recv_tok.length = 0; } if (maj_stat != (OM_uint32)GSS_S_COMPLETE && maj_stat != (OM_uint32)GSS_S_CONTINUE_NEEDED) { security_seterror(&rh->sech, _("error getting gss context: %s %s"), gss_error(maj_stat, min_stat), (char *)send_tok.value); goto done; } /* * Send back the response */ if (send_tok.length != 0 && tcpm_send_token(rc, rc->write, rs->handle, &errmsg, send_tok.value, send_tok.length) < 0) { security_seterror(&rh->sech, "%s", rc->errmsg); gss_release_buffer(&min_stat, &send_tok); goto done; } gss_release_buffer(&min_stat, &send_tok); /* * If we need to continue, then register for more packets */ if (maj_stat != (OM_uint32)GSS_S_CONTINUE_NEEDED) break; rvalue = tcpm_recv_token(rc, rc->read, &rc->handle, &rc->errmsg, (void *)&recv_tok.value, (ssize_t *)&recv_tok.length, 60); if (rvalue <= 0) { if (rvalue < 0) security_seterror(&rh->sech, _("recv error in gss loop: %s"), rc->errmsg); else security_seterror(&rh->sech, _("EOF in gss loop")); goto done; } } rval = 0; rc->auth = 1; done: gss_release_name(&min_stat, &gss_name); return (rval); }
/* * krb5 version of a security handle allocator. Logically sets * up a network "connection". */ static void krb5_connect( const char *hostname, char * (*conf_fn)(char *, void *), void (*fn)(void *, security_handle_t *, security_status_t), void * arg, void * datap) { struct sec_handle *rh; int result; char *canonname; assert(fn != NULL); assert(hostname != NULL); auth_debug(1, "krb5: krb5_connect: %s\n", hostname); krb5_init(); rh = alloc(sizeof(*rh)); security_handleinit(&rh->sech, &krb5_security_driver); rh->hostname = NULL; rh->rs = NULL; rh->ev_timeout = NULL; rh->rc = NULL; result = resolve_hostname(hostname, 0, NULL, &canonname); if(result != 0) { dbprintf(_("resolve_hostname(%s): %s\n"), hostname, gai_strerror(result)); security_seterror(&rh->sech, _("resolve_hostname(%s): %s\n"), hostname, gai_strerror(result)); (*fn)(arg, &rh->sech, S_ERROR); return; } if (canonname == NULL) { dbprintf(_("resolve_hostname(%s) did not return a canonical name\n"), hostname); security_seterror(&rh->sech, _("resolve_hostname(%s) did not return a canonical name\n"), hostname); (*fn)(arg, &rh->sech, S_ERROR); return; } rh->hostname = canonname; /* will be replaced */ canonname = NULL; /* steal reference */ rh->rs = tcpma_stream_client(rh, newhandle++); rh->rc->conf_fn = conf_fn; rh->rc->datap = datap; rh->rc->recv_security_ok = NULL; rh->rc->prefix_packet = NULL; if (rh->rs == NULL) goto error; amfree(rh->hostname); rh->hostname = stralloc(rh->rs->rc->hostname); #ifdef AMANDA_KEYTAB keytab_name = AMANDA_KEYTAB; #else if(conf_fn) { keytab_name = conf_fn("krb5keytab", datap); } #endif #ifdef AMANDA_PRINCIPAL principal_name = AMANDA_PRINCIPAL; #else if(conf_fn) { principal_name = conf_fn("krb5principal", datap); } #endif /* * We need to open a new connection. * * XXX need to eventually limit number of outgoing connections here. */ if(rh->rc->read == -1) { if (runkrb5(rh) < 0) goto error; rh->rc->refcnt++; } /* * The socket will be opened async so hosts that are down won't * block everything. We need to register a write event * so we will know when the socket comes alive. * * Overload rh->rs->ev_read to provide a write event handle. * We also register a timeout. */ rh->fn.connect = fn; rh->arg = arg; rh->rs->ev_read = event_register((event_id_t)(rh->rs->rc->write), EV_WRITEFD, sec_connect_callback, rh); rh->ev_timeout = event_register(CONNECT_TIMEOUT, EV_TIME, sec_connect_timeout, rh); amfree(canonname); return; error: amfree(canonname); (*fn)(arg, &rh->sech, S_ERROR); }
/* like sec_accept, but first it gets the remote system's hostname */ static void ssh_accept( const security_driver_t *driver, char *(*conf_fn)(char *, void *), int in, int out, void (*fn)(security_handle_t *, pkt_t *), void *datap) { struct sec_handle *rh; struct tcp_conn *rc = sec_tcp_conn_get(NULL, "", 0); char *ssh_connection, *p; char *errmsg = NULL; sockaddr_union addr; int result; /* "Accepting" an SSH connection means that amandad was invoked via sshd, so * we should have anSSH_CONNECTION env var. If not, then this probably isn't * a real SSH connection and we should bail out. */ ssh_connection = getenv("SSH_CONNECTION"); if (!ssh_connection) { errmsg = g_strdup("$SSH_CONNECTION not set - was amandad started by sshd?"); goto error; } /* make a local copy, to munge */ ssh_connection = g_strdup(ssh_connection); /* strip off the first component - the ASCII IP address */ if ((p = strchr(ssh_connection, ' ')) == NULL) { errmsg = g_strdup("$SSH_CONNECTION malformed"); goto error; } *p = '\0'; /* ---- everything from here on is just a warning, leaving hostname at "" */ SU_INIT(&addr, AF_INET); /* turn the string address into a sockaddr */ if ((result = str_to_sockaddr(ssh_connection, &addr)) != 1) { if (result == 0) { g_warning("Could not parse peer address %s", ssh_connection); } else { g_warning("Parsing peer address %s: %s", ssh_connection, gai_strerror(result)); } goto done; } /* find the hostname */ result = getnameinfo((struct sockaddr *)&addr, SS_LEN(&addr), rc->hostname, sizeof(rc->hostname), NULL, 0, 0); if (result != 0) { g_warning("Could not get hostname for SSH client %s: %s", ssh_connection, gai_strerror(result)); goto done; } /* and verify it */ if (check_name_give_sockaddr(rc->hostname, (struct sockaddr *)&addr, &errmsg) < 0) { rc->hostname[0] = '\0'; /* null out the bad hostname */ g_warning("Checking SSH client DNS: %s", errmsg); amfree(errmsg); goto done; } done: g_free(ssh_connection); rc->read = in; rc->write = out; rc->accept_fn = fn; rc->driver = driver; rc->conf_fn = conf_fn; rc->datap = datap; sec_tcp_conn_read(rc); return; error: if (ssh_connection) g_free(ssh_connection); /* make up a fake handle for the error */ rh = g_new0(struct sec_handle, 1); security_handleinit(&rh->sech, driver); security_seterror((security_handle_t*)rh, "ssh_accept: %s", errmsg); amfree(errmsg); (*fn)(&rh->sech, NULL); }
/* * ssh version of a security handle allocator. Logically sets * up a network "connection". */ static void ssh_connect( const char * hostname, char * (*conf_fn)(char *, void *), void (*fn)(void *, security_handle_t *, security_status_t), void * arg, void * datap) { struct sec_handle *rh; char *amandad_path=NULL, *client_username=NULL, *ssh_keys=NULL; char *client_port = NULL; assert(fn != NULL); assert(hostname != NULL); auth_debug(1, "ssh_connect: %s\n", hostname); rh = g_new0(struct sec_handle, 1); security_handleinit(&rh->sech, &ssh_security_driver); rh->dle_hostname = g_strdup(hostname); rh->hostname = NULL; rh->rs = NULL; rh->ev_timeout = NULL; rh->rc = NULL; rh->hostname = g_strdup(hostname); rh->rs = tcpma_stream_client(rh, newhandle++); if (rh->rc == NULL) goto error; rh->rc->conf_fn = conf_fn; rh->rc->datap = datap; if (rh->rs == NULL) goto error; amfree(rh->hostname); rh->hostname = g_strdup(rh->rs->rc->hostname); /* * We need to open a new connection. * * XXX need to eventually limit number of outgoing connections here. */ if(conf_fn) { char *port_str; amandad_path = conf_fn("amandad_path", datap); client_username = conf_fn("client_username", datap); ssh_keys = conf_fn("ssh_keys", datap); port_str = conf_fn("client_port", datap); if (port_str && strlen(port_str) >= 1) { client_port = port_str; } } if(rh->rc->read == -1) { if (runssh(rh->rs->rc, amandad_path, client_username, ssh_keys, client_port) < 0) { security_seterror(&rh->sech, _("can't connect to %s: %s"), hostname, rh->rs->rc->errmsg); goto error; } rh->rc->refcnt++; } /* * The socket will be opened async so hosts that are down won't * block everything. We need to register a write event * so we will know when the socket comes alive. * * Overload rh->rs->ev_read to provide a write event handle. * We also register a timeout. */ rh->fn.connect = fn; rh->arg = arg; rh->rs->rc->ev_write = event_register((event_id_t)rh->rs->rc->write, EV_WRITEFD, sec_connect_callback, rh); rh->ev_timeout = event_register((event_id_t)CONNECT_TIMEOUT, EV_TIME, sec_connect_timeout, rh); return; error: (*fn)(arg, &rh->sech, S_ERROR); }
/* * bsdtcp version of a security handle allocator. Logically sets * up a network "connection". */ static void bsdtcp_connect( const char *hostname, char * (*conf_fn)(char *, void *), void (*fn)(void *, security_handle_t *, security_status_t), void * arg, void * datap) { struct sec_handle *rh; int result; char *canonname; char *service; char *src_ip; in_port_t port; struct addrinfo *res = NULL; assert(fn != NULL); assert(hostname != NULL); (void)conf_fn; /* Quiet unused parameter warning */ (void)datap; /* Quiet unused parameter warning */ auth_debug(1, _("bsdtcp: bsdtcp_connect: %s\n"), hostname); rh = g_new0(struct sec_handle, 1); security_handleinit(&rh->sech, &bsdtcp_security_driver); rh->dle_hostname = g_strdup(hostname); rh->hostname = NULL; rh->rs = NULL; rh->ev_timeout = NULL; rh->rc = NULL; result = resolve_hostname(hostname, SOCK_STREAM, &res, &canonname); if(result != 0) { dbprintf(_("resolve_hostname(%s): %s\n"), hostname, gai_strerror(result)); security_seterror(&rh->sech, _("resolve_hostname(%s): %s"), hostname, gai_strerror(result)); (*fn)(arg, &rh->sech, S_ERROR); if (res) freeaddrinfo(res); return; } if (canonname == NULL) { dbprintf(_("resolve_hostname(%s) did not return a canonical name\n"), hostname); security_seterror(&rh->sech, _("resolve_hostname(%s) did not return a canonical name"), hostname); (*fn)(arg, &rh->sech, S_ERROR); if (res) freeaddrinfo(res); return; } rh->hostname = canonname; /* will be replaced */ canonname = NULL; /* steal reference */ rh->rs = tcpma_stream_client(rh, newhandle++); if (rh->rc == NULL) goto error; rh->rc->recv_security_ok = &bsd_recv_security_ok; rh->rc->prefix_packet = &bsd_prefix_packet; rh->rc->need_priv_port = 1; if (rh->rs == NULL) goto error; amfree(rh->hostname); rh->hostname = g_strdup(rh->rs->rc->hostname); if (conf_fn) { service = conf_fn("client_port", datap); if (!service || strlen(service) <= 1) service = AMANDA_SERVICE_NAME; src_ip = conf_fn("src_ip", datap); } else { service = AMANDA_SERVICE_NAME; src_ip = NULL; } port = find_port_for_service(service, "tcp"); if (port == 0) { security_seterror(&rh->sech, _("%s/tcp unknown protocol"), service); goto error; } /* * We need to open a new connection. * * XXX need to eventually limit number of outgoing connections here. */ rh->res = res; rh->next_res = res; rh->src_ip = src_ip; rh->port = port; if(rh->rc->read == -1) { int result = -1; while (rh->next_res) { result = runbsdtcp(rh, rh->src_ip, rh->port); if (result >=0 ) break; } if (result < 0) goto error; rh->rc->refcnt++; } /* * The socket will be opened async so hosts that are down won't * block everything. We need to register a write event * so we will know when the socket comes alive. * * Overload rh->rs->ev_read to provide a write event handle. * We also register a timeout. */ rh->fn.connect = &bsdtcp_fn_connect; rh->arg = rh; rh->connect_callback = fn; rh->connect_arg = arg; g_mutex_lock(security_mutex); rh->rs->rc->ev_write = event_create((event_id_t)(rh->rs->rc->write), EV_WRITEFD, sec_connect_callback, rh); rh->ev_timeout = event_create(CONNECT_TIMEOUT, EV_TIME, sec_connect_timeout, rh); event_activate(rh->rs->rc->ev_write); event_activate(rh->ev_timeout); g_mutex_unlock(security_mutex); return; error: if (res) { freeaddrinfo(res); } rh->res = NULL; rh->next_res = NULL; (*fn)(arg, &rh->sech, S_ERROR); }
/* * Open a ssl connection to the host listed in rc->hostname * Returns negative on error, with an errmsg in rc->errmsg. */ static int runssl( struct sec_handle * rh, in_port_t port, char *src_ip, char *ssl_fingerprint_file, char *ssl_cert_file, char *ssl_key_file, char *ssl_ca_cert_file, char *ssl_cipher_list, int ssl_check_certificate_host) { int my_socket; in_port_t my_port; struct tcp_conn *rc = rh->rc; int err; X509 *remote_cert; sockaddr_union sin; socklen_t_equiv len; if (!ssl_key_file) { security_seterror(&rh->sech, _("ssl-key-file must be set")); return -1; } if (!ssl_cert_file) { security_seterror(&rh->sech, _("ssl-cert-file must be set")); return -1; } my_socket = stream_client(src_ip, rc->hostname, port, STREAM_BUFSIZE, STREAM_BUFSIZE, &my_port, 0); if(my_socket < 0) { security_seterror(&rh->sech, "%s", strerror(errno)); return -1; } rc->read = rc->write = my_socket; len = sizeof(sin); if (getpeername(my_socket, (struct sockaddr *)&sin, &len) < 0) { security_seterror(&rh->sech, _("getpeername returned: %s\n"), strerror(errno)); return -1; } copy_sockaddr(&rc->peer, &sin); init_ssl(); /* Create an SSL_CTX structure */ rc->ctx = SSL_CTX_new(SSLv3_client_method()); if (!rc->ctx) { security_seterror(&rh->sech, "%s", ERR_error_string(ERR_get_error(), NULL)); return -1; } SSL_CTX_set_mode(rc->ctx, SSL_MODE_AUTO_RETRY); if (ssl_cipher_list) { g_debug("Set ssl_cipher_list to %s", ssl_cipher_list); if (SSL_CTX_set_cipher_list(rc->ctx, ssl_cipher_list) == 0) { security_seterror(&rh->sech, "%s", ERR_error_string(ERR_get_error(), NULL)); return -1; } } /* Load the private-key corresponding to the remote certificate */ g_debug("Loading ssl-key-file private-key %s", ssl_key_file); if (SSL_CTX_use_PrivateKey_file(rc->ctx, ssl_key_file, SSL_FILETYPE_PEM) <= 0) { security_seterror(&rh->sech, "%s", ERR_error_string(ERR_get_error(), NULL)); return -1; } /* Load the me certificate into the SSL_CTX structure */ g_debug("Loading ssl-cert-file certificate %s", ssl_cert_file); if (SSL_CTX_use_certificate_file(rc->ctx, ssl_cert_file, SSL_FILETYPE_PEM) <= 0) { security_seterror(&rh->sech, "%s", ERR_error_string(ERR_get_error(), NULL)); return -1; } /* Check if the remote certificate and private-key matches */ if (ssl_cert_file) { if (!SSL_CTX_check_private_key(rc->ctx)) { security_seterror(&rh->sech, _("Private key does not match the certificate public key")); return -1; } } if (ssl_ca_cert_file) { /* Load the RSA CA certificate into the SSL_CTX structure */ /* This will allow this remote to verify the me's */ /* certificate. */ g_debug("Loading ssl-ca-cert-file ca %s", ssl_ca_cert_file); if (!SSL_CTX_load_verify_locations(rc->ctx, ssl_ca_cert_file, NULL)) { security_seterror(&rh->sech, "%s", ERR_error_string(ERR_get_error(), NULL)); return -1; } } else { g_debug(_("no ssl-ca-cert-file defined")); } /* Set flag in context to require peer (me) certificate */ /* verification */ if (ssl_ca_cert_file) { g_debug("Enabling certification verification"); SSL_CTX_set_verify(rc->ctx, SSL_VERIFY_PEER, NULL); SSL_CTX_set_verify_depth(rc->ctx, 1); } else { g_debug("Not enabling certification verification"); } /* ----------------------------------------------- */ rc->ssl = SSL_new(rc->ctx); if (!rc->ssl) { security_seterror(&rh->sech, _("SSL_new failed: %s"), ERR_error_string(ERR_get_error(), NULL)); return -1; } SSL_set_connect_state(rc->ssl); /* Assign the socket into the SSL structure (SSL and socket without BIO) */ SSL_set_fd(rc->ssl, my_socket); /* Perform SSL Handshake on the SSL remote */ err = SSL_connect(rc->ssl); if (err == -1) { security_seterror(&rh->sech, _("SSL_connect failed: %s"), ERR_error_string(ERR_get_error(), NULL)); return -1; } /* Get the me's certificate (optional) */ remote_cert = SSL_get_peer_certificate(rc->ssl); if (remote_cert == NULL) { security_seterror(&rh->sech, _("server have no certificate")); return -1; } else { char *str; str = X509_NAME_oneline(X509_get_subject_name(remote_cert), 0, 0); auth_debug(1, _("\t subject: %s\n"), str); amfree (str); str = X509_NAME_oneline(X509_get_issuer_name(remote_cert), 0, 0); auth_debug(1, _("\t issuer: %s\n"), str); amfree(str); if (ssl_check_certificate_host) { int loc = -1; char *errmsg = NULL; X509_NAME *x509_name = X509_get_subject_name(remote_cert); loc = X509_NAME_get_index_by_NID(x509_name, NID_commonName, loc); if (loc != -1) { X509_NAME_ENTRY *x509_entry = X509_NAME_get_entry(x509_name, loc); ASN1_STRING *asn1_string = X509_NAME_ENTRY_get_data(x509_entry); char *cert_hostname = (char *)ASN1_STRING_data(asn1_string); auth_debug(1, "common_name: %s\n", cert_hostname); if (check_name_give_sockaddr((char*)cert_hostname, (struct sockaddr *)&rc->peer, &errmsg) < 0) { security_seterror(&rh->sech, _("Common name of certicate (%s) doesn't resolv to IP (%s): %s"), cert_hostname, str_sockaddr(&rc->peer), errmsg); amfree(errmsg); return -1; } auth_debug(1, _("Certificate common name (%s) resolve to IP (%s)\n"), cert_hostname, str_sockaddr(&rc->peer)); } else { security_seterror(&rh->sech, _("Certificate have no common name")); g_debug("Certificate have no common name"); return -1; } } /* if (ssl_dir) { if (!ssl_fingerprint_file || ssl_fingerprint_file == '\0') { struct stat statbuf; ssl_fingerprint_file = g_strdup_printf("%s/remote/%s/fingerprint", ssl_dir, cert_hostname); if (stat(ssl_fingerprint_file, &statbuf) == -1) { g_free(ssl_fingerprint_file); ssl_fingerprint_file = NULL; } } } */ if (ssl_fingerprint_file) { g_debug(_("run_ssl: Loading ssl-fingerprint-file %s"), ssl_fingerprint_file); str = validate_fingerprints(remote_cert, ssl_fingerprint_file); if (str) { security_seterror(&rh->sech, "%s", str); amfree(str); return -1; } } X509_free (remote_cert); } g_debug(_("SSL_cipher: %s"), SSL_get_cipher(rc->ssl)); return 0; }
/* * ssl version of a security handle allocator. Logically sets * up a network "connection". */ static void ssl_connect( const char *hostname, char * (*conf_fn)(char *, void *), void (*fn)(void *, security_handle_t *, security_status_t), void * arg, void * datap) { struct sec_handle *rh; int result; char *canonname; char *service; in_port_t port; char *src_ip = NULL; char *ssl_dir = NULL; char *ssl_fingerprint_file = NULL; char *ssl_cert_file = NULL; char *ssl_key_file = NULL; char *ssl_ca_cert_file = NULL; char *ssl_cipher_list = NULL; int ssl_check_certificate_host = 1; assert(fn != NULL); assert(hostname != NULL); auth_debug(1, _("ssl: ssl_connect: %s\n"), hostname); rh = g_new0(struct sec_handle, 1); security_handleinit(&rh->sech, &ssl_security_driver); rh->hostname = NULL; rh->rs = NULL; rh->ev_timeout = NULL; rh->rc = NULL; result = resolve_hostname(hostname, 0, NULL, &canonname); if(result != 0) { g_debug(_("resolve_hostname(%s): %s"), hostname, gai_strerror(result)); security_seterror(&rh->sech, _("resolve_hostname(%s): %s\n"), hostname, gai_strerror(result)); (*fn)(arg, &rh->sech, S_ERROR); return; } if (canonname == NULL) { g_debug(_("resolve_hostname(%s) did not return a canonical name"), hostname); security_seterror(&rh->sech, _("resolve_hostname(%s) did not return a canonical name\n"), hostname); (*fn)(arg, &rh->sech, S_ERROR); return; } rh->hostname = canonname; /* will be replaced */ canonname = NULL; /* steal reference */ rh->rs = tcpma_stream_client(rh, newhandle++); rh->rc->recv_security_ok = &bsd_recv_security_ok; rh->rc->prefix_packet = &bsd_prefix_packet; rh->rc->need_priv_port = 0; if (rh->rs == NULL) goto error; amfree(rh->hostname); rh->hostname = g_strdup(rh->rs->rc->hostname); ssl_dir = getconf_str(CNF_SSL_DIR); if (conf_fn) { service = conf_fn("remote_port", datap); if (!service || strlen(service) <= 1) service = AMANDA_SERVICE_NAME; g_debug("Connecting to service '%s'", service); src_ip = conf_fn("src_ip", datap); ssl_fingerprint_file = g_strdup(conf_fn("ssl_fingerprint_file", datap)); ssl_cert_file = g_strdup(conf_fn("ssl_cert_file", datap)); ssl_key_file = g_strdup(conf_fn("ssl_key_file", datap)); ssl_ca_cert_file = g_strdup(conf_fn("ssl_ca_cert_file", datap)); ssl_cipher_list = conf_fn("ssl_cipher_list", datap); ssl_check_certificate_host = atoi(conf_fn("ssl_check_certificate_host", datap)); } else { service = AMANDA_SERVICE_NAME; } if (ssl_dir) { if (!ssl_cert_file || ssl_cert_file == '\0') { ssl_cert_file = g_strdup_printf("%s/me/crt.pem", ssl_dir); } if (!ssl_key_file || ssl_key_file == '\0') { ssl_key_file = g_strdup_printf("%s/me/private/key.pem", ssl_dir); } if (!ssl_ca_cert_file || ssl_ca_cert_file == '\0') { ssl_ca_cert_file = g_strdup_printf("%s/CA/crt.pem", ssl_dir); } if (!ssl_fingerprint_file || ssl_fingerprint_file == '\0') { struct stat statbuf; ssl_fingerprint_file = g_strdup_printf("%s/remote/%s/fingerprint", ssl_dir, rh->hostname); if (stat(ssl_fingerprint_file, &statbuf) == -1) { g_free(ssl_fingerprint_file); ssl_fingerprint_file = NULL; } } } port = find_port_for_service(service, "tcp"); if (port == 0) { security_seterror(&rh->sech, _("%s/tcp unknown protocol"), service); goto error; } /* * We need to open a new connection. */ if(rh->rc->read == -1) { if (runssl(rh, port, src_ip, ssl_fingerprint_file, ssl_cert_file, ssl_key_file, ssl_ca_cert_file, ssl_cipher_list, ssl_check_certificate_host) < 0) goto error; rh->rc->refcnt++; } g_free(ssl_fingerprint_file); g_free(ssl_cert_file); g_free(ssl_key_file); g_free(ssl_ca_cert_file); /* * The socket will be opened async so hosts that are down won't * block everything. We need to register a write event * so we will know when the socket comes alive. * * Overload rh->rs->ev_read to provide a write event handle. * We also register a timeout. */ rh->fn.connect = fn; rh->arg = arg; rh->rs->ev_read = event_register((event_id_t)(rh->rs->rc->write), EV_WRITEFD, sec_connect_callback, rh); rh->ev_timeout = event_register(CONNECT_TIMEOUT, EV_TIME, sec_connect_timeout, rh); return; error: (*fn)(arg, &rh->sech, S_ERROR); }
/* * bsdtcp version of a security handle allocator. Logically sets * up a network "connection". */ static void bsdtcp_connect( const char *hostname, char * (*conf_fn)(char *, void *), void (*fn)(void *, security_handle_t *, security_status_t), void * arg, void * datap) { struct sec_handle *rh; int result; char *canonname; char *service; in_port_t port; assert(fn != NULL); assert(hostname != NULL); (void)conf_fn; /* Quiet unused parameter warning */ (void)datap; /* Quiet unused parameter warning */ auth_debug(1, _("bsdtcp: bsdtcp_connect: %s\n"), hostname); rh = alloc(sizeof(*rh)); security_handleinit(&rh->sech, &bsdtcp_security_driver); rh->hostname = NULL; rh->rs = NULL; rh->ev_timeout = NULL; rh->rc = NULL; result = resolve_hostname(hostname, 0, NULL, &canonname); if(result != 0) { dbprintf(_("resolve_hostname(%s): %s\n"), hostname, gai_strerror(result)); security_seterror(&rh->sech, _("resolve_hostname(%s): %s\n"), hostname, gai_strerror(result)); (*fn)(arg, &rh->sech, S_ERROR); return; } if (canonname == NULL) { dbprintf(_("resolve_hostname(%s) did not return a canonical name\n"), hostname); security_seterror(&rh->sech, _("resolve_hostname(%s) did not return a canonical name\n"), hostname); (*fn)(arg, &rh->sech, S_ERROR); return; } rh->hostname = canonname; /* will be replaced */ canonname = NULL; /* steal reference */ rh->rs = tcpma_stream_client(rh, newhandle++); rh->rc->recv_security_ok = &bsd_recv_security_ok; rh->rc->prefix_packet = &bsd_prefix_packet; if (rh->rs == NULL) goto error; amfree(rh->hostname); rh->hostname = stralloc(rh->rs->rc->hostname); if (conf_fn) { service = conf_fn("client_port", datap); if (strlen(service) <= 1) service = "amanda"; } else { service = "amanda"; } port = find_port_for_service(service, "tcp"); if (port == 0) { security_seterror(&rh->sech, _("%s/tcp unknown protocol"), service); goto error; } /* * We need to open a new connection. * * XXX need to eventually limit number of outgoing connections here. */ if(rh->rc->read == -1) { if (runbsdtcp(rh, port) < 0) goto error; rh->rc->refcnt++; } /* * The socket will be opened async so hosts that are down won't * block everything. We need to register a write event * so we will know when the socket comes alive. * * Overload rh->rs->ev_read to provide a write event handle. * We also register a timeout. */ rh->fn.connect = fn; rh->arg = arg; rh->rs->ev_read = event_register((event_id_t)(rh->rs->rc->write), EV_WRITEFD, sec_connect_callback, rh); rh->ev_timeout = event_register(CONNECT_TIMEOUT, EV_TIME, sec_connect_timeout, rh); return; error: (*fn)(arg, &rh->sech, S_ERROR); }
static void bsd_connect( const char * hostname, char * (*conf_fn)(char *, void *), void (*fn)(void *, security_handle_t *, security_status_t), void * arg, void * datap) { struct sec_handle *bh; in_port_t port = 0; struct timeval sequence_time; int sequence; char *handle; int result; struct addrinfo *res, *res_addr; char *canonname; int result_bind; char *service; assert(hostname != NULL); (void)conf_fn; /* Quiet unused parameter warning */ (void)datap; /* Quiet unused parameter warning */ bh = g_new0(struct sec_handle, 1); bh->proto_handle=NULL; security_handleinit(&bh->sech, &bsd_security_driver); result = resolve_hostname(hostname, SOCK_DGRAM, &res, &canonname); if(result != 0) { dbprintf(_("resolve_hostname(%s): %s\n"), hostname, gai_strerror(result)); security_seterror(&bh->sech, _("resolve_hostname(%s): %s\n"), hostname, gai_strerror(result)); (*fn)(arg, &bh->sech, S_ERROR); return; } if (canonname == NULL) { dbprintf(_("resolve_hostname(%s) did not return a canonical name\n"), hostname); security_seterror(&bh->sech, _("resolve_hostname(%s) did not return a canonical name\n"), hostname); (*fn)(arg, &bh->sech, S_ERROR); if (res) freeaddrinfo(res); return; } if (res == NULL) { dbprintf(_("resolve_hostname(%s): no results\n"), hostname); security_seterror(&bh->sech, _("resolve_hostname(%s): no results\n"), hostname); (*fn)(arg, &bh->sech, S_ERROR); amfree(canonname); return; } for (res_addr = res; res_addr != NULL; res_addr = res_addr->ai_next) { #ifdef WORKING_IPV6 /* IPv6 socket already bound */ if (res_addr->ai_addr->sa_family == AF_INET6 && not_init6 == 0) { break; } /* * Only init the IPv6 socket once */ if (res_addr->ai_addr->sa_family == AF_INET6 && not_init6 == 1) { uid_t euid; dgram_zero(&netfd6.dgram); euid = geteuid(); set_root_privs(1); result_bind = dgram_bind(&netfd6.dgram, res_addr->ai_addr->sa_family, &port); set_root_privs(0); if (result_bind != 0) { continue; } netfd6.handle = NULL; netfd6.pkt.body = NULL; netfd6.recv_security_ok = &bsd_recv_security_ok; netfd6.prefix_packet = &bsd_prefix_packet; /* * We must have a reserved port. Bomb if we didn't get one. */ if (port >= IPPORT_RESERVED) { security_seterror(&bh->sech, _("unable to bind to a reserved port (got port %u)"), (unsigned int)port); (*fn)(arg, &bh->sech, S_ERROR); freeaddrinfo(res); amfree(canonname); return; } not_init6 = 0; bh->udp = &netfd6; break; } #endif /* IPv4 socket already bound */ if (res_addr->ai_addr->sa_family == AF_INET && not_init4 == 0) { break; } /* * Only init the IPv4 socket once */ if (res_addr->ai_addr->sa_family == AF_INET && not_init4 == 1) { uid_t euid; dgram_zero(&netfd4.dgram); euid = geteuid(); set_root_privs(1); result_bind = dgram_bind(&netfd4.dgram, res_addr->ai_addr->sa_family, &port); set_root_privs(0); if (result_bind != 0) { continue; } netfd4.handle = NULL; netfd4.pkt.body = NULL; netfd4.recv_security_ok = &bsd_recv_security_ok; netfd4.prefix_packet = &bsd_prefix_packet; /* * We must have a reserved port. Bomb if we didn't get one. */ if (port >= IPPORT_RESERVED) { security_seterror(&bh->sech, "unable to bind to a reserved port (got port %u)", (unsigned int)port); (*fn)(arg, &bh->sech, S_ERROR); freeaddrinfo(res); amfree(canonname); return; } not_init4 = 0; bh->udp = &netfd4; break; } } if (res_addr == NULL) { dbprintf(_("Can't bind a socket to connect to %s\n"), hostname); security_seterror(&bh->sech, _("Can't bind a socket to connect to %s\n"), hostname); (*fn)(arg, &bh->sech, S_ERROR); amfree(canonname); freeaddrinfo(res); return; } #ifdef WORKING_IPV6 if (res_addr->ai_addr->sa_family == AF_INET6) bh->udp = &netfd6; else #endif bh->udp = &netfd4; auth_debug(1, _("Resolved hostname=%s\n"), canonname); if (conf_fn) { service = conf_fn("client_port", datap); if (!service || strlen(service) <= 1) service = "amanda"; } else { service = "amanda"; } port = find_port_for_service(service, "udp"); if (port == 0) { security_seterror(&bh->sech, _("%s/udp unknown protocol"), service); (*fn)(arg, &bh->sech, S_ERROR); amfree(canonname); freeaddrinfo(res); return; } amanda_gettimeofday(&sequence_time); sequence = (int)sequence_time.tv_sec ^ (int)sequence_time.tv_usec; handle=g_malloc(15); g_snprintf(handle, 14, "000-%08x", (unsigned)newhandle++); if (udp_inithandle(bh->udp, bh, canonname, (sockaddr_union *)res_addr->ai_addr, port, handle, sequence) < 0) { (*fn)(arg, &bh->sech, S_ERROR); amfree(bh->hostname); amfree(bh); } else { (*fn)(arg, &bh->sech, S_OK); } amfree(handle); amfree(canonname); freeaddrinfo(res); }
/* * The acknowledge wait state. We can enter here two ways: * * - the caller has received a packet, located the request for * that packet, and called us with an action of PA_RCVDATA. * * - the caller has determined that a request has timed out, * and has called us with PA_TIMEOUT. * * Here we process the acknowledgment, which usually means that * the client has agreed to our request and is working on it. * It will later send a reply when finished. */ static p_action_t s_ackwait( proto_t * p, p_action_t action, pkt_t * pkt) { assert(p != NULL); /* * The timeout case. If our retry count has gone to zero * fail this request. Otherwise, move to the send state * to retry the request. */ if (action == PA_TIMEOUT) { assert(pkt == NULL); if (--p->reqtries == 0) { security_seterror(p->security_handle, _("timeout waiting for ACK")); return (PA_ABORT); } p->state = s_sendreq; return (PA_CONTINUE); } assert(action == PA_RCVDATA); assert(pkt != NULL); /* * The packet-received state. Determine what kind of * packet we received, and act based on the reply type. */ switch (pkt->type) { /* * Received an ACK. Everything's good. The client is * now working on the request. We queue up again and * wait for the reply. */ case P_ACK: p->state = s_repwait; p->timeout = p->repwait; return (PA_PENDING); /* * Received a NAK. The request failed, so free up the * resources associated with it and return. * * This should NOT return PA_ABORT because it is not a local failure. */ case P_NAK: return (PA_FINISH); /* * The client skipped the ACK, and replied right away. * Move to the reply state to handle it. */ case P_REP: case P_PREP: p->state = s_repwait; return (PA_CONTINUE); /* * Unexpected packet. Requeue this request and hope * we get what we want later. */ default: return (PA_PENDING); } }
/* * The reply wait state. We enter here much like we do with s_ackwait. */ static p_action_t s_repwait( proto_t * p, p_action_t action, pkt_t * pkt) { pkt_t ack; /* * Timeout waiting for a reply. */ if (action == PA_TIMEOUT) { assert(pkt == NULL); /* * If we've blown our timeout limit, free up this packet and * return. */ if (p->resettries == 0 || DROP_DEAD_TIME(p->origtime)) { security_seterror(p->security_handle, _("timeout waiting for REP")); return (PA_ABORT); } /* * We still have some tries left. Resend the request. */ p->resettries--; p->state = s_sendreq; p->reqtries = getconf_int(CNF_REQ_TRIES); return (PA_CONTINUE); } assert(action == PA_RCVDATA); /* Finish if we get a NAK */ if (pkt->type == P_NAK) return (PA_FINISH); /* * We've received some data. If we didn't get a reply, * requeue the packet and retry. Otherwise, acknowledge * the reply, cleanup this packet, and return. */ if (pkt->type != P_REP && pkt->type != P_PREP) return (PA_PENDING); if(pkt->type == P_REP) { pkt_init_empty(&ack, P_ACK); if (security_sendpkt(p->security_handle, &ack) < 0) { /* XXX should retry */ amfree(ack.body); security_seterror(p->security_handle, _("error sending ACK: %s"), security_geterror(p->security_handle)); return (PA_ABORT); } amfree(ack.body); return (PA_FINISH); } else if(pkt->type == P_PREP) { p->timeout = p->repwait - CURTIME + p->curtime + 1; if (p->timeout <= 0) p->timeout = 1; return (PA_CONTPEND); } /* should never go here, shut up compiler warning */ return (PA_FINISH); }
/* * ssh version of a security handle allocator. Logically sets * up a network "connection". */ static void ssh_connect( const char * hostname, char * (*conf_fn)(char *, void *), void (*fn)(void *, security_handle_t *, security_status_t), void * arg, void * datap) { int result; struct sec_handle *rh; char *amandad_path=NULL, *client_username=NULL, *ssh_keys=NULL; assert(fn != NULL); assert(hostname != NULL); auth_debug(1, "ssh_connect: %s\n", hostname); rh = alloc(SIZEOF(*rh)); security_handleinit(&rh->sech, &ssh_security_driver); rh->hostname = NULL; rh->rs = NULL; rh->ev_timeout = NULL; rh->rc = NULL; /* get the canonical hostname */ rh->hostname = NULL; if ((result = resolve_hostname(hostname, 0, NULL, &rh->hostname)) != 0 || rh->hostname == NULL) { security_seterror(&rh->sech, _("ssh_security could not find canonical name for '%s': %s"), hostname, gai_strerror(result)); (*fn)(arg, &rh->sech, S_ERROR); return; } rh->rs = tcpma_stream_client(rh, newhandle++); rh->rc->conf_fn = conf_fn; rh->rc->datap = datap; if (rh->rs == NULL) goto error; amfree(rh->hostname); rh->hostname = stralloc(rh->rs->rc->hostname); /* * We need to open a new connection. * * XXX need to eventually limit number of outgoing connections here. */ if(conf_fn) { amandad_path = conf_fn("amandad_path", datap); client_username = conf_fn("client_username", datap); ssh_keys = conf_fn("ssh_keys", datap); } if(rh->rc->read == -1) { if (runssh(rh->rs->rc, amandad_path, client_username, ssh_keys) < 0) { security_seterror(&rh->sech, _("can't connect to %s: %s"), hostname, rh->rs->rc->errmsg); goto error; } rh->rc->refcnt++; } /* * The socket will be opened async so hosts that are down won't * block everything. We need to register a write event * so we will know when the socket comes alive. * * Overload rh->rs->ev_read to provide a write event handle. * We also register a timeout. */ rh->fn.connect = fn; rh->arg = arg; rh->rs->ev_read = event_register((event_id_t)rh->rs->rc->write, EV_WRITEFD, sec_connect_callback, rh); rh->ev_timeout = event_register((event_id_t)CONNECT_TIMEOUT, EV_TIME, sec_connect_timeout, rh); return; error: (*fn)(arg, &rh->sech, S_ERROR); }
/* * local version of a security handle allocator. Logically sets * up a network "connection". */ static void local_connect( const char * hostname, char * (*conf_fn)(char *, void *), void (*fn)(void *, security_handle_t *, security_status_t), void * arg, void * datap) { struct sec_handle *rh; char *amandad_path=NULL; char *client_username=NULL; char myhostname[MAX_HOSTNAME_LENGTH+1]; assert(fn != NULL); assert(hostname != NULL); auth_debug(1, _("local: local_connect: %s\n"), hostname); rh = g_new0(struct sec_handle, 1); security_handleinit(&rh->sech, &local_security_driver); rh->hostname = NULL; rh->rs = NULL; rh->ev_timeout = NULL; rh->rc = NULL; if (gethostname(myhostname, MAX_HOSTNAME_LENGTH) == -1) { security_seterror(&rh->sech, _("gethostname failed")); (*fn)(arg, &rh->sech, S_ERROR); return; } myhostname[SIZEOF(myhostname)-1] = '\0'; if (strcmp(hostname, myhostname) != 0 && match("^localhost(\\.localdomain)?$", hostname) == 0) { security_seterror(&rh->sech, _("%s: is not local"), hostname); (*fn)(arg, &rh->sech, S_ERROR); return; } rh->hostname = stralloc(hostname); rh->rs = tcpma_stream_client(rh, newhandle++); if (rh->rs == NULL) goto error; amfree(rh->hostname); rh->hostname = stralloc(rh->rs->rc->hostname); /* * We need to open a new connection. * * XXX need to eventually limit number of outgoing connections here. */ if(conf_fn) { amandad_path = conf_fn("amandad_path", datap); client_username = conf_fn("client_username", datap); } if(rh->rc->read == -1) { if (runlocal(rh->rs->rc, amandad_path, client_username) < 0) { security_seterror(&rh->sech, _("can't connect to %s: %s"), hostname, rh->rs->rc->errmsg); goto error; } rh->rc->refcnt++; } /* * The socket will be opened async so hosts that are down won't * block everything. We need to register a write event * so we will know when the socket comes alive. * * Overload rh->rs->ev_read to provide a write event handle. * We also register a timeout. */ rh->fn.connect = fn; rh->arg = arg; rh->rs->ev_read = event_register((event_id_t)rh->rs->rc->write, EV_WRITEFD, sec_connect_callback, rh); rh->ev_timeout = event_register((event_id_t)CONNECT_TIMEOUT, EV_TIME, sec_connect_timeout, rh); return; error: (*fn)(arg, &rh->sech, S_ERROR); amfree(rh->hostname); }