diff --git a/CMakeLists.txt b/CMakeLists.txt index 430eff9f..7ebcf4e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,15 +24,15 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE debug) endif() -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -fvisibility=hidden") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -fvisibility=hidden -std=gnu11") set(CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG") set(CMAKE_C_FLAGS_PACKAGE "-g -O2 -DNDEBUG") set(CMAKE_C_FLAGS_DEBUG "-g -O0") # set version set(LIBNETCONF2_MAJOR_VERSION 0) -set(LIBNETCONF2_MINOR_VERSION 11) -set(LIBNETCONF2_MICRO_VERSION 49) +set(LIBNETCONF2_MINOR_VERSION 12) +set(LIBNETCONF2_MICRO_VERSION 20) set(LIBNETCONF2_VERSION ${LIBNETCONF2_MAJOR_VERSION}.${LIBNETCONF2_MINOR_VERSION}.${LIBNETCONF2_MICRO_VERSION}) set(LIBNETCONF2_SOVERSION ${LIBNETCONF2_MAJOR_VERSION}.${LIBNETCONF2_MINOR_VERSION}) @@ -189,7 +189,11 @@ endif() if(ENABLE_SSH) find_package(LibSSH 0.7.0 REQUIRED) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNC_ENABLED_SSH") - target_link_libraries(netconf2 "-L${LIBSSH_LIBRARY_DIR}" -lssh -lssh_threads -lcrypt) + if(LibSSH_VERSION VERSION_LESS 0.8.0) + target_link_libraries(netconf2 "-L${LIBSSH_LIBRARY_DIR}" -lssh -lssh_threads -lcrypt) + else() + target_link_libraries(netconf2 "-L${LIBSSH_LIBRARY_DIR}" -lssh -lcrypt) + endif() include_directories(${LIBSSH_INCLUDE_DIRS}) endif() diff --git a/python/netconf.c b/python/netconf.c index 10ecb005..df4cfc68 100644 --- a/python/netconf.c +++ b/python/netconf.c @@ -27,6 +27,10 @@ PyObject *libnetconf2ReplyError; /* syslog usage flag */ static int syslogEnabled = 0; +/* libyang schema callback */ +static PyObject *schemaCallback = NULL; +static void *schemaCallbackData = NULL; + static void clb_print(NC_VERB_LEVEL level, const char* msg) { @@ -123,6 +127,68 @@ setSearchpath(PyObject *self, PyObject *args, PyObject *keywds) Py_RETURN_NONE; } +char * +schemaCallbackWrapper(const char *mod_name, const char *mod_rev, const char *submod_name, const char *sub_rev, + void *user_data, LYS_INFORMAT *format, void (**free_module_data)(void *model_data)) +{ + PyObject *arglist, *result, *data = NULL; + char *str = NULL; + + arglist = Py_BuildValue("(ssssO)", mod_name, mod_rev, submod_name, sub_rev, schemaCallbackData ? schemaCallbackData : Py_None); + if (!arglist) { + PyErr_Print(); + return NULL; + } + result = PyObject_CallObject(schemaCallback, arglist); + Py_DECREF(arglist); + + if (result) { + if (!PyArg_ParseTuple(result, "iU", format, &data)) { + Py_DECREF(result); + return NULL; + } + Py_DECREF(result); + *free_module_data = free; + str = strdup(PyUnicode_AsUTF8(data)); + Py_DECREF(data); + } + + return str; +} + +static PyObject * +setSchemaCallback(PyObject *self, PyObject *args, PyObject *keywds) +{ + PyObject *clb = NULL, *data = NULL; + static char *kwlist[] = {"func", "priv", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|O:setSchemaCallback", kwlist, &clb, &data)) { + return NULL; + } + + if (!clb || clb == Py_None) { + Py_XDECREF(schemaCallback); + Py_XDECREF(schemaCallbackData); + data = NULL; + } else if (!PyCallable_Check(clb)) { + PyErr_SetString(PyExc_TypeError, "The callback must be a function."); + return NULL; + } else { + Py_XDECREF(schemaCallback); + Py_XDECREF(schemaCallbackData); + + Py_INCREF(clb); + if (data) { + Py_INCREF(data); + } + } + nc_client_set_schema_callback(schemaCallbackWrapper, NULL); + schemaCallback = clb; + schemaCallbackData = data; + + Py_RETURN_NONE; +} + static PyMethodDef netconf2Methods[] = { {"setVerbosity", (PyCFunction)setVerbosity, METH_VARARGS | METH_KEYWORDS, "setVerbosity(level)\n--\n\n" @@ -150,6 +216,11 @@ static PyMethodDef netconf2Methods[] = { ":param path: Search directory.\n" ":type path: string\n" ":returns: None\n"}, + {"setSchemaCallback", (PyCFunction)setSchemaCallback, METH_VARARGS | METH_KEYWORDS, + "Set schema search callaback.\n\n" + "setSchemaCallback(func, priv=None)\n" + "with func(str mod_name, str mod_rev, str submod_name, str submod_rev, priv)\n" + "callback returns tuple of format (e.g. LYS_IN_YANG) and string of the schema content.\n"}, {NULL, NULL, 0, NULL} }; diff --git a/python/session.c b/python/session.c index 3b2545b3..c4c77db9 100644 --- a/python/session.c +++ b/python/session.c @@ -159,7 +159,6 @@ auth_interactive_pyclb(const char *auth_name, const char *instruction, const cha } return password; - } char * @@ -277,6 +276,10 @@ ncSessionInit(ncSessionObject *self, PyObject *args, PyObject *kwds) return -1; } + if (PyErr_Occurred()) { + PyErr_PrintEx(0); + } + /* get the internally created context for this session */ self->ctx = nc_session_get_ctx(session); diff --git a/src/io.c b/src/io.c index 88f67458..a0fc9dca 100644 --- a/src/io.c +++ b/src/io.c @@ -226,7 +226,7 @@ nc_read_until(struct nc_session *session, const char *endtag, size_t limit, uint if ((count + (len - matched)) >= size) { /* get more memory */ size = size + BUFFERSIZE; - chunk = realloc(chunk, (size + 1) * sizeof *chunk); + chunk = nc_realloc(chunk, (size + 1) * sizeof *chunk); if (!chunk) { ERRMEM; return -1; @@ -357,7 +357,7 @@ nc_read_msg_io(struct nc_session *session, int io_timeout, struct lyxml_elem **d } /* realloc message buffer, remember to count terminating null byte */ - msg = realloc(msg, len + chunk_len + 1); + msg = nc_realloc(msg, len + chunk_len + 1); if (!msg) { ERRMEM; ret = NC_MSG_ERROR; @@ -420,6 +420,12 @@ nc_read_msg_io(struct nc_session *session, int io_timeout, struct lyxml_elem **d /* NETCONF version 1.1 defines sending error reply from the server (RFC 6241 sec. 3) */ reply = nc_server_reply_err(nc_err(NC_ERR_MALFORMED_MSG)); + if (io_locked) { + /* nc_write_msg_io locks and unlocks the lock by itself */ + nc_session_io_unlock(session, __func__); + io_locked = 0; + } + if (nc_write_msg_io(session, io_timeout, NC_MSG_REPLY, NULL, reply) != NC_MSG_REPLY) { ERR("Session %u: unable to send a \"Malformed message\" error reply, terminating session.", session->id); if (session->status != NC_STATUS_INVALID) { diff --git a/src/libnetconf.h b/src/libnetconf.h index 50104b2c..e4eff090 100644 --- a/src/libnetconf.h +++ b/src/libnetconf.h @@ -394,7 +394,9 @@ * So, after starting listening on an endpoint you need to set the server * certificate (nc_server_tls_endpt_set_server_cert()). Its actual content * together with the matching private key will be loaded using a callback - * from nc_server_tls_set_server_cert_clb(). + * from nc_server_tls_set_server_cert_clb(). Additional certificates needed + * for the client to verify the server's certificate chain can be loaded using + * a callback from nc_server_tls_set_server_cert_chain_clb(). * * To accept client certificates, they must first be considered trusted, * which you have three ways of achieving. You can add each of their Certificate Authority @@ -428,6 +430,7 @@ * - nc_server_tls_endpt_get_ctn() * * - nc_server_tls_set_server_cert_clb() + * - nc_server_tls_set_server_cert_chain_clb() * - nc_server_tls_set_trusted_cert_list_clb() * * FD diff --git a/src/messages_server.h b/src/messages_server.h index 9d9d9c28..5ffb019a 100644 --- a/src/messages_server.h +++ b/src/messages_server.h @@ -16,6 +16,7 @@ #define NC_MESSAGES_SERVER_H_ #include +#include #include "netconf.h" #include "session.h" diff --git a/src/session.c b/src/session.c index 828734ee..40d656d4 100644 --- a/src/session.c +++ b/src/session.c @@ -933,9 +933,10 @@ nc_server_get_cpblts_version(struct ly_ctx *ctx, LYS_VERSION version) u = module_set_id = 0; while ((mod = ly_ctx_get_module_iter(ctx, &u))) { if (!strcmp(mod->name, "ietf-yang-library")) { - /* ietf-yang-library is always part of the list, but it is specific since it is 1.1 schema */ - sprintf(str, "%s?%s%s&module-set-id=%u", mod->ns, mod->rev_size ? "revision=" : "", - mod->rev_size ? mod->rev[0].date : "", ly_ctx_get_module_set_id(ctx)); + /* Add the yang-library NETCONF capability as defined in RFC 7950 5.6.4 */ + sprintf(str, "urn:ietf:params:netconf:capability:yang-library:1.0?%s%s&module-set-id=%u", + mod->rev_size ? "revision=" : "", mod->rev_size ? mod->rev[0].date : "", + ly_ctx_get_module_set_id(ctx)); add_cpblt(ctx, str, &cpblts, &size, &count); continue; } else if (mod->type) { @@ -967,7 +968,7 @@ nc_server_get_cpblts_version(struct ly_ctx *ctx, LYS_VERSION version) ERRINT; break; } - if (i) { + if (features_count) { strcat(str, ","); ++str_len; } diff --git a/src/session_client.c b/src/session_client.c index 0cc4bcb2..d0d778ff 100644 --- a/src/session_client.c +++ b/src/session_client.c @@ -263,97 +263,122 @@ nc_client_get_schema_callback(void **user_data) return client_opts.schema_clb; } -/* NC_SCHEMAS_DIR used as the last resort */ -static int -ctx_check_and_load_ietf_netconf(struct ly_ctx *ctx, char **cpblts) + +struct schema_info { + char *name; + char *revision; + struct { + char *name; + char *revision; + } *submodules; + struct ly_set features; + int implemented; +}; + +struct clb_data_s { + void *user_data; + ly_module_imp_clb user_clb; + struct schema_info *schemas; + struct nc_session *session; + int has_get_schema; +}; + +static char * +retrieve_schema_data_localfile(const char *name, const char *rev, struct clb_data_s *clb_data, + LYS_INFORMAT *format) { - int i; - const struct lys_module *ietfnc; + char *localfile = NULL; + FILE *f; + long length, l; + char *model_data = NULL; - ietfnc = ly_ctx_get_module(ctx, "ietf-netconf", NULL, 1); - if (!ietfnc) { - ietfnc = ly_ctx_load_module(ctx, "ietf-netconf", NULL); - if (!ietfnc) { - ietfnc = lys_parse_path(ctx, NC_SCHEMAS_DIR"/ietf-netconf.yin", LYS_IN_YIN); - } - } - if (!ietfnc) { - ERR("Loading base NETCONF schema failed."); - return 1; + if (lys_search_localfile(ly_ctx_get_searchdirs(clb_data->session->ctx), + !(ly_ctx_get_options(clb_data->session->ctx) & LY_CTX_DISABLE_SEARCHDIR_CWD), + name, rev, &localfile, format)) { + return NULL; } + if (localfile) { + VRB("Session %u: reading schema from localfile \"%s\".", clb_data->session->id, localfile); + f = fopen(localfile, "r"); + if (!f) { + ERR("Session %u: unable to open \"%s\" file to get schema (%s).", + clb_data->session->id, localfile, strerror(errno)); + free(localfile); + return NULL; + } - /* set supported capabilities from ietf-netconf */ - for (i = 0; cpblts[i]; ++i) { - if (!strncmp(cpblts[i], "urn:ietf:params:netconf:capability:", 35)) { - if (!strncmp(cpblts[i] + 35, "writable-running", 16)) { - lys_features_enable(ietfnc, "writable-running"); - } else if (!strncmp(cpblts[i] + 35, "candidate", 9)) { - lys_features_enable(ietfnc, "candidate"); - } else if (!strcmp(cpblts[i] + 35, "confirmed-commit:1.1")) { - lys_features_enable(ietfnc, "confirmed-commit"); - } else if (!strncmp(cpblts[i] + 35, "rollback-on-error", 17)) { - lys_features_enable(ietfnc, "rollback-on-error"); - } else if (!strcmp(cpblts[i] + 35, "validate:1.1")) { - lys_features_enable(ietfnc, "validate"); - } else if (!strncmp(cpblts[i] + 35, "startup", 7)) { - lys_features_enable(ietfnc, "startup"); - } else if (!strncmp(cpblts[i] + 35, "url", 3)) { - lys_features_enable(ietfnc, "url"); - } else if (!strncmp(cpblts[i] + 35, "xpath", 5)) { - lys_features_enable(ietfnc, "xpath"); - } + fseek(f, 0, SEEK_END); + length = ftell(f); + if (length < 0) { + ERR("Session %u: unable to get size of schema file \"%s\".", + clb_data->session->id, localfile); + free(localfile); + fclose(f); + return NULL; + } + fseek(f, 0, SEEK_SET); + + model_data = malloc(length + 1); + if (!model_data) { + ERRMEM; + } else if ((l = fread(model_data, 1, length, f)) != length) { + ERR("Session %u: reading schema from \"%s\" failed (%d bytes read, but %d expected).", + clb_data->session->id, localfile, l, length); + free(model_data); + model_data = NULL; + } else { + /* terminating NULL byte */ + model_data[length] = '\0'; } + fclose(f); + free(localfile); } - return 0; + return model_data; } static char * -getschema_module_clb(const char *mod_name, const char *mod_rev, const char *submod_name, const char *submod_rev, - void *user_data, LYS_INFORMAT *format, void (**free_model_data)(void *model_data)) +retrieve_schema_data_getschema(const char *name, const char *rev, struct clb_data_s *clb_data, + LYS_INFORMAT *format) { - struct nc_session *session = (struct nc_session *)user_data; struct nc_rpc *rpc; struct nc_reply *reply; struct nc_reply_data *data_rpl; struct nc_reply_error *error_rpl; struct lyd_node_anydata *get_schema_data; NC_MSG_TYPE msg; - char *model_data = NULL; uint64_t msgid; - char *filename = NULL; - FILE *output; + char *localfile = NULL; + FILE *f; + char *model_data = NULL; - if (submod_name) { - rpc = nc_rpc_getschema(submod_name, submod_rev, "yang", NC_PARAMTYPE_CONST); - } else { - rpc = nc_rpc_getschema(mod_name, mod_rev, "yang", NC_PARAMTYPE_CONST); - } + VRB("Session %u: reading schema from server via get-schema.", clb_data->session->id); + rpc = nc_rpc_getschema(name, rev, "yang", NC_PARAMTYPE_CONST); - while ((msg = nc_send_rpc(session, rpc, 0, &msgid)) == NC_MSG_WOULDBLOCK) { + while ((msg = nc_send_rpc(clb_data->session, rpc, 0, &msgid)) == NC_MSG_WOULDBLOCK) { usleep(1000); } if (msg == NC_MSG_ERROR) { - ERR("Session %u: failed to send the RPC.", session->id); + ERR("Session %u: failed to send the RPC.", clb_data->session->id); nc_rpc_free(rpc); return NULL; } do { - msg = nc_recv_reply(session, rpc, msgid, NC_READ_ACT_TIMEOUT * 1000, 0, &reply); + msg = nc_recv_reply(clb_data->session, rpc, msgid, NC_READ_ACT_TIMEOUT * 1000, 0, &reply); } while (msg == NC_MSG_NOTIF); nc_rpc_free(rpc); if (msg == NC_MSG_WOULDBLOCK) { - ERR("Session %u: timeout for receiving reply to a expired.", session->id); + ERR("Session %u: timeout for receiving reply to a expired.", clb_data->session->id); return NULL; } else if (msg == NC_MSG_ERROR) { - ERR("Session %u: failed to receive a reply to .", session->id); + ERR("Session %u: failed to receive a reply to .", clb_data->session->id); return NULL; } switch (reply->type) { case NC_RPL_OK: - ERR("Session %u: unexpected reply OK to a RPC.", session->id); + ERR("Session %u: unexpected reply OK to a RPC.", clb_data->session->id); nc_reply_free(reply); return NULL; case NC_RPL_DATA: @@ -363,14 +388,14 @@ getschema_module_clb(const char *mod_name, const char *mod_rev, const char *subm error_rpl = (struct nc_reply_error *)reply; if (error_rpl->count) { ERR("Session %u: error reply to a RPC (tag \"%s\", message \"%s\").", - session->id, error_rpl->err[0].tag, error_rpl->err[0].message); + clb_data->session->id, error_rpl->err[0].tag, error_rpl->err[0].message); } else { - ERR("Session %u: unexpected reply error to a RPC.", session->id); + ERR("Session %u: unexpected reply error to a RPC.", clb_data->session->id); } nc_reply_free(reply); return NULL; case NC_RPL_NOTIF: - ERR("Session %u: unexpected reply notification to a RPC.", session->id); + ERR("Session %u: unexpected reply notification to a RPC.", clb_data->session->id); nc_reply_free(reply); return NULL; } @@ -378,7 +403,7 @@ getschema_module_clb(const char *mod_name, const char *mod_rev, const char *subm data_rpl = (struct nc_reply_data *)reply; if ((data_rpl->data->schema->nodetype != LYS_RPC) || strcmp(data_rpl->data->schema->name, "get-schema") || !data_rpl->data->child || (data_rpl->data->child->schema->nodetype != LYS_ANYXML)) { - ERR("Session %u: unexpected data in reply to a RPC.", session->id); + ERR("Session %u: unexpected data in reply to a RPC.", clb_data->session->id); nc_reply_free(reply); return NULL; } @@ -398,85 +423,192 @@ getschema_module_clb(const char *mod_name, const char *mod_rev, const char *subm case LYD_ANYDATA_JSOND: case LYD_ANYDATA_SXML: case LYD_ANYDATA_SXMLD: + case LYD_ANYDATA_LYB: + case LYD_ANYDATA_LYBD: ERRINT; - break; + nc_reply_free(reply); } nc_reply_free(reply); - *free_model_data = free; - *format = LYS_IN_YANG; + + if (model_data && !model_data[0]) { + /* empty data */ + free(model_data); + model_data = NULL; + } /* try to store the model_data into local schema repository */ - if (model_data && client_opts.schema_searchpath) { - if (asprintf(&filename, "%s/%s%s%s.yang", client_opts.schema_searchpath, mod_name, - mod_rev ? "@" : "", mod_rev ? mod_rev : "") == -1) { - ERRMEM; - } else { - output = fopen(filename, "w"); - if (!output) { - WRN("Unable to store \"%s\" as a local copy of schema retreived via (%s).", - filename, strerror(errno)); + if (model_data) { + *format = LYS_IN_YANG; + if (client_opts.schema_searchpath) { + if (asprintf(&localfile, "%s/%s%s%s.yang", client_opts.schema_searchpath, name, + rev ? "@" : "", rev ? rev : "") == -1) { + ERRMEM; } else { - fputs(model_data, output); - fclose(output); + f = fopen(localfile, "w"); + if (!f) { + WRN("Unable to store \"%s\" as a local copy of schema retrieved via (%s).", + localfile, strerror(errno)); + } else { + fputs(model_data, f); + fclose(f); + } + free(localfile); } - free(filename); } } return model_data; } +static void free_with_user_data(void *data, void *user_data) +{ + free(data); + (void)user_data; +} + +static const char * +retrieve_schema_data(const char *mod_name, const char *mod_rev, const char *submod_name, const char *sub_rev, + void *user_data, LYS_INFORMAT *format, void (**free_module_data)(void *model_data, void *user_data)) +{ + struct clb_data_s *clb_data = (struct clb_data_s *)user_data; + unsigned int u, v, match = 1; + const char *name = NULL, *rev = NULL; + char *model_data = NULL; + + /* get and check the final name and revision of the schema to be retrieved */ + if (!mod_rev || !mod_rev[0]) { + /* newest revision requested - get the newest revision from the list of available modules on server */ + match = 0; + for (u = 0; clb_data->schemas[u].name; ++u) { + if (strcmp(mod_name, clb_data->schemas[u].name)) { + continue; + } + if (!match || strcmp(mod_rev, clb_data->schemas[u].revision) > 0) { + mod_rev = clb_data->schemas[u].revision; + } + match = u + 1; + } + if (!match) { + WRN("Session %u: unable to identify revision of the schema \"%s\" from the available server side information.", + clb_data->session->id, mod_name); + } + } + if (submod_name) { + name = submod_name; + if (sub_rev) { + rev = sub_rev; + } else if (match) { + if (!clb_data->schemas[match - 1].submodules) { + WRN("Session %u: Unable to identify revision of the requested submodule \"%s\", in schema \"%s\", from the available server side information.", + clb_data->session->id, submod_name, mod_name); + } else { + for (v = 0; clb_data->schemas[match - 1].submodules[v].name; ++v) { + if (!strcmp(submod_name, clb_data->schemas[match - 1].submodules[v].name)) { + rev = sub_rev = clb_data->schemas[match - 1].submodules[v].revision; + } + } + if (!rev) { + ERR("Session %u: requested submodule \"%s\" is not known for schema \"%s\" on server side.", + clb_data->session->id, submod_name, mod_name); + return NULL; + } + } + } + } else { + name = mod_name; + rev = mod_rev; + } + + VRB("Session %u: retreiving data for schema \"%s\", revision \"%s\".", clb_data->session->id, name, rev); + + if (match) { + /* we have enough information to avoid communication with server and try to get + * the schema locally */ + + /* 1. try to get data locally */ + model_data = retrieve_schema_data_localfile(name, rev, clb_data, format); + + /* 2. try to use */ + if (!model_data && clb_data->has_get_schema) { + model_data = retrieve_schema_data_getschema(name, rev, clb_data, format); + } + } else { + /* we are unsure which revision of the schema we should load, so first try to get + * the newest revision from the server via get-schema and only if the server does not + * implement get-schema, try to load the newest revision locally. This is imperfect + * solution, but there are situation when a client does not know what revision is + * actually implemented by the server. */ + + /* 1. try to use */ + if (clb_data->has_get_schema) { + model_data = retrieve_schema_data_getschema(name, rev, clb_data, format); + } + + /* 2. try to get data locally */ + if (!model_data) { + model_data = retrieve_schema_data_localfile(name, rev, clb_data, format); + } + } + + /* 3. try to use user callback */ + if (!model_data && clb_data->user_clb) { + VRB("Session %u: reading schema via user callback.", clb_data->session->id); + return clb_data->user_clb(mod_name, mod_rev, submod_name, sub_rev, clb_data->user_data, format, free_module_data); + } + + *free_module_data = free_with_user_data; + return model_data; +} + static int -nc_ctx_load_module(struct nc_session *session, const char *name, const char *revision, int implement, +nc_ctx_load_module(struct nc_session *session, const char *name, const char *revision, struct schema_info *schemas, ly_module_imp_clb user_clb, void *user_data, int has_get_schema, const struct lys_module **mod) { int ret = 0; struct ly_err_item *eitem; + const char *module_data = NULL; + LYS_INFORMAT format; + void (*free_module_data)(void*, void*) = NULL; + struct clb_data_s clb_data; - *mod = ly_ctx_get_module(session->ctx, name, revision, 0); + *mod = NULL; + if (revision) { + *mod = ly_ctx_get_module(session->ctx, name, revision, 0); + } if (*mod) { - if (implement && !(*mod)->implemented) { + if (!(*mod)->implemented) { /* make the present module implemented */ if (lys_set_implemented(*mod)) { ERR("Failed to implement model \"%s\".", (*mod)->name); ret = -1; } } - } else if (!(*mod) && implement) { + } else { /* missing implemented module, load it ... */ + clb_data.has_get_schema = has_get_schema; + clb_data.schemas = schemas; + clb_data.session = session; + clb_data.user_clb = user_clb; + clb_data.user_data = user_data; /* clear all the errors and just collect them for now */ ly_err_clean(session->ctx, NULL); ly_log_options(LY_LOSTORE); - /* 1) using only searchpaths */ - *mod = ly_ctx_load_module(session->ctx, name, revision); - - /* 2) using user callback */ - if (!(*mod) && user_clb) { - ly_ctx_set_module_imp_clb(session->ctx, user_clb, user_data); - *mod = ly_ctx_load_module(session->ctx, name, revision); - } - - /* 3) using get-schema callback */ - if (has_get_schema && !(*mod)) { - ly_ctx_set_module_imp_clb(session->ctx, &getschema_module_clb, session); - *mod = ly_ctx_load_module(session->ctx, name, revision); - if (*mod) { - /* print get-schema warning */ - ly_log_options(LY_LOLOG); - eitem = ly_err_first(session->ctx); - while (eitem) { - if (eitem->level == LY_LLWRN) { - ly_err_print(eitem); - } - eitem = eitem->next; - } + /* get module data */ + module_data = retrieve_schema_data(name, revision, NULL, NULL, &clb_data, &format, &free_module_data); + + if (module_data) { + /* parse the schema */ + ly_ctx_set_module_imp_clb(session->ctx, retrieve_schema_data, &clb_data); + + *mod = lys_parse_mem(session->ctx, module_data, format); + if (*free_module_data) { + (*free_module_data)((char*)module_data, user_data); } - } - /* unset callback back to use searchpath */ - ly_ctx_set_module_imp_clb(session->ctx, NULL, NULL); + ly_ctx_set_module_imp_clb(session->ctx, NULL, NULL); + } /* restore logging options, then print errors on definite failure */ ly_log_options(LY_LOLOG | LY_LOSTORE_LAST); @@ -485,6 +617,13 @@ nc_ctx_load_module(struct nc_session *session, const char *name, const char *rev ly_err_print(eitem); } ret = -1; + } else { + /* print only warnings */ + for (eitem = ly_err_first(session->ctx); eitem && eitem->next; eitem = eitem->next) { + if (eitem->level == LY_LLWRN) { + ly_err_print(eitem); + } + } } /* clean the errors */ @@ -494,125 +633,55 @@ nc_ctx_load_module(struct nc_session *session, const char *name, const char *rev return ret; } -/* NC_SCHEMAS_DIR not used (implicitly) */ -static int -nc_ctx_fill_cpblts(struct nc_session *session, ly_module_imp_clb user_clb, void *user_data, int has_get_schema) +static void +free_schema_info(struct schema_info *list) { - int ret = 1; - const struct lys_module *mod; - char *ptr, *ptr2; - const char *module_cpblt; - char *name = NULL, *revision = NULL, *features = NULL; - unsigned int u; - - for (u = 0; session->opts.client.cpblts[u]; ++u) { - module_cpblt = strstr(session->opts.client.cpblts[u], "module="); - /* this capability requires a module */ - if (!module_cpblt) { - continue; - } + unsigned int u, v; - /* get module name */ - ptr = (char *)module_cpblt + 7; - ptr2 = strchr(ptr, '&'); - if (!ptr2) { - ptr2 = ptr + strlen(ptr); - } - free(name); - name = strndup(ptr, ptr2 - ptr); + if (!list) { + return; + } - /* get module revision */ - free(revision); revision = NULL; - ptr = strstr(module_cpblt, "revision="); - if (ptr) { - ptr += 9; - ptr2 = strchr(ptr, '&'); - if (!ptr2) { - ptr2 = ptr + strlen(ptr); - } - revision = strndup(ptr, ptr2 - ptr); + for (u = 0; list[u].name; ++u) { + free(list[u].name); + free(list[u].revision); + for (v = 0; v < list[u].features.number; ++v) { + free(list[u].features.set.g[v]); } - - /* we can continue even if it fails */ - nc_ctx_load_module(session, name, revision, 1, user_clb, user_data, has_get_schema, &mod); - - if (!mod) { - if (session->status != NC_STATUS_RUNNING) { - /* something bad heppened, discard the session */ - ERR("Session %d: invalid session, discarding.", nc_session_get_id(session)); - ret = 1; - goto cleanup; - } - - /* all loading ways failed, the schema will be ignored in the received data */ - WRN("Failed to load schema \"%s@%s\".", name, revision ? revision : ""); - session->flags |= NC_SESSION_CLIENT_NOT_STRICT; - } else { - /* set features - first disable all to enable specified then */ - lys_features_disable(mod, "*"); - - ptr = strstr(module_cpblt, "features="); - if (ptr) { - ptr += 9; - ptr2 = strchr(ptr, '&'); - if (!ptr2) { - ptr2 = ptr + strlen(ptr); - } - free(features); - features = strndup(ptr, ptr2 - ptr); - - /* basically manual strtok_r (to avoid macro) */ - ptr2 = features; - for (ptr = features; *ptr; ++ptr) { - if (*ptr == ',') { - *ptr = '\0'; - /* remember last feature */ - ptr2 = ptr + 1; - } - } - - ptr = features; - while (1) { - lys_features_enable(mod, ptr); - if (ptr != ptr2) { - ptr += strlen(ptr) + 1; - } else { - break; - } - } + free(list[u].features.set.g); + if (list[u].submodules) { + for (v = 0; list[u].submodules[v].name; ++v) { + free(list[u].submodules[v].name); + free(list[u].submodules[v].revision); } + free(list[u].submodules); } } - - ret = 0; - -cleanup: - free(name); - free(revision); - free(features); - - return ret; + free(list); } + static int -nc_ctx_fill_yl(struct nc_session *session, ly_module_imp_clb user_clb, void *user_data, int has_get_schema) +build_schema_info_yl(struct nc_session *session, struct schema_info **result) { - int ret = 1; struct nc_rpc *rpc = NULL; struct nc_reply *reply = NULL; struct nc_reply_error *error_rpl; - struct nc_reply_data *data_rpl; + struct lyd_node *yldata = NULL; NC_MSG_TYPE msg; uint64_t msgid; - struct lyd_node *data = NULL, *iter; - struct ly_set *modules = NULL, *imports = NULL, *features = NULL; - unsigned int u, v; - const char *name, *revision; - int implemented, imports_flag = 0; - const struct lys_module *mod; + struct ly_set *modules = NULL; + unsigned int u, v, submodules_count; + struct lyd_node *iter, *child; + struct lys_module *mod; + int ret = EXIT_SUCCESS; /* get yang-library data from the server */ - rpc = nc_rpc_get("/ietf-yang-library:*", 0, NC_PARAMTYPE_CONST); + if (nc_session_cpblt(session, "urn:ietf:params:netconf:capability:xpath:1.0")) { + rpc = nc_rpc_get("/ietf-yang-library:*", 0, NC_PARAMTYPE_CONST); + } else { + rpc = nc_rpc_get("", 0, NC_PARAMTYPE_CONST); + } if (!rpc) { goto cleanup; } @@ -621,7 +690,7 @@ nc_ctx_fill_yl(struct nc_session *session, ly_module_imp_clb user_clb, void *use usleep(1000); } if (msg == NC_MSG_ERROR) { - ERR("Session %u: failed to send request for yang-library data.", + WRN("Session %u: failed to send request for yang-library data.", session->id); goto cleanup; } @@ -630,16 +699,16 @@ nc_ctx_fill_yl(struct nc_session *session, ly_module_imp_clb user_clb, void *use msg = nc_recv_reply(session, rpc, msgid, NC_READ_ACT_TIMEOUT * 1000, 0, &reply); } while (msg == NC_MSG_NOTIF); if (msg == NC_MSG_WOULDBLOCK) { - ERR("Session %u: timeout for receiving reply to a yang-library data expired.", session->id); + WRN("Session %u: timeout for receiving reply to a yang-library data expired.", session->id); goto cleanup; } else if (msg == NC_MSG_ERROR) { - ERR("Session %u: failed to receive a reply to of yang-library data.", session->id); + WRN("Session %u: failed to receive a reply to of yang-library data.", session->id); goto cleanup; } switch (reply->type) { case NC_RPL_OK: - ERR("Session %u: unexpected reply OK to a yang-library RPC.", session->id); + WRN("Session %u: unexpected reply OK to a yang-library RPC.", session->id); goto cleanup; case NC_RPL_DATA: /* fine */ @@ -647,200 +716,345 @@ nc_ctx_fill_yl(struct nc_session *session, ly_module_imp_clb user_clb, void *use case NC_RPL_ERROR: error_rpl = (struct nc_reply_error *)reply; if (error_rpl->count) { - ERR("Session %u: error reply to a yang-library RPC (tag \"%s\", message \"%s\").", + WRN("Session %u: error reply to a yang-library RPC (tag \"%s\", message \"%s\").", session->id, error_rpl->err[0].tag, error_rpl->err[0].message); } else { - ERR("Session %u: unexpected reply error to a yang-library RPC.", session->id); + WRN("Session %u: unexpected reply error to a yang-library RPC.", session->id); } goto cleanup; case NC_RPL_NOTIF: - ERR("Session %u: unexpected reply notification to a yang-library RPC.", session->id); + WRN("Session %u: unexpected reply notification to a yang-library RPC.", session->id); goto cleanup; } - data_rpl = (struct nc_reply_data *)reply; - if (!data_rpl->data || strcmp(data_rpl->data->schema->module->name, "ietf-yang-library")) { - ERR("Session %u: unexpected data in reply to a yang-library RPC.", session->id); + yldata = ((struct nc_reply_data *)reply)->data; + if (!yldata || strcmp(yldata->schema->module->name, "ietf-yang-library")) { + WRN("Session %u: unexpected data in reply to a yang-library RPC.", session->id); goto cleanup; } - modules = lyd_find_path(data_rpl->data, "/ietf-yang-library:modules-state/module"); - if (!modules || !modules->number) { - ERR("No yang-library modules information for session %u.", session->id); + modules = lyd_find_path(yldata, "/ietf-yang-library:modules-state/module"); + if (!modules) { + WRN("Session %u: no module information in reply to a yang-library RPC.", session->id); goto cleanup; } - features = ly_set_new(); - imports = ly_set_new(); - -parse: - for (u = modules->number - 1; u < modules->number; u--) { - name = revision = NULL; - ly_set_clean(features); - implemented = 0; + (*result) = calloc(modules->number + 1, sizeof **result); + if (!(*result)) { + ERRMEM; + ret = EXIT_FAILURE; + goto cleanup; + } - /* store the data */ + for (u = 0; u < modules->number; ++u) { + submodules_count = 0; + mod = ((struct lyd_node *)modules->set.d[u])->schema->module; LY_TREE_FOR(modules->set.d[u]->child, iter) { + if (iter->schema->module != mod) { + /* ignore node from other schemas (augments) */ + continue; + } if (!((struct lyd_node_leaf_list *)iter)->value_str || !((struct lyd_node_leaf_list *)iter)->value_str[0]) { /* ignore empty nodes */ continue; } if (!strcmp(iter->schema->name, "name")) { - name = ((struct lyd_node_leaf_list *)iter)->value_str; + (*result)[u].name = strdup(((struct lyd_node_leaf_list *)iter)->value_str); } else if (!strcmp(iter->schema->name, "revision")) { - revision = ((struct lyd_node_leaf_list *)iter)->value_str; + (*result)[u].revision = strdup(((struct lyd_node_leaf_list *)iter)->value_str); } else if (!strcmp(iter->schema->name, "conformance-type")) { - implemented = !strcmp(((struct lyd_node_leaf_list *)iter)->value_str, "implement"); + (*result)[u].implemented = !strcmp(((struct lyd_node_leaf_list *)iter)->value_str, "implement"); } else if (!strcmp(iter->schema->name, "feature")) { - ly_set_add(features, (void *)((struct lyd_node_leaf_list *)iter)->value_str, LY_SET_OPT_USEASLIST); + ly_set_add(&(*result)[u].features, (void *)strdup(((struct lyd_node_leaf_list *)iter)->value_str), LY_SET_OPT_USEASLIST); + } else if (!strcmp(iter->schema->name, "submodule")) { + submodules_count++; } } - /* continue even on fail */ - nc_ctx_load_module(session, name, revision, implemented, user_clb, user_data, has_get_schema, &mod); - - if (!mod && !implemented) { /* will be loaded automatically, but remember to set features in the end */ - if (imports_flag) { - ERR("Module \"%s@%s\" is supposed to be imported, but no other module imports it.", - name, revision ? revision : ""); - ret = -1; + if (submodules_count) { + (*result)[u].submodules = calloc(submodules_count + 1, sizeof *(*result)[u].submodules); + if (!(*result)[u].submodules) { + ERRMEM; + free_schema_info(*result); + *result = NULL; + ret = EXIT_FAILURE; goto cleanup; + } else { + v = 0; + LY_TREE_FOR(modules->set.d[u]->child, iter) { + mod = ((struct lyd_node *)modules->set.d[u])->schema->module; + if (mod == iter->schema->module && !strcmp(iter->schema->name, "submodule")) { + LY_TREE_FOR(iter->child, child) { + if (mod != child->schema->module) { + continue; + } else if (!strcmp(child->schema->name, "name")) { + (*result)[u].submodules[v].name = strdup(((struct lyd_node_leaf_list *)child)->value_str); + } else if (!strcmp(child->schema->name, "revision")) { + (*result)[u].submodules[v].name = strdup(((struct lyd_node_leaf_list *)child)->value_str); + } + } + } + } } - ly_set_add(imports, modules->set.d[u], LY_SET_OPT_USEASLIST); + } + } + +cleanup: + nc_rpc_free(rpc); + nc_reply_free(reply); + ly_set_free(modules); + + if (session->status != NC_STATUS_RUNNING) { + /* something bad heppened, discard the session */ + ERR("Session %d: invalid session, discarding.", nc_session_get_id(session)); + ret = EXIT_FAILURE; + } + + return ret; +} + +static int +build_schema_info_cpblts(char **cpblts, struct schema_info **result) +{ + unsigned int u, v; + char *module_cpblt, *ptr, *ptr2; + + for (u = 0; cpblts[u]; ++u); + (*result) = calloc(u + 1, sizeof **result); + if (!(*result)) { + ERRMEM; + return EXIT_FAILURE; + } + + for (u = v = 0; cpblts[u]; ++u) { + module_cpblt = strstr(cpblts[u], "module="); + /* this capability requires a module */ + if (!module_cpblt) { continue; } + /* get module's name */ + ptr = (char *)module_cpblt + 7; + ptr2 = strchr(ptr, '&'); + if (!ptr2) { + ptr2 = ptr + strlen(ptr); + } + (*result)[v].name = strndup(ptr, ptr2 - ptr); + + /* get module's revision */ + ptr = strstr(module_cpblt, "revision="); + if (ptr) { + ptr += 9; + ptr2 = strchr(ptr, '&'); + if (!ptr2) { + ptr2 = ptr + strlen(ptr); + } + (*result)[v].revision = strndup(ptr, ptr2 - ptr); + } + + /* all are implemented since there is no better information in capabilities list */ + (*result)[v].implemented = 1; + + /* get module's features */ + ptr = strstr(module_cpblt, "features="); + if (ptr) { + ptr += 9; + for (ptr2 = ptr; *ptr && *ptr != '&'; ++ptr) { + if (*ptr == ',') { + ly_set_add(&(*result)[v].features, (void *)strndup(ptr2, ptr - ptr2), LY_SET_OPT_USEASLIST); + ptr2 = ptr + 1; + } + } + /* the last one */ + ly_set_add(&(*result)[v].features, (void *)strndup(ptr2, ptr - ptr2), LY_SET_OPT_USEASLIST); + } + ++v; + } + + return EXIT_SUCCESS; +} + +static int +nc_ctx_fill(struct nc_session *session, struct schema_info *modules, ly_module_imp_clb user_clb, void *user_data, int has_get_schema) +{ + int ret = EXIT_FAILURE; + const struct lys_module *mod; + unsigned int u, v; + + for (u = 0; modules[u].name; ++u) { + /* skip import-only modules */ + if (!modules[u].implemented) { + continue; + } + + /* we can continue even if it fails */ + nc_ctx_load_module(session, modules[u].name, modules[u].revision, modules, user_clb, user_data, has_get_schema, &mod); + if (!mod) { + if (session->status != NC_STATUS_RUNNING) { + /* something bad heppened, discard the session */ + ERR("Session %d: invalid session, discarding.", nc_session_get_id(session)); + goto cleanup; + } + /* all loading ways failed, the schema will be ignored in the received data */ - WRN("Failed to load schema \"%s@%s\".", name, revision ? revision : ""); + WRN("Failed to load schema \"%s@%s\".", modules[u].name, modules[u].revision ? modules[u].revision : ""); session->flags |= NC_SESSION_CLIENT_NOT_STRICT; } else { /* set features - first disable all to enable specified then */ lys_features_disable(mod, "*"); - for (v = 0; v < features->number; v++) { - lys_features_enable(mod, (const char*)features->set.g[v]); + for (v = 0; v < modules[u].features.number; v++) { + lys_features_enable(mod, (const char*)modules[u].features.set.g[v]); } } } - if (!imports_flag && imports->number) { - /* even imported modules should be now loaded as dependency, so just go through - * the parsing again and just set the features */ - ly_set_free(modules); - modules = imports; - imports = NULL; - imports_flag = 1; - goto parse; - } - /* done */ - ret = 0; + ret = EXIT_SUCCESS; cleanup: - nc_rpc_free(rpc); - nc_reply_free(reply); - lyd_free_withsiblings(data); - ly_set_free(modules); - ly_set_free(imports); - ly_set_free(features); + return ret; +} - if (session->status != NC_STATUS_RUNNING) { - ERR("Session %d: invalid session, discarding.", nc_session_get_id(session)); - ret = -1; +static int +nc_ctx_fill_ietf_netconf(struct nc_session *session, struct schema_info *modules, ly_module_imp_clb user_clb, void *user_data, int has_get_schema) +{ + unsigned int u, v; + const struct lys_module *ietfnc; + + ietfnc = ly_ctx_get_module(session->ctx, "ietf-netconf", NULL, 1); + if (!ietfnc) { + nc_ctx_load_module(session, "ietf-netconf", NULL, modules, user_clb, user_data, has_get_schema, &ietfnc); + if (!ietfnc) { + WRN("Unable to find correct \"ietf-netconf\" schema, trying to use backup from \"%s\".", NC_SCHEMAS_DIR"/ietf-netconf.yin"); + ietfnc = lys_parse_path(session->ctx, NC_SCHEMAS_DIR"/ietf-netconf.yin", LYS_IN_YIN); + } + } + if (!ietfnc) { + ERR("Loading base NETCONF schema failed."); + return 1; } - return ret; + /* set supported capabilities from ietf-netconf */ + for (u = 0; modules[u].name; ++u) { + if (strcmp(modules[u].name, "ietf-netconf") || !modules[u].implemented) { + continue; + } + + lys_features_disable(ietfnc, "*"); + for (v = 0; v < modules[u].features.number; v++) { + lys_features_enable(ietfnc, (const char*)modules[u].features.set.g[v]); + } + } + + return 0; } int nc_ctx_check_and_fill(struct nc_session *session) { - int i, get_schema_support = 0, yanglib_support = 0, ret = -1, r; + int i, get_schema_support = 0, yanglib_support = 0, ret = -1; ly_module_imp_clb old_clb = NULL; void *old_data = NULL; const struct lys_module *mod = NULL; char *revision; + struct schema_info *server_modules = NULL, *sm = NULL; assert(session->opts.client.cpblts && session->ctx); - /* store the original user's callback, here we will be switching between searchpath, user callback - * and get-schema callback */ + /* store the original user's callback, we will be switching between local search, get-schema and user callback */ old_clb = ly_ctx_get_module_imp_clb(session->ctx, &old_data); - ly_ctx_set_module_imp_clb(session->ctx, NULL, NULL); /* unset callback, so we prefer local searchpath */ - /* check if get-schema is supported */ + /* switch off default searchpath to use only our callback integrating modifying searchpath algorithm to limit + * schemas only to those present on the server side */ + ly_ctx_set_disable_searchdirs(session->ctx); + + /* our callback is set later with appropriate data */ + ly_ctx_set_module_imp_clb(session->ctx, NULL, NULL); + + /* check if get-schema and yang-library is supported */ for (i = 0; session->opts.client.cpblts[i]; ++i) { if (!strncmp(session->opts.client.cpblts[i], "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?", 52)) { get_schema_support = 1 + i; if (yanglib_support) { break; } - } else if (!strncmp(session->opts.client.cpblts[i], "urn:ietf:params:xml:ns:yang:ietf-yang-library?", 46)) { + } else if (!strncmp(session->opts.client.cpblts[i], "urn:ietf:params:netconf:capability:yang-library:1.0", 51)) { yanglib_support = 1 + i; if (get_schema_support) { break; } } } + + /* get information about server's schemas from capabilities list until we will have yang-library */ + if (build_schema_info_cpblts(session->opts.client.cpblts, &server_modules) || !server_modules) { + ERR("Session %u: unable to get server's schema information from the 's capabilities.", session->id); + goto cleanup; + } + /* get-schema is supported, load local ietf-netconf-monitoring so we can create RPCs */ if (get_schema_support && !ly_ctx_get_module(session->ctx, "ietf-netconf-monitoring", NULL, 1)) { - if (!lys_parse_path(session->ctx, NC_SCHEMAS_DIR"/ietf-netconf-monitoring.yin", LYS_IN_YIN)) { - WRN("Loading NETCONF monitoring schema failed, cannot use ."); + if (nc_ctx_load_module(session, "ietf-netconf-monitoring", NULL, server_modules, old_clb, old_data, 0, &mod)) { + WRN("Session %u: loading NETCONF monitoring schema failed, cannot use .", session->id); get_schema_support = 0; } } /* load base model disregarding whether it's in capabilities (but NETCONF capabilities are used to enable features) */ - if (ctx_check_and_load_ietf_netconf(session->ctx, session->opts.client.cpblts)) { + if (nc_ctx_fill_ietf_netconf(session, server_modules, old_clb, old_data, get_schema_support)) { goto cleanup; } - if (yanglib_support && get_schema_support) { + /* get correct version of ietf-yang-library into context */ + if (yanglib_support) { /* use get schema to get server's ietf-yang-library */ revision = strstr(session->opts.client.cpblts[yanglib_support - 1], "revision="); if (!revision) { - WRN("Loading NETCONF ietf-yang-library schema failed, missing revision in NETCONF message."); - WRN("Unable to automatically use ."); + WRN("Session %u: loading NETCONF ietf-yang-library schema failed, missing revision in NETCONF message.", session->id); + WRN("Session %u: unable to automatically use .", session->id); yanglib_support = 0; } else { revision = strndup(&revision[9], 10); - if (nc_ctx_load_module(session, "ietf-yang-library", revision, 1, old_clb, old_data, 1, &mod)) { - WRN("Loading NETCONF ietf-yang-library schema failed, unable to automatically use ."); + if (nc_ctx_load_module(session, "ietf-yang-library", revision, server_modules, old_clb, old_data, get_schema_support, &mod)) { + WRN("Session %u: loading NETCONF ietf-yang-library schema failed, unable to automatically use .", session->id); yanglib_support = 0; } free(revision); } } + /* prepare structured information about server's schemas */ if (yanglib_support) { - /* load schemas according to the ietf-yang-library data, which are more precise than capabilities list */ - r = nc_ctx_fill_yl(session, old_clb, old_data, get_schema_support); - if (r == -1) { + if (build_schema_info_yl(session, &sm)) { goto cleanup; - } else if (r == 1) { - VRB("Session %d: trying to use capabilities instead of ietf-yang-library data.", nc_session_get_id(session)); - /* try to use standard capabilities */ - goto capabilities; + } else if (!sm) { + VRB("Session %u: trying to use capabilities instead of ietf-yang-library data.", session->id); + } else { + /* prefer yang-library information, currently we have it from capabilities used for getting correct yang-library schema */ + free_schema_info(server_modules); + server_modules = sm; } - } else { -capabilities: + } - if (nc_ctx_fill_cpblts(session, old_clb, old_data, get_schema_support)) { - goto cleanup; - } + if (nc_ctx_fill(session, server_modules, old_clb, old_data, get_schema_support)) { + goto cleanup; } /* succsess */ ret = 0; if (session->flags & NC_SESSION_CLIENT_NOT_STRICT) { - WRN("Some models failed to be loaded, any data from these models (and any other unknown) will be ignored."); + WRN("Session %u: some models failed to be loaded, any data from these models (and any other unknown) will be ignored.", session->id); } cleanup: + free_schema_info(server_modules); + /* set user callback back */ ly_ctx_set_module_imp_clb(session->ctx, old_clb, old_data); + ly_ctx_unset_disable_searchdirs(session->ctx); return ret; } @@ -873,7 +1087,7 @@ nc_connect_inout(int fdin, int fdout, struct ly_ctx *ctx) /* assign context (dicionary needed for handshake) */ if (!ctx) { - ctx = ly_ctx_new(NC_SCHEMAS_DIR, 0); + ctx = ly_ctx_new(NC_SCHEMAS_DIR, LY_CTX_NOYANGLIBRARY); /* definitely should not happen, but be ready */ if (!ctx && !(ctx = ly_ctx_new(NULL, 0))) { /* that's just it */ @@ -940,16 +1154,6 @@ _non_blocking_connect(int timeout, int* sock_pending, struct addrinfo *res) goto cleanup; } } - /* check the usability of the socket */ - if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { - ERR("getsockopt failed: (%s).", strerror(errno)); - goto cleanup; - } - if (error == ECONNREFUSED) { - /* network connection failed, try another resource */ - VRB("getsockopt error: (%s).", strerror(error)); - goto cleanup; - } } ts.tv_sec = timeout; ts.tv_usec = 0; @@ -972,6 +1176,18 @@ _non_blocking_connect(int timeout, int* sock_pending, struct addrinfo *res) } return -1; } + + /* check the usability of the socket */ + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len) < 0) { + ERR("getsockopt failed: (%s).", strerror(errno)); + goto cleanup; + } + if (error == ECONNREFUSED) { + /* network connection failed, try another resource */ + VRB("getsockopt error: (%s).", strerror(error)); + errno = error; + goto cleanup; + } return sock; cleanup: diff --git a/src/session_p.h b/src/session_p.h index a6dae221..af2291cb 100644 --- a/src/session_p.h +++ b/src/session_p.h @@ -18,6 +18,7 @@ #include #include +#include #include @@ -186,6 +187,11 @@ struct nc_server_opts { void *server_cert_data; void (*server_cert_data_free)(void *data); + int (*server_cert_chain_clb)(const char *name, void *user_data, char ***cert_paths, int *cert_path_count, + char ***cert_data, int *cert_data_count); + void *server_cert_chain_data; + void (*server_cert_chain_data_free)(void *data); + int (*trusted_cert_list_clb)(const char *name, void *user_data, char ***cert_paths, int *cert_path_count, char ***cert_data, int *cert_data_count); void *trusted_cert_list_data; @@ -263,14 +269,15 @@ struct nc_server_opts { } conn; NC_CH_START_WITH start_with; uint8_t max_attempts; + uint32_t id; pthread_mutex_t lock; } *ch_clients; uint16_t ch_client_count; pthread_rwlock_t ch_client_lock; - /* ACCESS locked with sid_lock */ - uint32_t new_session_id; - pthread_spinlock_t sid_lock; + /* Atomic IDs */ + atomic_uint_fast32_t new_session_id; + atomic_uint_fast32_t new_client_id; }; /** diff --git a/src/session_server.c b/src/session_server.c index 9f6f7b7d..75b98592 100644 --- a/src/session_server.c +++ b/src/session_server.c @@ -515,7 +515,7 @@ nc_server_init(struct ly_ctx *ctx) server_opts.ctx = ctx; server_opts.new_session_id = 1; - pthread_spin_init(&server_opts.sid_lock, PTHREAD_PROCESS_PRIVATE); + server_opts.new_client_id = 1; errno=0; @@ -549,8 +549,6 @@ nc_server_destroy(void) server_opts.capabilities = NULL; server_opts.capabilities_count = 0; - pthread_spin_destroy(&server_opts.sid_lock); - #if defined(NC_ENABLED_SSH) || defined(NC_ENABLED_TLS) nc_server_del_endpt(NULL, 0); #endif @@ -703,9 +701,7 @@ nc_accept_inout(int fdin, int fdout, const char *username, struct nc_session **s (*session)->ctx = server_opts.ctx; /* assign new SID atomically */ - pthread_spin_lock(&server_opts.sid_lock); - (*session)->id = server_opts.new_session_id++; - pthread_spin_unlock(&server_opts.sid_lock); + (*session)->id = atomic_fetch_add(&server_opts.new_session_id, 1); /* NETCONF handshake */ msgtype = nc_handshake_io(*session); @@ -2015,11 +2011,7 @@ nc_accept(int timeout, struct nc_session **session) pthread_rwlock_unlock(&server_opts.endpt_lock); /* assign new SID atomically */ - /* LOCK */ - pthread_spin_lock(&server_opts.sid_lock); - (*session)->id = server_opts.new_session_id++; - /* UNLOCK */ - pthread_spin_unlock(&server_opts.sid_lock); + (*session)->id = atomic_fetch_add(&server_opts.new_session_id, 1); /* NETCONF handshake */ msgtype = nc_handshake_io(*session); @@ -2081,6 +2073,7 @@ nc_server_ch_add_client(const char *name, NC_TRANSPORT_IMPL ti) return -1; } server_opts.ch_clients[server_opts.ch_client_count - 1].name = lydict_insert(server_opts.ctx, name, 0); + server_opts.ch_clients[server_opts.ch_client_count - 1].id = atomic_fetch_add(&server_opts.new_client_id, 1); server_opts.ch_clients[server_opts.ch_client_count - 1].ti = ti; server_opts.ch_clients[server_opts.ch_client_count - 1].ch_endpts = NULL; server_opts.ch_clients[server_opts.ch_client_count - 1].ch_endpt_count = 0; @@ -2758,11 +2751,7 @@ nc_connect_ch_client_endpt(struct nc_ch_client *client, struct nc_ch_endpt *endp } /* assign new SID atomically */ - /* LOCK */ - pthread_spin_lock(&server_opts.sid_lock); - (*session)->id = server_opts.new_session_id++; - /* UNLOCK */ - pthread_spin_unlock(&server_opts.sid_lock); + (*session)->id = atomic_fetch_add(&server_opts.new_session_id, 1); /* NETCONF handshake */ msgtype = nc_handshake_io(*session); @@ -2904,17 +2893,19 @@ nc_ch_client_thread(void *arg) struct nc_ch_client_thread_arg *data = (struct nc_ch_client_thread_arg *)arg; NC_MSG_TYPE msgtype; uint8_t cur_attempts = 0; - uint16_t i; + uint16_t next_endpt_index; char *cur_endpt_name = NULL; struct nc_ch_endpt *cur_endpt; struct nc_session *session; struct nc_ch_client *client; + uint32_t client_id; /* LOCK */ client = nc_server_ch_client_with_endpt_lock(data->client_name); if (!client) { goto cleanup; } + client_id = client->id; cur_endpt = &client->ch_endpts[0]; cur_endpt_name = strdup(cur_endpt->name); @@ -2938,6 +2929,10 @@ nc_ch_client_thread(void *arg) if (!client) { goto cleanup; } + if (client->id != client_id) { + nc_server_ch_client_unlock(client); + goto cleanup; + } /* session changed status -> it was disconnected for whatever reason, * persistent connection immediately tries to reconnect, periodic waits some first */ @@ -2953,14 +2948,28 @@ nc_ch_client_thread(void *arg) if (!client) { goto cleanup; } + if (client->id != client_id) { + nc_server_ch_client_unlock(client); + goto cleanup; + } } /* set next endpoint to try */ if (client->start_with == NC_CH_FIRST_LISTED) { - cur_endpt = &client->ch_endpts[0]; - free(cur_endpt_name); - cur_endpt_name = strdup(cur_endpt->name); - } /* else we keep the current one */ + next_endpt_index = 0; + } else { + /* we keep the current one but due to unlock/lock we have to find it again */ + for (next_endpt_index = 0; next_endpt_index < client->ch_endpt_count; ++next_endpt_index) { + if (!strcmp(client->ch_endpts[next_endpt_index].name, cur_endpt_name)) { + break; + } + } + if (next_endpt_index >= client->ch_endpt_count) { + /* endpoint was removed, start with the first one */ + next_endpt_index = 0; + } + } + } else { /* UNLOCK */ nc_server_ch_client_unlock(client); @@ -2973,40 +2982,41 @@ nc_ch_client_thread(void *arg) if (!client) { goto cleanup; } + if (client->id != client_id) { + nc_server_ch_client_unlock(client); + goto cleanup; + } ++cur_attempts; /* try to find our endpoint again */ - for (i = 0; i < client->ch_endpt_count; ++i) { - if (!strcmp(client->ch_endpts[i].name, cur_endpt_name)) { + for (next_endpt_index = 0; next_endpt_index < client->ch_endpt_count; ++next_endpt_index) { + if (!strcmp(client->ch_endpts[next_endpt_index].name, cur_endpt_name)) { break; } } - if (i < client->ch_endpt_count) { + if (next_endpt_index >= client->ch_endpt_count) { /* endpoint was removed, start with the first one */ - cur_endpt = &client->ch_endpts[0]; - free(cur_endpt_name); - cur_endpt_name = strdup(cur_endpt->name); - + next_endpt_index = 0; cur_attempts = 0; } else if (cur_attempts == client->max_attempts) { /* we have tried to connect to this endpoint enough times */ - if (i < client->ch_endpt_count - 1) { + if (next_endpt_index < client->ch_endpt_count - 1) { /* just go to the next endpoint */ - cur_endpt = &client->ch_endpts[i + 1]; - free(cur_endpt_name); - cur_endpt_name = strdup(cur_endpt->name); + ++next_endpt_index; } else { /* cur_endpoint is the last, start with the first one */ - cur_endpt = &client->ch_endpts[0]; - free(cur_endpt_name); - cur_endpt_name = strdup(cur_endpt->name); + next_endpt_index = 0; } cur_attempts = 0; } /* else we keep the current one */ } + + cur_endpt = &client->ch_endpts[next_endpt_index]; + free(cur_endpt_name); + cur_endpt_name = strdup(cur_endpt->name); } cleanup: diff --git a/src/session_server.h b/src/session_server.h index 2d84865f..fd4c6eff 100644 --- a/src/session_server.h +++ b/src/session_server.h @@ -663,6 +663,23 @@ void nc_server_tls_set_server_cert_clb(int (*cert_clb)(const char *name, void *u char **privkey_path, char **privkey_data, int *privkey_data_rsa), void *user_data, void (*free_user_data)(void *user_data)); +/** + * @brief Set the callback for retrieving server certificate chain + * + * @param[in] cert_chain_clb Callback that should return all the certificates of the chain. Zero return indicates success, + * non-zero an error. On success, \p cert_paths and \p cert_data are expected to be set or left + * NULL. Both will be (deeply) freed. + * - \p cert_paths expect an array of PEM files, + * - \p cert_path_count number of \p cert_paths array members, + * - \p cert_data expect an array of base-64 encoded ASN.1 DER cert data, + * - \p cert_data_count number of \p cert_data array members. + * @param[in] user_data Optional arbitrary user data that will be passed to \p cert_clb. + * @param[in] free_user_data Optional callback that will be called during cleanup to free any \p user_data. + */ +void nc_server_tls_set_server_cert_chain_clb(int (*cert_chain_clb)(const char *name, void *user_data, char ***cert_paths, + int *cert_path_count, char ***cert_data, int *cert_data_count), + void *user_data, void (*free_user_data)(void *user_data)); + /** * @brief Add a trusted certificate list. Can be both a CA or a client one. Can be * safely used together with nc_server_tls_endpt_set_trusted_ca_paths(). diff --git a/src/session_server_ssh.c b/src/session_server_ssh.c index ea9bab9b..918435f3 100644 --- a/src/session_server_ssh.c +++ b/src/session_server_ssh.c @@ -1555,9 +1555,7 @@ nc_session_accept_ssh_channel(struct nc_session *orig_session, struct nc_session } /* assign new SID atomically */ - pthread_spin_lock(&server_opts.sid_lock); - new_session->id = server_opts.new_session_id++; - pthread_spin_unlock(&server_opts.sid_lock); + new_session->id = atomic_fetch_add(&server_opts.new_session_id, 1); /* NETCONF handshake */ msgtype = nc_handshake_io(new_session); @@ -1628,9 +1626,7 @@ nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_session **session) } /* assign new SID atomically */ - pthread_spin_lock(&server_opts.sid_lock); - new_session->id = server_opts.new_session_id++; - pthread_spin_unlock(&server_opts.sid_lock); + new_session->id = atomic_fetch_add(&server_opts.new_session_id, 1); /* NETCONF handshake */ msgtype = nc_handshake_io(new_session); diff --git a/src/session_server_tls.c b/src/session_server_tls.c index 70ac35da..bac03579 100644 --- a/src/session_server_tls.c +++ b/src/session_server_tls.c @@ -513,7 +513,7 @@ nc_tlsclb_verify(int preverify_ok, X509_STORE_CTX *x509_ctx) /* get the last certificate, that is the peer (client) certificate */ if (!session->opts.server.client_cert) { cert_stack = X509_STORE_CTX_get1_chain(x509_ctx); - session->opts.server.client_cert = sk_X509_value(cert_stack, sk_X509_num(cert_stack) - 1); + session->opts.server.client_cert = sk_X509_value(cert_stack, 0); X509_up_ref(session->opts.server.client_cert); sk_X509_pop_free(cert_stack, X509_free); } @@ -986,6 +986,21 @@ nc_server_tls_set_server_cert_clb(int (*cert_clb)(const char *name, void *user_d server_opts.server_cert_data_free = free_user_data; } +API void +nc_server_tls_set_server_cert_chain_clb(int (*cert_chain_clb)(const char *name, void *user_data, char ***cert_paths, + int *cert_path_count, char ***cert_data, int *cert_data_count), + void *user_data, void (*free_user_data)(void *user_data)) +{ + if (!cert_chain_clb) { + ERRARG("cert_chain_clb"); + return; + } + + server_opts.server_cert_chain_clb = cert_chain_clb; + server_opts.server_cert_chain_data = user_data; + server_opts.server_cert_chain_data_free = free_user_data; +} + static int nc_server_tls_add_trusted_cert_list(const char *name, struct nc_server_tls_opts *opts) { @@ -1391,7 +1406,7 @@ nc_server_tls_add_ctn(uint32_t id, const char *fingerprint, NC_TLS_CTN_MAPTYPE m new->next = opts->ctn; opts->ctn = new; } else { - for (ctn = opts->ctn; ctn->next && ctn->next->id < id; ctn = ctn->next); + for (ctn = opts->ctn; ctn->next && ctn->next->id <= id; ctn = ctn->next); if (ctn->id == id) { /* it exists already */ new = ctn; @@ -1705,6 +1720,78 @@ nc_tls_make_verify_key(void) pthread_key_create(&verify_key, NULL); } +static X509* +tls_load_cert(const char *cert_path, const char *cert_data) +{ + X509 *cert; + + if (cert_path) { + cert = pem_to_cert(cert_path); + } else { + cert = base64der_to_cert(cert_data); + } + + if (!cert) { + if (cert_path) { + ERR("Loading a trusted certificate (path \"%s\") failed (%s).", cert_path, + ERR_reason_error_string(ERR_get_error())); + } else { + ERR("Loading a trusted certificate (data \"%s\") failed (%s).", cert_data, + ERR_reason_error_string(ERR_get_error())); + } + } + return cert; +} + +static int +nc_tls_ctx_set_server_cert_chain(SSL_CTX *tls_ctx, const char *cert_name) +{ + char **cert_paths = NULL, **cert_data = NULL; + int cert_path_count = 0, cert_data_count = 0, ret = 0, i = 0; + X509 *cert = NULL; + + if (!server_opts.server_cert_chain_clb) { + /* This is optional, so return OK */ + return 0; + } + + if (server_opts.server_cert_chain_clb(cert_name, server_opts.server_cert_chain_data, &cert_paths, + &cert_path_count, &cert_data, &cert_data_count)) { + ERR("Server certificate chain callback failed."); + return -1; + } + + for (i = 0; i < cert_path_count; ++i) { + cert = tls_load_cert(cert_paths[i], NULL); + if (!cert || SSL_CTX_add_extra_chain_cert(tls_ctx, cert) != 1) { + ERR("Loading the server certificate chain failed (%s).", ERR_reason_error_string(ERR_get_error())); + ret = -1; + goto cleanup; + } + } + + for (i = 0; i < cert_data_count; ++i) { + cert = tls_load_cert(NULL, cert_data[i]); + if (!cert || SSL_CTX_add_extra_chain_cert(tls_ctx, cert) != 1) { + ERR("Loading the server certificate chain failed (%s).", ERR_reason_error_string(ERR_get_error())); + ret = -1; + goto cleanup; + } + } +cleanup: + for (i = 0; i < cert_path_count; ++i) { + free(cert_paths[i]); + } + free(cert_paths); + for (i = 0; i < cert_data_count; ++i) { + free(cert_data[i]); + } + free(cert_data); + /* cert is owned by the SSL_CTX */ + + return ret; +} + static int nc_tls_ctx_set_server_cert_key(SSL_CTX *tls_ctx, const char *cert_name) { @@ -1759,6 +1846,8 @@ nc_tls_ctx_set_server_cert_key(SSL_CTX *tls_ctx, const char *cert_name) } } + ret = nc_tls_ctx_set_server_cert_chain(tls_ctx, cert_name); + cleanup: X509_free(cert); EVP_PKEY_free(pkey); @@ -1772,22 +1861,8 @@ nc_tls_ctx_set_server_cert_key(SSL_CTX *tls_ctx, const char *cert_name) static void tls_store_add_trusted_cert(X509_STORE *cert_store, const char *cert_path, const char *cert_data) { - X509 *cert; - - if (cert_path) { - cert = pem_to_cert(cert_path); - } else { - cert = base64der_to_cert(cert_data); - } - + X509 *cert = tls_load_cert(cert_path, cert_data); if (!cert) { - if (cert_path) { - ERR("Loading a trusted certificate (path \"%s\") failed (%s).", cert_path, - ERR_reason_error_string(ERR_get_error())); - } else { - ERR("Loading a trusted certificate (data \"%s\") failed (%s).", cert_data, - ERR_reason_error_string(ERR_get_error())); - } return; }