/* * Secure Network Transport Layer Library v2 implementation. * (sntllv2) it superseed all versions before due to the: * - memory consumption * - new features such as pulse emitting * - performance optimization * * This is a proprietary software. See COPYING for further details. * * (c) Askele Group 2013-2015 * */ #include #include #include #include #include #include #include #include #include #include #ifdef WIN32 #include #define EBADE 1 #define NETDB_SUCCESS 0 #else #include #include #include #include #endif #include #include #include #include #include #include #include "internal.h" typedef struct __sntll_bundle_type { void *buf; conn_t *conn; } sntllv2_bundle_t; /* networking helpers */ #ifndef WIN32 int __resolvehost(const char *hostname, char *buf, int buf_len, struct hostent **rhp) { struct hostent *hostbuf ;//= malloc(sizeof(struct hostent)); struct hostent *hp = *rhp = NULL; int herr = 0, hres = 0; hostbuf = malloc(sizeof(struct hostent)); if(!hostbuf) return NO_ADDRESS; hres = gethostbyname_r(hostname, hostbuf, buf, buf_len, &hp, &herr); if(hres) return NO_ADDRESS; *rhp = hp; return NETDB_SUCCESS; } #endif static int __conn_read(conn_t *co, void *buf, size_t buf_len) { int rfd = SSL_get_fd(co->ssl), r; fd_set readset, writeset; int ofcmode, read_blocked = 0, read_blocked_on_write = 0; /* First we make the socket nonblocking */ #ifndef WIN32 ofcmode = fcntl(rfd, F_GETFL,0); ofcmode |= O_NDELAY; if(fcntl(rfd, F_SETFL, ofcmode)) fprintf(stderr, "[sntllv2] (RD)Couldn't make socket nonblocking"); #endif __retry: do { __try_again: r = SSL_read(co->ssl, buf, (int)buf_len); switch(SSL_get_error (co->ssl, r)) { case SSL_ERROR_NONE: return r; break; case SSL_ERROR_WANT_READ: /* get prepare to select */ read_blocked = 1; break; case SSL_ERROR_WANT_WRITE: /* here we blocked on write */ read_blocked_on_write = 1; break; case SSL_ERROR_SYSCALL: if(errno == EAGAIN || errno == EINTR) goto __try_again; else { fprintf(stderr, "[sntllv2] (RD)SSL syscall error.\n"); goto __close_conn; } break; case SSL_ERROR_WANT_CONNECT: case SSL_ERROR_WANT_ACCEPT: fprintf(stderr, "[sntllv2] (RD)SSL negotiation required. Trying again.\n"); goto __try_again; break; case SSL_ERROR_SSL: fprintf(stderr, "[sntllv2] (RD)SSL error occured. Connection will be closed.\n"); goto __close_conn; break; case SSL_ERROR_ZERO_RETURN: fprintf(stderr, "[sntllv2] (RD)SSL connection is cleary closed.\n"); default: __close_conn: fprintf(stderr, "[sntllv2] (RD)Unknown error on %s (errno = %d)\n", co->uuid, errno); return -1; } } while(SSL_pending(co->ssl) && !read_blocked); __select_retry: if(read_blocked) { FD_ZERO(&readset); FD_SET(rfd, &readset); /* waits until something will be ready to read */ r = select(rfd + 1, &readset, NULL, NULL, NULL); if(r < 0) { if(errno == EINTR || errno == EAGAIN) goto __select_retry; fprintf(stderr, "[sntllv2] (RD)Select (%d) on %s\n", errno, co->uuid); return -1; } if(!r) { fprintf(stderr, "[sntllv2] (RD)Nothing to wait for\n"); return 0; } read_blocked = 0; if(r && FD_ISSET(rfd, &readset)) goto __retry; /* try to read again */ } if(read_blocked_on_write) { /* we was blocked on write */ FD_ZERO(&readset); FD_ZERO(&writeset); FD_SET(rfd, &readset); FD_SET(rfd, &writeset); r = select(rfd + 1, &readset, &writeset, NULL, NULL); read_blocked_on_write = 0; if(r && FD_ISSET(rfd, &writeset)) goto __retry; } return r; } static int __conn_write(conn_t *co, void *buf, size_t buf_len) { int r, rfd = SSL_get_fd(co->ssl); fd_set writeset; __retry: r = SSL_write(co->ssl, buf, (int)buf_len); switch(SSL_get_error(co->ssl, r)) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: /* here we should block */ FD_ZERO(&writeset); FD_SET(rfd, &writeset); r = select(rfd + 1, NULL, &writeset, NULL, NULL); if(r && FD_ISSET(rfd, &writeset)) goto __retry; break; case SSL_ERROR_SYSCALL: if(errno == EAGAIN || errno == EINTR) goto __retry; else goto __close_conn; break; default: __close_conn: if(r < 0) { fprintf(stderr, "[sntllv2] (WR)Unknown error on %s (%d)\n", co->uuid, r); return -1; } else return r; } return r; } int _sntll_writemsg(conn_t *co, sxmsg_t *msg) { sntllv2_head_t *head; size_t rd; int r; if(!co || !msg) return SNE_FAILED; /* check message for validity */ head = &msg->mhead; if(head->payload_length && !msg->payload) return SNE_FAILED; /* write the head and payload if applicable */ pthread_mutex_lock(&co->sslinout[1]); rd = __conn_write(co, head, sizeof(sntllv2_head_t)); if(rd < 0) { co->flags |= SNSX_CLOSED; r = SNE_ESSL; } else if(head->payload_length) { rd = __conn_write(co, msg->payload, head->payload_length); /* check up again */ if(rd < 0) { co->flags |= SNSX_CLOSED; r = SNE_ESSL; } } pthread_mutex_unlock(&co->sslinout[1]); if(!(co->flags & SNSX_CLOSED)) r = SNE_SUCCESS; return r; } static sntllv2_bundle_t *__sntll_bundle_create(conn_t *co) { sntllv2_bundle_t *n = malloc(sizeof(sntllv2_bundle_t)); if(!n) return NULL; else memset(n, 0, sizeof(sntllv2_bundle_t)); n->buf = mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if(n->buf == MAP_FAILED) { free(n); return NULL; } n->conn = co; return n; } static void __sntll_bundle_destroy(sntllv2_bundle_t *n) { munmap(n->buf, 65536); free(n); return; } static int ex_ssldata_index; /** < index used to work with additional data * provided to the special call during SSL handshake */ /* this function is an ugly implementation to get C string with uuid */ extern char *__generate_uuid(void); /* this is a callback to perform a custom SSL certs chain validation, * as I promised here the comments, a lot of ... * The first shit: 0 means validation failed, 1 otherwise * The second shit: X509 API, I guess u will love it ;-) * openssl calls this function for each certificate in chain, * since our case is a simple (depth of chain is one, since we're * don't care for public certificates lists or I cannot find any reasons to * do it ...), amount of calls reduced, and in this case we're interested * only in top of chain i.e. actual certificate used on client side, * the validity of signing for other certificates within chain is * guaranteed by the ssl itself. * u know, we need to lookup in database, or elsewhere... some information * about client certificate, and decide - is it valid, or not?, if so * yep I mean it's valid, we can assign it's long fucking number to * security context, to use in ongoing full scaled connection handshaking. */ static int __verify_certcall(int preverify_ok, X509_STORE_CTX *ctx) { // X509 *cert = X509_STORE_CTX_get_current_cert(ctx); int err = X509_STORE_CTX_get_error(ctx), depth = X509_STORE_CTX_get_error_depth(ctx); SSL *ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); conn_t *co = SSL_get_ex_data(ssl, ex_ssldata_index); /* this is a custom data we're set before */ conn_sys_t *ssys = co->ssys; /* now we need to check for certificates with a long chain, * so since we have a short one, reject long ones */ if(depth > VERIFY_DEPTH) { /* longer than we expect */ preverify_ok = 0; /* yep, 0 means error for those function callback in openssl, fucking set */ err = X509_V_ERR_CERT_CHAIN_TOO_LONG; X509_STORE_CTX_set_error(ctx, err); } if(!preverify_ok) return 0; /* ok, now we're on top of SSL (depth == 0) certs chain, * and we can validate client certificate */ if(!depth) { co->pctx->certid = ASN1_INTEGER_get((const ASN1_INTEGER *)X509_get_serialNumber(ctx->current_cert)); /* now we're need to check the ssl cert */ if(ssys->validate_sslpem) { if(ssys->validate_sslpem(co)) return 0; else return 1; } else return 0; } return preverify_ok; } /* dummy just to check the server side */ static int __verify_certcall_dummy(int preverify_ok, X509_STORE_CTX *ctx) { return preverify_ok; } int sntl_init(void) { /* init SSL library */ SSL_library_init(); OpenSSL_add_all_algorithms(); SSL_load_error_strings(); ex_ssldata_index = SSL_get_ex_new_index(0, "__ssldata index", NULL, NULL, NULL); return 0; } conn_t *__connection_minimal_alloc(struct in_addr *addr) { conn_t *co = malloc(sizeof(conn_t)); int r; if(!co) { r = ENOMEM; goto __fail; } else memset(co, 0, sizeof(conn_t)); if(!(co->messages = malloc(sizeof(uintptr_t)*1024))) { r = ENOMEM; goto __fail; } else memset(co->messages, 0, sizeof(uintptr_t)*1024); if(!(co->pctx = malloc(sizeof(perm_ctx_t)))) { r = ENOMEM; goto __fail; } else memset(co->pctx, 0, sizeof(perm_ctx_t)); if(addr) { if(!(co->pctx->addr = malloc(sizeof(struct in_addr)))) { r = ENOMEM; goto __fail; } memcpy(co->pctx->addr, addr, sizeof(struct in_addr)); } if(!(co->uuid = __generate_uuid())) { r = ENOMEM; goto __fail; } return co; __fail: if(co) { if(co->pctx) { if(co->pctx->addr) free(co->pctx->addr); free(co->pctx); } if(co->messages) free(co->messages); free(co); } errno = r; return NULL; } static int __connection_second_alloc(conn_t *co) { usrtc_node_init(&co->csnode, co); memset(&co->idx_ch, 0, sizeof(idx_allocator_t)); memset(&co->idx_msg, 0, sizeof(idx_allocator_t)); if((idx_allocator_init(&co->idx_ch, 512, 0))) goto __fail; if((idx_allocator_init(&co->idx_msg, 1024, 0))) goto __fail; if(!(co->channels = malloc(sizeof(uintptr_t)*512))) goto __fail; else memset(co->channels, 0, sizeof(uintptr_t)*512); /* init mutexes */ pthread_mutex_init(&co->idx_ch_lock, NULL); pthread_mutex_init(&co->idx_msg_lock, NULL); pthread_mutex_init(&co->write_pending_lock, NULL); pthread_mutex_init(&co->sslinout[0], NULL); pthread_mutex_init(&co->sslinout[1], NULL); /* init list */ list_init_head(&co->write_pending); return SNE_SUCCESS; __fail: idx_allocator_destroy(&co->idx_msg); idx_allocator_destroy(&co->idx_ch); return SNE_ENOMEM; } static void __connection_second_free(conn_t *co) { if(co->channels) free(co->channels); idx_allocator_destroy(&co->idx_msg); idx_allocator_destroy(&co->idx_ch); pthread_mutex_destroy(&co->idx_ch_lock); pthread_mutex_destroy(&co->idx_msg_lock); pthread_mutex_destroy(&co->write_pending_lock); pthread_mutex_destroy(&co->sslinout[0]); pthread_mutex_destroy(&co->sslinout[1]); return; } static void __connection_minimal_free(conn_t *co) { if(co) { if(co->pctx) { if(co->pctx->addr) free(co->pctx->addr); free(co->pctx); } if(co->messages) free(co->messages); free(co->uuid); free(co); } return; } static int __eval_syssexp(conn_t *co, sexp_t *sx) { cx_rpc_list_t *rpc_list = co->ssys->system_rpc; usrtc_node_t *node; cx_rpc_t *rentry; char *rpcf; if(sx->ty == SEXP_LIST) rpcf = sx->list->val; else return SNE_BADPROTO; /* find an appropriate function */ node = usrtc_lookup(rpc_list->rpc_tree, rpcf); if(!node) return SNE_ENORPC; else rentry = (cx_rpc_t *)usrtc_node_getdata(node); /* call it */ return rentry->rpcf((void *)co, sx); } static void *__sntll_thread(void *b) { sntllv2_bundle_t *bun = (sntllv2_bundle_t *)b; conn_t *co = bun->conn; void *buf = bun->buf; char *bbuf = (char*)buf; sntllv2_head_t *mhead = (sntllv2_head_t *)buf; sxmsg_t *msg; sexp_t *sx; chnl_t *channel; pthread_t self = pthread_self(); int dispatch = 0; size_t rd, wr; ulong_t mid; /* byte buffer is following head */ bbuf += sizeof(sntllv2_head_t); __wait_alive: /* flag test - FIXME: make it atomic (it will works atomically on x86, btw on others not) */ if(!(co->flags & SNSX_ALIVE)) { if(co->flags & SNSX_CLOSED) goto __finish; else { usleep(20); goto __wait_alive; } } /* check up a thread */ if(pthread_equal(self, co->thrd_poll[7])) /* dispatcher */ dispatch = 1; /* the following logic : (except dispatcher) * 1. check up pending write -> if exists write one and start again., otherwise go next * 2. read from ssl connection (we will sleep if other already acquire the lock) */ while(1) { __again: pthread_mutex_lock(&(co->sslinout[0])); if(co->flags & SNSX_CLOSED) { pthread_mutex_unlock(&(co->sslinout[0])); goto __finish; } rd = __conn_read(co, mhead, sizeof(sntllv2_head_t)); #ifdef _VERBOSE_DEBUG dumphead(mhead); #endif if(rd != sizeof(sntllv2_head_t)) { __sslproto_error: co->flags |= SNSX_CLOSED; pthread_mutex_unlock(&(co->sslinout[0])); goto __finish; } else { /* check up if we can read or not */ if(mhead->payload_length) { rd = __conn_read(co, bbuf, mhead->payload_length); if(rd < 0) goto __sslproto_error; else pthread_mutex_unlock(&(co->sslinout[0])); if(rd != mhead->payload_length) { /* if we're need to do something */ if(mhead->msgid >= 1024) { mhead->opcode = SNE_INVALINDEX; goto __return_error; } else msg = co->messages[mhead->msgid]; if(!msg) { if(mhead->attr & SXMSG_OPEN) mhead->opcode = SNE_BADPROTO; else { if((mhead->attr & SXMSG_PROTO) || (mhead->attr & SXMSG_LINK)) mhead->opcode = SNE_BADPROTO; else mhead->opcode = SNE_NOSUCHMSG; } } __return_error: mhead->attr |= SXMSG_CLOSED; mhead->payload_length = 0; pthread_mutex_lock(&(co->sslinout[1])); wr = __conn_write(co, mhead, sizeof(sntllv2_head_t)); pthread_mutex_unlock(&(co->sslinout[1])); if(wr < 0) goto __finish; else goto __again; } } else pthread_mutex_unlock(&(co->sslinout[0])); /* take a message */ if(mhead->attr & SXMSG_PROTO) { /* protocol message i.e. channel open/close */ /* ok, check up the side */ if(mhead->attr & SXMSG_REPLYREQ) { /* means we're not initiators and we don't need to allocate a message */ if(mhead->attr & SXMSG_OPEN) mhead->opcode = _channel_open(co, &mhead->reserve); else mhead->opcode = _channel_close(co, mhead->reserve); /* set flags */ mhead->payload_length = 0; mhead->attr &= ~SXMSG_REPLYREQ; pthread_mutex_lock(&(co->sslinout[1])); wr = __conn_write(co, mhead, sizeof(sntllv2_head_t)); pthread_mutex_unlock(&(co->sslinout[1])); if(wr < 0) goto __finish; } else { /* it's came back */ /* reply came ... */ if(mhead->msgid >= 1024) { __inval_idx_nor: fprintf(stderr, "[sntllv2] Invalid index of the message.\n"); goto __again; } mid = mhead->msgid; msg = co->messages[mid]; if(!msg) goto __inval_idx_nor; /* ok now we'are copy data and unlock wait mutex */ memcpy(&msg->mhead, mhead, sizeof(sntllv2_head_t)); pthread_mutex_unlock(&msg->wait); } } else if(mhead->attr & SXMSG_LINK) { /* link layer messages */ if(mhead->attr & SXMSG_CLOSED) goto __finish; /* close the link */ if(mhead->attr & SXMSG_PULSE) { /* it's a link pulse messages */ /* TODO: syncronization and so on */ if(mhead->opcode == SNE_RAPIDMSG) { /* custom pulse */ sx = parse_sexp(bbuf, mhead->payload_length); if(sx && co->ssys->on_pulse) co->ssys->on_pulse(co, sx); if(sx) destroy_sexp(sx); } } } else { /* regular messages */ if((mhead->attr & SXMSG_OPEN) && (mhead->attr & SXMSG_REPLYREQ)) { /* dialog initiation */ channel = co->channels[mhead->reserve]; if(!channel) { /* ok, we'are failed */ mhead->opcode = SNE_NOSUCHCHAN; __ret_regerr: mhead->payload_length = 0; mhead->attr &= ~SXMSG_REPLYREQ; mhead->attr &= ~SXMSG_OPEN; mhead->attr |= SXMSG_CLOSED; pthread_mutex_lock(&(co->sslinout[1])); wr = __conn_write(co, mhead, sizeof(sntllv2_head_t)); pthread_mutex_unlock(&(co->sslinout[1])); if(wr < 0) goto __finish; else goto __again; } /* if message is busy - fails */ msg = co->messages[mhead->msgid]; if(msg) { mhead->opcode = SNE_EBUSY; goto __ret_regerr; } /* now we will take a deal */ if(!(msg = malloc(sizeof(sxmsg_t)))) { mhead->opcode = SNE_ENOMEM; goto __ret_regerr; } else { /* set mutex and channel */ pthread_mutex_init(&msg->wait, NULL); pthread_mutex_lock(&msg->wait); msg->pch = channel; /* copy header only */ memcpy(&msg->mhead, mhead, sizeof(sntllv2_head_t)); if(mhead->payload_length) msg->payload = bbuf; } mid = mhead->msgid; pthread_mutex_lock(&co->idx_msg_lock); idx_reserve(&co->idx_msg, mid); co->messages[mhead->msgid] = msg; pthread_mutex_unlock(&co->idx_msg_lock); /* now we are able to process the message */ _message_process(msg); } else if(mhead->attr & SXMSG_CLOSED) { /* check for the message */ if(mhead->msgid >= 1024) goto __inval_idx_nor; msg = co->messages[mhead->msgid]; if(!msg) goto __inval_idx_nor; /* message dialog is closed - remove this right now */ pthread_mutex_lock(&co->idx_msg_lock); idx_free(&co->idx_msg, mhead->msgid); co->messages[mhead->msgid] = NULL; pthread_mutex_unlock(&co->idx_msg_lock); if(msg->mhead.attr & SXMSG_TIMEDOUT) { /* nobody wait for it */ /* now just free it */ pthread_mutex_unlock(&msg->wait); pthread_mutex_destroy(&msg->wait); free(msg); } else { memcpy(&msg->mhead, mhead, sizeof(sntllv2_head_t)); if(mhead->payload_length) { msg->payload = malloc(mhead->payload_length); if(msg->payload) memcpy(msg->payload, bbuf, mhead->payload_length); else msg->mhead.opcode = SNE_ENOMEM; } pthread_mutex_unlock(&msg->wait); /* wake up thread waiting for */ } } else if((!(mhead->attr & SXMSG_CLOSED) && !(mhead->attr & SXMSG_OPEN)) && (mhead->attr & SXMSG_REPLYREQ)) { /* ongoing dialog */ /* check for the message */ if(mhead->msgid >= 1024) goto __inval_idx_nor; msg = co->messages[mhead->msgid]; if(!msg) goto __inval_idx_nor; if(msg->mhead.attr & SXMSG_TIMEDOUT) { /* nobody wait for it */ pthread_mutex_lock(&co->idx_msg_lock); idx_free(&co->idx_msg, mhead->msgid); co->messages[mhead->msgid] = NULL; pthread_mutex_unlock(&co->idx_msg_lock); /* now just free it */ pthread_mutex_destroy(&msg->wait); free(msg); /* we must reply */ mhead->opcode = SNE_ETIMEDOUT; goto __ret_regerr; } else { memcpy(&msg->mhead, mhead, sizeof(sntllv2_head_t)); if(mhead->payload_length) { msg->payload = malloc(mhead->payload_length); if(msg->payload) memcpy(msg->payload, bbuf, mhead->payload_length); else { mhead->opcode = msg->mhead.opcode = SNE_ENOMEM; /* we will return it to waitee */ msg->mhead.attr &= ~SXMSG_REPLYREQ; /* doesn't need to reply */ /* reply here now */ mhead->payload_length = 0; mhead->attr &= ~SXMSG_REPLYREQ; mhead->attr &= ~SXMSG_OPEN; mhead->attr |= SXMSG_CLOSED; pthread_mutex_lock(&(co->sslinout[1])); wr = __conn_write(co, mhead, sizeof(sntllv2_head_t)); pthread_mutex_unlock(&(co->sslinout[1])); if(wr < 0) goto __finish; } } pthread_mutex_unlock(&msg->wait); /* wake up thread waiting for */ } } else { mhead->opcode = SNE_BADPROTO; goto __ret_regerr; } } } } __finish: __sntll_bundle_destroy(b); /* destroy bundle */ return NULL; } conn_t *connection_master_link(conn_sys_t *ssys, int sck, struct in_addr *addr) { void *buf = NULL; char *bbuf; conn_t *co = __connection_minimal_alloc(addr); sxmsg_t *msg = NULL; sntllv2_head_t *head; sntllv2_bundle_t *bundle; size_t rd; int r = SNE_FAILED; if(!co) { errno = SNE_ENOMEM; return NULL; } /* ok, now we need to init ssl stuff */ co->ssys = ssys; /* init SSL certificates and context */ co->ctx = SSL_CTX_new(TLSv1_2_server_method()); if(!co->ctx) { r = SNE_ENOMEM; goto __fail; } else { /* set verify context */ SSL_CTX_set_verify(co->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, __verify_certcall); /* set verify depth */ SSL_CTX_set_verify_depth(co->ctx, VERIFY_DEPTH); } /* load certificates */ SSL_CTX_load_verify_locations(co->ctx, ssys->rootca, NULL); /* set the local certificate from CertFile */ if(SSL_CTX_use_certificate_file(co->ctx, ssys->certpem, SSL_FILETYPE_PEM)<=0) { ERR_print_errors_fp(stderr); r = SNE_ESSL; goto __fail; } /* set the private key from KeyFile (may be the same as CertFile) */ if(SSL_CTX_use_PrivateKey_file(co->ctx, ssys->certkey, SSL_FILETYPE_PEM)<=0) { r = SNE_ESSL; goto __fail; } /* verify private key */ if (!SSL_CTX_check_private_key(co->ctx)) { r = SNE_ESSL; goto __fail; } /* now we will create an SSL connection */ co->ssl = SSL_new(co->ctx); if(!co->ssl) { r = SNE_ENOMEM; goto __fail; } else SSL_set_fd(co->ssl, sck); /* attach connected socket */ SSL_set_accept_state(co->ssl); /* set the context to verify ssl connection */ SSL_set_ex_data(co->ssl, ex_ssldata_index, (void *)co); SSL_set_accept_state(co->ssl); if(SSL_accept(co->ssl) == -1) { r = SNE_EPERM; goto __fail; } /* ok, now we are able to allocate and so on */ /* set connection to the batch mode */ co->flags |= SNSX_BATCHMODE; /* allocate our first buffer */ buf = mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if(buf == MAP_FAILED) { r = SNE_ENOMEM; goto __fail2; } /* allocate first message */ if(!(msg = malloc(sizeof(sxmsg_t)))) { r = SNE_ENOMEM; goto __fail2; } else { memset(msg, 0, sizeof(sxmsg_t)); co->messages[0] = msg; } bbuf = (char *)buf; bbuf += sizeof(sntllv2_head_t); sexp_t *sx; while(co->flags & SNSX_BATCHMODE) { rd = __conn_read(co, buf, sizeof(sntllv2_head_t)); if(rd == sizeof(sntllv2_head_t)) { head = (sntllv2_head_t *)buf; /* check for returns */ if(head->opcode != SNE_SUCCESS) { r = head->opcode; goto __fail3; } else { /* opcode is fine */ /* if we're ready for messaging mode, turn off batch mode */ if(co->flags & SNSX_MESSAGINGMODE) { co->flags &= ~SNSX_BATCHMODE; break; } } if(!head->payload_length) continue; /* pass the following check up */ rd = __conn_read(co, bbuf, head->payload_length); if(rd != head->payload_length) { r = SNE_LINKERROR; goto __fail3; } bbuf[rd] = '\0'; sx = parse_sexp(bbuf, rd); if(!sx) goto __fail3; /* initialize message */ msg->payload = bbuf; msg->mhead.payload_length = 0; /* deal with it */ r = __eval_syssexp(co, sx); memcpy(head, &msg->mhead, sizeof(sntllv2_head_t)); head->opcode = r; if(r != SNE_SUCCESS) { /* we finish */ head->payload_length = 0; __conn_write(co, head, sizeof(sntllv2_head_t)); destroy_sexp(sx); goto __fail3; } rd = __conn_write(co, buf, sizeof(sntllv2_head_t) + msg->mhead.payload_length); if(rd != sizeof(sntllv2_head_t) + msg->mhead.payload_length) { destroy_sexp(sx); goto __fail3; } destroy_sexp(sx); } else { r = SNE_LINKERROR; goto __fail3; } } /* if we're there - negotiation is done, going to init messaging mode */ r = __connection_second_alloc(co); if(r != SNE_SUCCESS) goto __fail3; /* free message */ co->messages[0] = NULL; free(msg); /* and now we're need to create a thread poll */ if(!(bundle = malloc(sizeof(sntllv2_bundle_t)))) { r = SNE_ENOMEM; goto __fail4; } else { bundle->buf = buf; bundle->conn = co; } int i; for(i = 0; i < 8; i++) { if(bundle == (void *)0xdead) bundle = __sntll_bundle_create(co); if(!bundle) goto __fail5; r = pthread_create(&co->thrd_poll[i], NULL, __sntll_thread, bundle); if(r) goto __fail5; else bundle = (void *)0xdead; } /* all is done, connection now ready */ co->flags |= SNSX_ALIVE; r = SNE_SUCCESS; errno = r; return co; __fail5: r = SNE_ENOMEM; /* bundles will be freed by the threads when SSL_read will fails. */ __fail4: __connection_second_free(co); __fail3: if(ssys->on_destroy) ssys->on_destroy(co); __fail2: if(msg) free(msg); if(buf != MAP_FAILED) munmap(buf, 65536); SSL_shutdown(co->ssl); __fail: if(co) { if(co->ssl) SSL_free(co->ssl); if(co->ctx) SSL_CTX_free(co->ctx); __connection_minimal_free(co); } close(sck); errno = r; return NULL; } conn_t *connection_link(conn_sys_t *ssys, const char *host, int port, const char *SSL_cert, const char *login, const char *passwd) { conn_t *co = __connection_minimal_alloc(NULL); struct hostent *host_; struct sockaddr_in addr; int r = SNE_SUCCESS, sck; #ifdef WIN32 WSADATA wsaData; #endif char hostbuf[2048]; void *buf = NULL; char *bbuf; sntllv2_head_t *head; sntllv2_bundle_t *bundle; sxmsg_t *msg; size_t rd, wr; int i; r = SNE_IGNORED; if(!host || !SSL_cert) goto __fail; if(!co) { r = SNE_ENOMEM; goto __fail; } #ifdef WIN32 WSAStartup(MAKEWORD(2, 2), &wsaData); #endif /* ok, now we need to init ssl stuff */ co->ssys = ssys; /* init SSL certificates and context */ co->ctx = SSL_CTX_new(TLSv1_2_client_method()); if(!co->ctx) { r = SNE_ENOMEM; goto __fail; } else { /* set verify context */ SSL_CTX_set_verify(co->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, __verify_certcall_dummy); /* set verify depth */ SSL_CTX_set_verify_depth(co->ctx, VERIFY_DEPTH); } /* load certificates */ SSL_CTX_load_verify_locations(co->ctx, ssys->rootca, NULL); /* set the local certificate from CertFile */ if(SSL_CTX_use_certificate_file(co->ctx, SSL_cert, SSL_FILETYPE_PEM)<=0) { ERR_print_errors_fp(stderr); r = SNE_ESSL; goto __fail; } /* set the private key from KeyFile (may be the same as CertFile) */ if(SSL_CTX_use_PrivateKey_file(co->ctx, SSL_cert, SSL_FILETYPE_PEM)<=0) { r = SNE_ESSL; goto __fail; } /* verify private key */ if (!SSL_CTX_check_private_key(co->ctx)) { r = SNE_ESSL; goto __fail; } /* resolve host */ #ifdef WIN32 host_ = gethostbyname(host); #else r = __resolvehost(host, hostbuf, 2048, &host_); #endif if(r) { r = SNE_FAILED; goto __fail; } /* create a socket */ sck = socket(PF_INET, SOCK_STREAM, 0); bzero(&addr, sizeof(addr)); /* try to connect it */ addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = *(uint32_t*)(host_->h_addr); r = connect(sck, (struct sockaddr*)&addr, sizeof(addr)); if(r) { close(sck); r = SNE_FAILED; /* couldn't connect to the desired host */ goto __fail; } /* SSL handshake */ co->ssl = SSL_new(co->ctx); /* TODO: checkout for it */ SSL_set_fd(co->ssl, sck); /* attach connected socket */ SSL_set_connect_state(co->ssl); if(SSL_connect(co->ssl) == -1) { r = SNE_EPERM; /* shutdown connection */ goto __fail; } /* if success we're ready to use established SSL channel */ /* set connection to the batch mode */ co->flags |= SNSX_BATCHMODE; /* allocate our first buffer */ buf = mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if(buf == MAP_FAILED) { r = SNE_ENOMEM; goto __fail2; } /* allocate first message */ if(!(msg = malloc(sizeof(sxmsg_t)))) { r = SNE_ENOMEM; goto __fail2; } else { memset(msg, 0, sizeof(sxmsg_t)); co->messages[0] = msg; } bbuf = (char *)buf; bbuf += sizeof(sntllv2_head_t); head = (sntllv2_head_t *)buf; sexp_t *sx; size_t ln; while(co->flags & SNSX_BATCHMODE) { /* form a message -- credentials */ ln = snprintf(bbuf, 65535 - sizeof(sntllv2_head_t), "(auth-set-credentials \"%s\" \"%s\")", login ? login : "nil", passwd ? passwd : "nil"); head->opcode = SNE_SUCCESS; head->payload_length = ln; wr = __conn_write(co, buf, ln + sizeof(sntllv2_head_t)); if(wr < 0) goto __fail2; rd = __conn_read(co, head, sizeof(sntllv2_head_t)); if(rd < 0) goto __fail2; if(head->opcode != SNE_SUCCESS) { r = head->opcode; goto __fail2; } /* ok, get available channels */ head->opcode = SNE_SUCCESS; ln = snprintf(bbuf, 65535 - sizeof(sntllv2_head_t), "(get-channels-list)"); head->payload_length = ln; wr = __conn_write(co, buf, ln + sizeof(sntllv2_head_t)); if(wr < 0) goto __fail2; rd = __conn_read(co, head, sizeof(sntllv2_head_t)); if(rd < 0) goto __fail2; if(head->opcode != SNE_SUCCESS) goto __fail2; if(!head->payload_length) goto __fail2; rd = __conn_read(co, bbuf, head->payload_length); if(rd < 0) goto __fail2; /* perform a parsing of the desired message */ bbuf[rd] = '\0'; sx = parse_sexp(bbuf, rd); if(!sx) { r = SNE_BADPROTO; goto __fail2; } r = __eval_syssexp(co, sx); if(!r) r = SNE_SUCCESS; destroy_sexp(sx); /* write back */ head->opcode = r; head->payload_length = 0; wr = __conn_write(co, head, sizeof(sntllv2_head_t)); if(wr < 0) { blub("fuck"); r = SNE_LINKERROR; goto __fail2;} if(r != SNE_SUCCESS) { r = SNE_LINKERROR; goto __fail2;} } /* if we're there - negotiation is done, going to init messaging mode */ r = __connection_second_alloc(co); if(r != SNE_SUCCESS) goto __fail3; /* free message */ co->messages[0] = NULL; free(msg); /* and now we're need to create a thread poll */ if(!(bundle = malloc(sizeof(sntllv2_bundle_t)))) { r = SNE_ENOMEM; goto __fail4; } else { bundle->buf = buf; bundle->conn = co; } for(i = 0; i < 8; i++) { if(bundle == (void *)0xdead) bundle = __sntll_bundle_create(co); if(!bundle) goto __fail5; r = pthread_create(&co->thrd_poll[i], NULL, __sntll_thread, bundle); if(r) goto __fail5; else bundle = (void *)0xdead; } /* all is done, connection now ready */ co->flags |= SNSX_ALIVE; return co; __fail5: r = SNE_ENOMEM; /* bundles will be freed by the threads when SSL_read will fails. */ __fail4: __connection_second_free(co); __fail3: if(ssys->on_destroy) ssys->on_destroy(co); __fail2: if(buf != MAP_FAILED) munmap(buf, 65536); SSL_shutdown(co->ssl); close(sck); __fail: if(co) { if(co->ssl) SSL_free(co->ssl); if(co->ctx) SSL_CTX_free(co->ctx); __connection_minimal_free(co); } errno = r; return NULL; }