/* * Secure X Message Passing Library v2 implementation. * (sxmplv2) it superseed all versions before due to the: * - memory consumption * - new features such as pulse emitting * - performance optimization * * (c) Askele Group 2013-2015 * (c) Alexander Vdolainen 2013-2015 * * libsxmp is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * libsxmp is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see ."; * */ #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" static int __insert_rpc_function(usrtc_t *tree, const char *name, int (*rpcf)(void *, sexp_t *)) { sxl_rpc_t *ent = malloc(sizeof(sxl_rpc_t)); usrtc_node_t *node; if(!ent) return ENOMEM; else node = &ent->node; if(!(ent->name = strdup(name))) { free(ent); return ENOMEM; } else ent->rpcf = rpcf; usrtc_node_init(node, ent); usrtc_insert(tree, node, ent->name); return 0; } static void __destroy_rpc_list_tree(usrtc_t *tree) { usrtc_node_t *node; sxl_rpc_t *ent; for(node = usrtc_first(tree); node != NULL; node = usrtc_first(tree)) { ent = (sxl_rpc_t *)usrtc_node_getdata(node); usrtc_delete(tree, node); free(ent->name); free(ent); } return; } /* negotiation functions */ /** * Proto: (auth-set-credentials "" "") * Return - message return; opcode as a return of this function. * in batch mode message with 0 index used always */ static int __set_credentials(void *cctx, sexp_t *sx) { register int idx; sxlink_t *co = (sxlink_t *)cctx; sxhub_t *ssys = co->ssys; sexp_t *isx; char *login = NULL; char *passwd = NULL; /* take a deal with S-exp */ SEXP_ITERATE_LIST(sx, isx, idx) { if(isx->ty == SEXP_LIST) return SXE_BADPROTO; if(idx > 0 && isx->aty != SEXP_DQUOTE) return SXE_BADPROTO; if(idx == 1) login = isx->val; else if(idx == 2) passwd = isx->val; else if(idx > 2) return SXE_BADPROTO; } if(!login || !passwd) return SXE_BADPROTO; co->pctx->login = strdup(login); co->pctx->passwd = strdup(passwd); if(!co->pctx->login || !co->pctx->passwd) { if(co->pctx->login) free(co->pctx->login); if(co->pctx->passwd) free(co->pctx->passwd); return SXE_ENOMEM; } if(ssys->secure_check) return ssys->secure_check(co); else return SXE_SUCCESS; } static int __get_channels_list(void *cctx, sexp_t *sx) { sxlink_t *co = (sxlink_t *)cctx; sxhub_t *ssys = co->ssys; sxmsg_t *msg = co->messages[0]; char *buf = msg->payload; usrtc_node_t *node; rpc_typed_list_t *list_ent; size_t maxlen = 65535 - sizeof(sxmplv2_head_t); size_t ulen = 0; /* determine how this function was called */ if(!strcmp(sx->list->val, "get-channels-list")) co->cp_version = V2; else co->cp_version = V2_1; /* last supported in this version */ /* call the function */ if(ssys->get_rpc_typed_list_tree) co->rpc_list = ssys->get_rpc_typed_list_tree(co); if(!co->rpc_list) return SXE_EPERM; //buf += sizeof(sxmplv2_head_t); if(co->cp_version == V2) ulen += snprintf(buf + ulen, maxlen - ulen, "(set-channels-list "); else ulen += snprintf(buf + ulen, maxlen - ulen, "(!@c< "); for(node = usrtc_first(co->rpc_list); node != NULL; node = usrtc_next(co->rpc_list, node)) { /* fill the list */ list_ent = (rpc_typed_list_t *)usrtc_node_getdata(node); ulen += snprintf(buf + ulen, maxlen - ulen, "(:%d \"%s\")", list_ent->type_id, list_ent->description); } ulen += snprintf(buf + ulen, maxlen - ulen, ")"); msg->mhead.payload_length = ulen + 1; if(co->cp_version == V2) { /* we're ready for messaging mode */ co->flags |= SXMP_MESSAGINGMODE; co->flags &= ~SXMP_BATCHMODE; } return SXE_SUCCESS; } static int __get_streams(void *cctx, sexp_t *sx) { sxlink_t *link = (sxlink_t *)cctx; sxhub_t *hub = link->ssys; sxmsg_t *msg = link->messages[0]; char *buf = msg->payload; usrtc_node_t *node, *rpc_node; struct sxstream_description *s_desc; size_t maxlen = 65535 - sizeof(sxmplv2_head_t); size_t ulen = 0, sts = 0; int tcid = 0; if(link->cp_version == V_UNKNOWN) /* oops, version doesn't a happy one */ return SXE_FAILED; if(!hub->streams) { /* no streams provided */ __return_nil: ulen = snprintf(buf, maxlen, "(!@s< nil)"); msg->mhead.payload_length = ulen + 1; /* and now we're ready to exit from batch mode */ link->flags |= SXMP_MESSAGINGMODE; link->flags &= ~SXMP_BATCHMODE; return SXE_SUCCESS; } else { /* set all streams available */ ulen += snprintf(buf + ulen, maxlen - ulen, "(!@s< "); for(node = usrtc_first(hub->streams); node != NULL; node = usrtc_next(hub->streams, node)) { s_desc = (struct sxstream_description *)usrtc_node_getdata(node); tcid = s_desc->pcid; if((rpc_node = usrtc_lookup(link->rpc_list, &tcid))) { /* channel allowed */ /* ids */ ulen += snprintf(buf + ulen, maxlen - ulen, "(:tcid %d :stid %d :t ", s_desc->pcid, s_desc->stid); /* type */ switch(s_desc->type) { case 0: ulen += snprintf(buf + ulen, maxlen - ulen, "e "); break; case SXE_O_NAMED: ulen += snprintf(buf + ulen, maxlen - ulen, "n "); break; case SXE_O_BINARY: ulen += snprintf(buf + ulen, maxlen - ulen, "b "); break; } /* flags */ ulen += snprintf(buf + ulen, maxlen - ulen, ":a "); if(s_desc->flags & SXE_O_READ) ulen += snprintf(buf + ulen, maxlen - ulen, "r"); else ulen += snprintf(buf + ulen, maxlen - ulen, "-"); if(s_desc->flags & SXE_O_WRITE) ulen += snprintf(buf + ulen, maxlen - ulen, "w"); else ulen += snprintf(buf + ulen, maxlen - ulen, "-"); if(s_desc->flags & SXE_O_TRUNC) ulen += snprintf(buf + ulen, maxlen - ulen, "t"); else ulen += snprintf(buf + ulen, maxlen - ulen, "-"); if(s_desc->flags & SXE_O_ASYNC) ulen += snprintf(buf + ulen, maxlen - ulen, "a"); else ulen += snprintf(buf + ulen, maxlen - ulen, "-"); ulen += snprintf(buf + ulen, maxlen - ulen, ")"); sts++; } } if(!sts) goto __return_nil; ulen += snprintf(buf + ulen, maxlen - ulen, ")"); msg->mhead.payload_length = ulen + 1; } link->flags |= SXMP_MESSAGINGMODE; link->flags &= ~SXMP_BATCHMODE; return SXE_SUCCESS; } static long __cmp_uint16(const void *a, const void *b) { return (long) *(uint16_t*)a - *(uint16_t*)b; } #define _MOD_STID 0xa #define _MOD_TCID 0xb #define _MOD_TYPE 0xc #define _MOD_FLAGS 0xd #define _MOD_UNKWN 0x0 static int __set_streams(void *cctx, sexp_t *sx) { register int idx, iidx; usrtc_t *tree = NULL; sxlink_t *link = (sxlink_t *)cctx; sexp_t *isx, *iisx; struct sxstream_description *s_desc; usrtc_node_t *node = NULL; int modp = 0, r, value_mod = 0, mod = _MOD_UNKWN; int flags, type, stid, pcid; if(!link->remote_streams) { tree = malloc(sizeof(usrtc_t)); if(!tree) return SXE_ENOMEM; usrtc_init(tree, USRTC_SPLAY, MAX_STREAMS_TYPES, __cmp_uint16); link->remote_streams = tree; } SEXP_ITERATE_LIST(sx, isx, idx) { if(!idx) continue; if(isx->ty != SEXP_LIST) { free(tree); link->remote_streams = NULL; if(!strcmp(isx->val, "nil")) goto __fini; else return SXE_BADPROTO; } else { SEXP_ITERATE_LIST(isx, iisx, iidx) { if(iisx->ty == SEXP_LIST) { r = SXE_BADPROTO; __clean_up_on_error: /* clean up all stuff */ for(node = usrtc_first(tree); node != NULL; node = usrtc_first(tree)) { s_desc = (struct sxstream_description *)usrtc_node_getdata(node); usrtc_delete(tree, node); free(s_desc); } free(tree); link->remote_streams = NULL; return r; } if(iisx->val[0] == ':' && value_mod) { r = SXE_BADPROTO; goto __clean_up_on_error; } else if(iisx->val[0] == ':' && !value_mod) { value_mod = 1; if(!strcmp(iisx->val, ":stid")) mod = _MOD_STID; else if(!strcmp(iisx->val, ":tcid")) mod = _MOD_TCID; else if(!strcmp(iisx->val, ":t")) mod = _MOD_TYPE; else if(!strcmp(iisx->val, ":a")) mod = _MOD_FLAGS; else { r = SXE_BADPROTO; goto __clean_up_on_error; } } else if(iisx->val[0] != ':' && value_mod) { switch(mod) { case _MOD_STID: stid = atoi(iisx->val); modp++; break; case _MOD_TCID: pcid = atoi(iisx->val); modp++; break; case _MOD_TYPE: switch(iisx->val[0]) { case 'e': type = 0; break; case 'b': type = SXE_O_BINARY; break; case 'n': type = SXE_O_NAMED; break; default: r = SXE_BADPROTO; goto __clean_up_on_error; } modp++; break; case _MOD_FLAGS: flags = 0; if(iisx->val[0] == 'r') flags |= SXE_O_READ; else if(iisx->val[1] == 'w') flags |= SXE_O_WRITE; else if(iisx->val[2] == 't') flags |= SXE_O_TRUNC; else if(iisx->val[3] == 'a') flags |= SXE_O_ASYNC; else { r = SXE_BADPROTO; goto __clean_up_on_error; } modp++; break; } value_mod = 0; } else { r = SXE_BADPROTO; goto __clean_up_on_error; } } /* here we go */ if(modp < 4) { r = SXE_BADPROTO; goto __clean_up_on_error; } else if(!(s_desc = malloc(sizeof(struct sxstream_description)))) { r = SXE_ENOMEM; goto __clean_up_on_error; } usrtc_node_init(&s_desc->node, s_desc); s_desc->ops = NULL; s_desc->flags = (uint16_t)flags; s_desc->type = (uint16_t)type; s_desc->pcid = (uint16_t)pcid; s_desc->stid = (uint16_t)stid; usrtc_insert(tree, &s_desc->node, &s_desc->stid); /* finish */ modp = 0; } } __fini: link->flags |= SXMP_MESSAGINGMODE; link->flags &= ~SXMP_BATCHMODE; return SXE_SUCCESS; } #undef _MOD_STID #undef _MOD_TCID #undef _MOD_TYPE #undef _MOD_FLAGS #undef _MOD_UNKWN static int __set_channels_list(void *cctx, sexp_t *sx) { register int idx; sxlink_t *co = (sxlink_t *)cctx; sxhub_t *ssys = co->ssys; sexp_t *isx, *iisx; int id, r; /* determine how this function was called */ if(!strcmp(sx->list->val, "get-channels-list")) co->cp_version = V2; else co->cp_version = V2_1; /* last supported in this version */ SEXP_ITERATE_LIST(sx, isx, idx) { if(!idx) continue; if(isx->ty != SEXP_LIST) return SXE_BADPROTO; if(sexp_list_length(isx) != 2) return SXE_BADPROTO; /* get id */ sexp_list_car(isx, &iisx); if(iisx->ty == SEXP_LIST) return SXE_BADPROTO; if(iisx->aty != SEXP_BASIC) return SXE_BADPROTO; if(iisx->val[0] != ':') return SXE_BADPROTO; id = atoi(iisx->val + sizeof(char)); /* get short description */ sexp_list_cdr(isx, &iisx); if(iisx->ty == SEXP_LIST) return SXE_BADPROTO; if(iisx->aty != SEXP_DQUOTE) return SXE_BADPROTO; /* ok, here we go */ if(ssys->set_typed_list_callback) { r = ssys->set_typed_list_callback(co, id, iisx->val); if(r != SXE_SUCCESS) return r; } } if(co->cp_version == V2) { /* we're ready for messaging mode */ co->flags |= SXMP_MESSAGINGMODE; co->flags &= ~SXMP_BATCHMODE; } return SXE_SUCCESS; } static int __my_version_ack(void *cctx, sexp_t *sx) { register int idx; sxlink_t *link = (sxlink_t *)cctx; sexp_t *isx; SEXP_ITERATE_LIST(sx, isx, idx) { if(!idx) continue; if(idx > 2) return SXE_BADPROTO; if(isx->ty == SEXP_LIST) return SXE_BADPROTO; if(!strcmp(isx->val, V2_1_TPROT)) link->cp_version = V2_1; else link->cp_version = V_UNKNOWN; } return SXE_SUCCESS; } static int __my_version_set(void *cctx, sexp_t *sx) { register int idx; sxlink_t *link = (sxlink_t *)cctx; sexp_t *isx; SEXP_ITERATE_LIST(sx, isx, idx) { if(!idx) continue; if(idx > 2) return SXE_BADPROTO; if(isx->ty == SEXP_LIST) return SXE_BADPROTO; if(!strcmp(isx->val, V2_1_TPROT)) link->cp_version = V2_1; else return SXE_FAILED; /* failed to set another version */ } return SXE_SUCCESS; } static int __init_systemrpc_tree(usrtc_t *rtree) { /* batch mode negotiation context functions */ if(__insert_rpc_function(rtree, "auth-set-credentials", __set_credentials)) goto __fail; /* old version V2 */ if(__insert_rpc_function(rtree, "get-channels-list", __get_channels_list)) goto __fail; /* old V2 (v1 also) */ if(__insert_rpc_function(rtree, "set-channels-list", __set_channels_list)) goto __fail; /* sync functions */ /* channels */ if(__insert_rpc_function(rtree, "!@c>", __get_channels_list)) goto __fail; if(__insert_rpc_function(rtree, "!@c<", __set_channels_list)) goto __fail; /* version */ if(__insert_rpc_function(rtree, "!@v>", __my_version_ack)) goto __fail; if(__insert_rpc_function(rtree, "!@v<", __my_version_ack)) goto __fail; if(__insert_rpc_function(rtree, "!@V>", __my_version_set)) goto __fail; /* streams */ if(__insert_rpc_function(rtree, "!@s>", __get_streams)) goto __fail; if(__insert_rpc_function(rtree, "!@s<", __set_streams)) goto __fail; return 0; __fail: __destroy_rpc_list_tree(rtree); return ENOMEM; } static int __init_builtinrpc_tree(usrtc_t *rtree) { /* streams */ if(__insert_rpc_function(rtree, _SXSTREAMOPEN_CMD, _builtin_stream_open)) goto __fail; if(__insert_rpc_function(rtree, _SXSTREAMCLOSE_CMD, _builtin_stream_close)) goto __fail; if(__insert_rpc_function(rtree, _SXSTREAMEREAD_CMD, _builtin_stream_eread)) goto __fail; return 0; __fail: __destroy_rpc_list_tree(rtree); return ENOMEM; } static long __cmp_cstr(const void *a, const void *b) { return (long)strcmp((const char *)a, (const char *)b); } int sxhub_init(sxhub_t *ssys) { int r = 0; if(!ssys) return EINVAL; else memset(ssys, 0, sizeof(sxhub_t)); if(!(ssys->links = malloc(sizeof(usrtc_t)))) return ENOMEM; /* init links list */ usrtc_init(ssys->links, USRTC_REDBLACK, MAX_LINKS, __cmp_cstr); if((r = pthread_rwlock_init(&(ssys->rwlock), NULL))) goto __fini; /* init RPC list related functions */ if(!(ssys->system_rpc = malloc(sizeof(sxl_rpclist_t)))) { r = ENOMEM; goto __lfini; } else { if(!(ssys->system_rpc->rpc_tree = malloc(sizeof(usrtc_t)))) { r = ENOMEM; goto __lfini; } usrtc_init(ssys->system_rpc->rpc_tree, USRTC_SPLAY, 256, __cmp_cstr); r = __init_systemrpc_tree(ssys->system_rpc->rpc_tree); if(r) { free(ssys->system_rpc->rpc_tree); goto __lfini; } } /* init builtin functions list */ if(!(ssys->stream_rpc = malloc(sizeof(sxl_rpclist_t)))) { r = ENOMEM; goto __lfini; } else { if(!(ssys->stream_rpc->rpc_tree = malloc(sizeof(usrtc_t)))) { r = ENOMEM; goto __lfini; } usrtc_init(ssys->stream_rpc->rpc_tree, USRTC_SPLAY, 256, __cmp_cstr); r = __init_builtinrpc_tree(ssys->stream_rpc->rpc_tree); if(r) { free(ssys->stream_rpc->rpc_tree); goto __lfini; } } return 0; __lfini: if(ssys->system_rpc) free(ssys->system_rpc); if(ssys->stream_rpc) free(ssys->stream_rpc); pthread_rwlock_destroy(&(ssys->rwlock)); __fini: if(ssys->links) free(ssys->links); return r; } int sxhub_free(sxhub_t *ssys) { usrtc_node_t *node; __destroy_rpc_list_tree(ssys->system_rpc->rpc_tree); free(ssys->system_rpc->rpc_tree); free(ssys->system_rpc); /* free streams description tree */ if(ssys->streams) { for(node = usrtc_first(ssys->streams); node != NULL; node = usrtc_first(ssys->streams)) usrtc_delete(ssys->streams, node); free(ssys->streams); } free(ssys->links); pthread_rwlock_destroy(&(ssys->rwlock)); SSL_CTX_free(ssys->ctx); return 0; } int sxhub_destroy(sxhub_t *ssys) { int r = sxhub_free(ssys); free(ssys); return r; } sxhub_t *sxhub_create(void) { int r = 0; sxhub_t *nsys = malloc(sizeof(sxhub_t)); if(!nsys) { errno = ENOMEM; return NULL; } r = sxhub_init(nsys); if(r) { errno = r; free(nsys); return NULL; } return nsys; } int sxhub_setsslserts(sxhub_t *ssys, const char *rootca, const char *certpem, const char *certkey) { int r = ENOMEM; if(!ssys) return EINVAL; /* simply copying */ if(!(ssys->rootca = strdup(rootca))) return ENOMEM; if(!(ssys->certkey = strdup(certkey))) goto __fail; if(!(ssys->certpem = strdup(certpem))) goto __fail; r = 0; return 0; __fail: if(ssys->rootca) free(ssys->rootca); if(ssys->certkey) free(ssys->certkey); if(ssys->certpem) free(ssys->certpem); return r; } int sxhub_stream_register(sxhub_t *hub, const struct sxstream_description *s_desc) { usrtc_t *tree = NULL; usrtc_node_t *node = NULL; if(!hub->streams) { tree = malloc(sizeof(usrtc_t)); if(!tree) return ENOMEM; usrtc_init(tree, USRTC_SPLAY, MAX_STREAMS_TYPES, __cmp_uint16); hub->streams = tree; } else { tree = hub->streams; node = usrtc_lookup(tree, &s_desc->stid); if(node) return EEXIST; } node = (usrtc_node_t *)&s_desc->node; usrtc_node_init(node, (void *)s_desc); usrtc_insert(tree, node, &s_desc->stid); return 0; }