struct nc_server_reply * op_lock(struct lyd_node *rpc, struct nc_session *ncs) { struct np2_sessions *sessions; sr_datastore_t ds = 0; struct nc_session **dsl = NULL; time_t *dst; struct ly_set *nodeset; struct nc_server_error *e; struct nc_server_reply *ereply = NULL; const char *dsname; /* get sysrepo connections for this session */ sessions = (struct np2_sessions *)nc_session_get_data(ncs); if (np2srv_sr_check_exec_permission(sessions->srs, "/ietf-netconf:lock", &ereply)) { goto finish; } /* get know which datastore is being affected */ nodeset = lyd_find_path(rpc, "/ietf-netconf:lock/target/*"); dsname = nodeset->set.d[0]->schema->name; ly_set_free(nodeset); if (!strcmp(dsname, "running")) { /* TODO additional requirements in case of supporting confirmed-commit */ ds = SR_DS_RUNNING; dsl = &dslock.running; dst = &dslock.running_time; } else if (!strcmp(dsname, "startup")) { ds = SR_DS_STARTUP; dsl = &dslock.startup; dst = &dslock.startup_time; } else if (!strcmp(dsname, "candidate")) { ds = SR_DS_CANDIDATE; dsl = &dslock.candidate; dst = &dslock.candidate_time; } else { EINT; e = nc_err(NC_ERR_OP_FAILED, NC_ERR_TYPE_PROT); nc_err_set_msg(e, np2log_lasterr(), "en"); ereply = nc_server_reply_err(e); goto finish; } if (ds != sessions->ds) { /* update sysrepo session */ if (np2srv_sr_session_switch_ds(sessions->srs, ds, &ereply)) { goto finish; } sessions->ds = ds; } pthread_rwlock_rdlock(&dslock_rwl); if (*dsl) { lock_held: /* lock already held */ pthread_rwlock_unlock(&dslock_rwl); ERR("Locking datastore %s by session %d failed (datastore is already locked by session %d).", dsname, nc_session_get_id(ncs), nc_session_get_id(*dsl)); e = nc_err(NC_ERR_LOCK_DENIED, nc_session_get_id(*dsl)); nc_err_set_msg(e, np2log_lasterr(), "en"); ereply = nc_server_reply_err(e); goto finish; } pthread_rwlock_unlock(&dslock_rwl); pthread_rwlock_wrlock(&dslock_rwl); /* check again dsl, it could change between unlock and wrlock */ if (*dsl) { goto lock_held; } if (np2srv_sr_lock_datastore(sessions->srs, &ereply)) { /* lock is held outside Netopeer */ pthread_rwlock_unlock(&dslock_rwl); /* add lock denied error */ ERR("Locking datastore %s by session %d failed.", dsname, nc_session_get_id(ncs)); e = nc_err(NC_ERR_LOCK_DENIED, 0); nc_err_set_msg(e, np2log_lasterr(), "en"); nc_server_reply_add_err(ereply, e); goto finish; } /* update local information about locks */ *dsl = ncs; *dst = time(NULL); pthread_rwlock_unlock(&dslock_rwl); /* build positive RPC Reply */ ereply = nc_server_reply_ok(); finish: return ereply; }
struct nc_server_reply * op_validate(struct lyd_node *rpc, struct nc_session *ncs) { struct np2_sessions *sessions; struct ly_set *nodeset; struct nc_server_error *e = NULL; int rc; struct lyd_node *config = NULL; struct lyd_node_anyxml *axml; const char *dsname; sr_datastore_t ds = SR_DS_CANDIDATE; /* get sysrepo connections for this session */ sessions = (struct np2_sessions *)nc_session_get_data(ncs); /* get know which datastore is being affected */ nodeset = lyd_get_node(rpc, "/ietf-netconf:validate/source/*"); dsname = nodeset->set.d[0]->schema->name; axml = (struct lyd_node_anyxml *)nodeset->set.d[0]; ly_set_free(nodeset); if (!strcmp(dsname, "running")) { ds = SR_DS_RUNNING; } else if (!strcmp(dsname, "startup")) { ds = SR_DS_STARTUP; } else if (!strcmp(dsname, "candidate")) { ds = SR_DS_CANDIDATE; } else if (!strcmp(dsname, "config")) { /* get data tree to validate */ config = lyd_parse_xml(rpc->schema->module->ctx, &axml->value.xml, LYD_OPT_CONFIG | LYD_OPT_DESTRUCT); if (ly_errno != LY_SUCCESS) { ly_set_free(nodeset); goto error; } rc = lyd_validate(&config, LYD_OPT_CONFIG, np2srv.ly_ctx); /* cleanup */ lyd_free_withsiblings(config); goto done; } /* TODO support URL */ if (ds != sessions->ds) { /* update sysrepo session */ sr_session_switch_ds(sessions->srs, ds); sessions->ds = ds; } if (ds != SR_DS_CANDIDATE) { /* refresh datastore content */ if (sr_session_refresh(sessions->srs) != SR_ERR_OK) { goto error; } } /* validate sysrepo's datastore */ rc = sr_validate(sessions->srs); if (rc != SR_ERR_OK) { goto error; } done: return nc_server_reply_ok(); error: /* handle error */ if (!e) { e = nc_err(NC_ERR_OP_FAILED, NC_ERR_TYPE_APP); nc_err_set_msg(e, np2log_lasterr(), "en"); } return nc_server_reply_err(e); }
struct nc_server_reply * op_unlock(struct lyd_node *rpc, struct nc_session *ncs) { struct np2_sessions *sessions; sr_datastore_t ds = 0; struct nc_session **dsl = NULL; time_t *dst; struct ly_set *nodeset; const char *dsname; struct nc_server_error *e; struct nc_server_reply *ereply = NULL; /* get sysrepo connections for this session */ sessions = (struct np2_sessions *)nc_session_get_data(ncs); if (np2srv_sr_check_exec_permission(sessions->srs, "/ietf-netconf:unlock", &ereply)) { goto finish; } /* get know which datastore is being affected */ nodeset = lyd_find_path(rpc, "/ietf-netconf:unlock/target/*"); dsname = nodeset->set.d[0]->schema->name; ly_set_free(nodeset); if (!strcmp(dsname, "running")) { ds = SR_DS_RUNNING; dsl = &dslock.running; dst = &dslock.running_time; } else if (!strcmp(dsname, "startup")) { ds = SR_DS_STARTUP; dsl = &dslock.startup; dst = &dslock.startup_time; } else if (!strcmp(dsname, "candidate")) { ds = SR_DS_CANDIDATE; dsl = &dslock.candidate; dst = &dslock.candidate_time; } else { EINT; e = nc_err(NC_ERR_OP_FAILED, NC_ERR_TYPE_PROT); nc_err_set_msg(e, np2log_lasterr(), "en"); ereply = nc_server_reply_err(e); goto finish; } if (ds != sessions->ds) { /* update sysrepo session */ if (np2srv_sr_session_switch_ds(sessions->srs, ds, &ereply)) { goto finish; } sessions->ds = ds; } pthread_rwlock_rdlock(&dslock_rwl); if (!(*dsl)) { /* lock is not held */ pthread_rwlock_unlock(&dslock_rwl); ERR("Unlocking datastore %s by session %d failed (lock is not active).", dsname, nc_session_get_id(ncs)); e = nc_err(NC_ERR_OP_FAILED, NC_ERR_TYPE_PROT); nc_err_set_msg(e, np2log_lasterr(), "en"); ereply = nc_server_reply_err(e); goto finish; } else { /* lock is held, but by who? */ if ((*dsl) != ncs) { /* by someone else */ pthread_rwlock_unlock(&dslock_rwl); ERR("Unlocking datastore %s by session %d failed (lock is held by session %d).", dsname, nc_session_get_id(ncs), nc_session_get_id(*dsl)); e = nc_err(NC_ERR_LOCK_DENIED, nc_session_get_id(*dsl)); nc_err_set_msg(e, np2log_lasterr(), "en"); ereply = nc_server_reply_err(e); goto finish; } } pthread_rwlock_unlock(&dslock_rwl); pthread_rwlock_wrlock(&dslock_rwl); if (np2srv_sr_unlock_datastore(sessions->srs, &ereply)) { /* lock is held outside Netopeer */ pthread_rwlock_unlock(&dslock_rwl); /* add lock denied error */ ERR("Unlocking datastore %s by session %d failed.", dsname, nc_session_get_id(ncs)); e = nc_err(NC_ERR_LOCK_DENIED, 0); nc_err_set_msg(e, np2log_lasterr(), "en"); nc_server_reply_add_err(ereply, e); goto finish; } /* according to RFC 6241 8.3.5.2, discard changes */ np2srv_sr_discard_changes(sessions->srs, NULL); /* update local information about locks */ *dsl = NULL; *dst = 0; pthread_rwlock_unlock(&dslock_rwl); /* build positive RPC Reply */ ereply = nc_server_reply_ok(); finish: return ereply; }
/* return NC_MSG_ERROR can change session status, acquires IO lock as needed */ NC_MSG_TYPE nc_read_msg_io(struct nc_session *session, int io_timeout, struct lyxml_elem **data, int passing_io_lock) { int ret, io_locked = passing_io_lock; char *msg = NULL, *chunk; uint64_t chunk_len, len = 0; /* use timeout in milliseconds instead seconds */ uint32_t inact_timeout = NC_READ_INACT_TIMEOUT * 1000; struct timespec ts_act_timeout; struct nc_server_reply *reply; assert(session && data); *data = NULL; if ((session->status != NC_STATUS_RUNNING) && (session->status != NC_STATUS_STARTING)) { ERR("Session %u: invalid session to read from.", session->id); ret = NC_MSG_ERROR; goto cleanup; } nc_gettimespec_mono(&ts_act_timeout); nc_addtimespec(&ts_act_timeout, NC_READ_ACT_TIMEOUT * 1000); if (!io_locked) { /* SESSION IO LOCK */ ret = nc_session_io_lock(session, io_timeout, __func__); if (ret < 0) { ret = NC_MSG_ERROR; goto cleanup; } else if (!ret) { ret = NC_MSG_WOULDBLOCK; goto cleanup; } io_locked = 1; } /* read the message */ switch (session->version) { case NC_VERSION_10: ret = nc_read_until(session, NC_VERSION_10_ENDTAG, 0, inact_timeout, &ts_act_timeout, &msg); if (ret == -1) { ret = NC_MSG_ERROR; goto cleanup; } /* cut off the end tag */ msg[ret - NC_VERSION_10_ENDTAG_LEN] = '\0'; break; case NC_VERSION_11: while (1) { ret = nc_read_until(session, "\n#", 0, inact_timeout, &ts_act_timeout, NULL); if (ret == -1) { ret = NC_MSG_ERROR; goto cleanup; } ret = nc_read_until(session, "\n", 0, inact_timeout, &ts_act_timeout, &chunk); if (ret == -1) { ret = NC_MSG_ERROR; goto cleanup; } if (!strcmp(chunk, "#\n")) { /* end of chunked framing message */ free(chunk); if (!msg) { ERR("Session %u: invalid frame chunk delimiters.", session->id); goto malformed_msg; } break; } /* convert string to the size of the following chunk */ chunk_len = strtoul(chunk, (char **)NULL, 10); free(chunk); if (!chunk_len) { ERR("Session %u: invalid frame chunk size detected, fatal error.", session->id); goto malformed_msg; } /* now we have size of next chunk, so read the chunk */ ret = nc_read_chunk(session, chunk_len, inact_timeout, &ts_act_timeout, &chunk); if (ret == -1) { ret = NC_MSG_ERROR; goto cleanup; } /* realloc message buffer, remember to count terminating null byte */ msg = nc_realloc(msg, len + chunk_len + 1); if (!msg) { ERRMEM; ret = NC_MSG_ERROR; goto cleanup; } memcpy(msg + len, chunk, chunk_len); len += chunk_len; msg[len] = '\0'; free(chunk); } break; } /* SESSION IO UNLOCK */ assert(io_locked); nc_session_io_unlock(session, __func__); io_locked = 0; DBG("Session %u: received message:\n%s\n", session->id, msg); /* build XML tree */ *data = lyxml_parse_mem(session->ctx, msg, 0); if (!*data) { goto malformed_msg; } else if (!(*data)->ns) { ERR("Session %u: invalid message root element (invalid namespace).", session->id); goto malformed_msg; } free(msg); msg = NULL; /* get and return message type */ if (!strcmp((*data)->ns->value, NC_NS_BASE)) { if (!strcmp((*data)->name, "rpc")) { return NC_MSG_RPC; } else if (!strcmp((*data)->name, "rpc-reply")) { return NC_MSG_REPLY; } else if (!strcmp((*data)->name, "hello")) { return NC_MSG_HELLO; } else { ERR("Session %u: invalid message root element (invalid name \"%s\").", session->id, (*data)->name); goto malformed_msg; } } else if (!strcmp((*data)->ns->value, NC_NS_NOTIF)) { if (!strcmp((*data)->name, "notification")) { return NC_MSG_NOTIF; } else { ERR("Session %u: invalid message root element (invalid name \"%s\").", session->id, (*data)->name); goto malformed_msg; } } else { ERR("Session %u: invalid message root element (invalid namespace \"%s\").", session->id, (*data)->ns->value); goto malformed_msg; } malformed_msg: ERR("Session %u: malformed message received.", session->id); if ((session->side == NC_SERVER) && (session->version == NC_VERSION_11)) { /* 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) { session->status = NC_STATUS_INVALID; session->term_reason = NC_SESSION_TERM_OTHER; } } nc_server_reply_free(reply); } ret = NC_MSG_ERROR; cleanup: if (io_locked) { nc_session_io_unlock(session, __func__); } free(msg); free(*data); *data = NULL; return ret; }