/* * 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 #ifdef WIN32 #include #define EBADE 1 #define NETDB_SUCCESS 0 #else #include #include #include #include #endif #include #include #include #include #include typedef struct __sntll_bundle_type { void *buf; conn_t *conn; } sntllv2_bundle_t; 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); 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 */ co->idx_ch_lock = PTHREAD_MUTEX_INITIALIZER; co->idx_msg_lock = PTHREAD_MUTEX_INITIALIZER; co->write_pending_lock = PTHREAD_MUTEX_INITIALIZER; co->sslinout[0] = PTHREAD_MUTEX_INITIALIZER; co->sslinout[1] = PTHREAD_MUTEX_INITIALIZER; /* init list */ list_head_init(&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->co; void *buf = bun->buf; char *bbuf = (char*)buf; sntllv2_head_t *mhead = (sntllv2_head_t *)buf; sxmsg_t *msg; chnl_t *channel; pthread_t self = pthread_self(); int dispatch = 0; /* 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])); rd = __conn_read(co, mhead, sizeof(sntllv2_head_t)); if(rd != sizeof(sntllv2_bundle_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; } msg = co->messages[mhead->msgid]; 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_CLOSE) goto __finish; /* close the link */ if(mhead->attr & SXMSG_PULSE) { /* it's a link pulse messages */ /* TODO: syncronization and so on */ } } 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_CLOSE; 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 */ msg->wait = PTHREAD_MUTEX_INITIALIZER; msg->pch = channel; /* copy header only */ memcpy(&msg->mhead, mhead, sizeof(sntllv2_head_t)); msg->payload = bbuf; } pthread_mutex_lock(&co->idx_ch_lock); idx_reserve(&co->idx_ch, mhead->msgid); co->messages[mhead->msgid] = msg; pthread_mutex_unlock(&co->idx_ch_lock); /* now we are able to process the message */ __message_process(msg); } else if(mhead->attr & SXMSG_CLOSE) { /* 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_ch_lock); idx_free(&co->idx_ch, mhead->msgid); co->messages[mhead->msgid] = NULL; pthread_mutex_unlock(&co->idx_ch_lock); /* now just free it */ 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_CLOSE) && !(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_ch_lock); idx_free(&co->idx_ch, mhead->msgid); co->messages[mhead->msgid] = NULL; pthread_mutex_unlock(&co->idx_ch_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_CLOSE; 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; } int connection_create_fapi_m(conn_sys_t *ssys, conn_t *co, int sck, struct in_addr *addr) { void *buf = NULL; char *bbuf; conn_t *coc = __connection_minimal_alloc(addr); sx_msg_t *msg = NULL; sntllv2_head_t *head; sntllv2_bundle_t *bundle; size_t rd; int r = SNE_FAILED; if(!coc) return SNE_ENOMEM; /* 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(sx_msg_t)))) { r = SNE_ENOMEM; goto __fail2; } else { memset(msg, 0, sizeof(sx_msg_t)); coc->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; } 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->payload_length = 0; /* deal with it */ r = __eval_syssexp(co, sx); head->opcode = r; if(r != SNE_SUCCESS) { /* we finish */ head->payload_length = 0; __conn_write(co, buf, sizeof(sntllv2_head_t)); destroy_sexp(sx); goto __fail3; } rd = __conn_write(co, buf, sizeof(sntllv2_head_t) + msg->payload_length); if(rd != sizeof(sntllv2_head_t) + msg->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; /* 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 == 0xdead) bundle = __sntll_bundle_create(co); if(!bundle) goto __fail5; r = pthread_create(&thrd_poll[i], NULL, __sntll_thread, bundle); if(r) goto __fail5; else bundle = 0xdead; } /* all is done, connection now ready */ co->flags |= SNSX_ALIVE; return SNE_SUCCESS; __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(coc) { if(co->ssl) SSL_free(co->ssl); if(co->ctx) SSL_CTX_free(co->ctx); __connection_minimal_free(coc); } close(sck); return r; }