266 lines
8.4 KiB
C
266 lines
8.4 KiB
C
/**
|
|
* @file client.c
|
|
* @author Roman Janota <xjanot04@fit.vutbr.cz>
|
|
* @brief libnetconf2 client example
|
|
*
|
|
* @copyright
|
|
* Copyright (c) 2022 CESNET, z.s.p.o.
|
|
*
|
|
* This source code is licensed under BSD 3-Clause License (the "License").
|
|
* You may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* https://opensource.org/licenses/BSD-3-Clause
|
|
*/
|
|
|
|
#include "example.h"
|
|
|
|
#include <getopt.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <libyang/libyang.h>
|
|
|
|
#include "log.h"
|
|
#include "messages_client.h"
|
|
#include "netconf.h"
|
|
#include "session_client.h"
|
|
#include "session_client_ch.h"
|
|
|
|
static void
|
|
help_print()
|
|
{
|
|
printf("Example usage:\n"
|
|
" client get\n"
|
|
"\n"
|
|
" Available options:\n"
|
|
" -h, --help\t \tPrint usage help.\n"
|
|
" -p, --port\t\t<port>\tSpecify the port to connect to.\n"
|
|
" -u, --unix-path\t<path>\tConnect to a UNIX socket located at <path>.\n"
|
|
" -P, --ssh-pubkey\t<path>\tSet the path to an SSH Public key.\n"
|
|
" -i, --ssh-privkey\t<path>\tSet the path to an SSH Private key.\n\n"
|
|
" Available RPCs:\n"
|
|
" get [xpath-filter]\t\t\t\t\t send a <get> RPC with optional XPath filter\n"
|
|
" get-config [datastore] [xpath-filter]\t\t send a <get-config> RPC with optional XPath filter and datastore, the default datastore is \"running\" \n\n");
|
|
}
|
|
|
|
static enum NC_DATASTORE_TYPE
|
|
string2datastore(const char *str)
|
|
{
|
|
if (!str) {
|
|
return NC_DATASTORE_RUNNING;
|
|
}
|
|
|
|
if (!strcmp(str, "candidate")) {
|
|
return NC_DATASTORE_CANDIDATE;
|
|
} else if (!strcmp(str, "running")) {
|
|
return NC_DATASTORE_RUNNING;
|
|
} else if (!strcmp(str, "startup")) {
|
|
return NC_DATASTORE_STARTUP;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int
|
|
send_rpc(struct nc_session *session, NC_RPC_TYPE rpc_type, const char *param1, const char *param2)
|
|
{
|
|
enum NC_DATASTORE_TYPE datastore;
|
|
int r = 0, rc = 0;
|
|
uint64_t msg_id = 0;
|
|
struct lyd_node *envp = NULL, *op = NULL;
|
|
struct nc_rpc *rpc = NULL;
|
|
|
|
/* decide which type of RPC to send */
|
|
switch (rpc_type) {
|
|
case NC_RPC_GET:
|
|
/* create get RPC with an optional filter */
|
|
rpc = nc_rpc_get(param1, NC_WD_UNKNOWN, NC_PARAMTYPE_CONST);
|
|
break;
|
|
|
|
case NC_RPC_GETCONFIG:
|
|
/* create get-config RPC with a source datastore and an optional filter */
|
|
datastore = string2datastore(param1);
|
|
if (!datastore) {
|
|
ERR_MSG_CLEANUP("Invalid name of a datastore. Use candidate, running, startup or neither.\n");
|
|
}
|
|
rpc = nc_rpc_getconfig(datastore, param2, NC_WD_UNKNOWN, NC_PARAMTYPE_CONST);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
if (!rpc) {
|
|
ERR_MSG_CLEANUP("Error while creating a RPC\n");
|
|
}
|
|
|
|
/* send the RPC on the session and remember NETCONF message ID */
|
|
r = nc_send_rpc(session, rpc, 100, &msg_id);
|
|
if (r != NC_MSG_RPC) {
|
|
ERR_MSG_CLEANUP("Couldn't send a RPC\n");
|
|
}
|
|
|
|
/* receive the server's reply with the expected message ID
|
|
* as separate rpc-reply NETCONF envelopes and the parsed YANG output itself, if any */
|
|
r = nc_recv_reply(session, rpc, msg_id, 100, &envp, &op);
|
|
if (r != NC_MSG_REPLY) {
|
|
ERR_MSG_CLEANUP("Couldn't receive a reply from the server\n");
|
|
}
|
|
|
|
/* print the whole reply */
|
|
if (!op) {
|
|
r = lyd_print_file(stdout, envp, LYD_XML, 0);
|
|
} else {
|
|
r = lyd_print_file(stdout, op, LYD_XML, 0);
|
|
if (r) {
|
|
ERR_MSG_CLEANUP("Couldn't print the RPC to stdout\n");
|
|
}
|
|
r = lyd_print_file(stdout, envp, LYD_XML, 0);
|
|
}
|
|
if (r) {
|
|
ERR_MSG_CLEANUP("Couldn't print the RPC to stdout\n");
|
|
}
|
|
|
|
cleanup:
|
|
lyd_free_all(envp);
|
|
lyd_free_all(op);
|
|
nc_rpc_free(rpc);
|
|
return rc;
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
int rc = 0, opt, port = 0;
|
|
struct nc_session *session = NULL;
|
|
const char *unix_socket_path = NULL, *rpc_parameter_1 = NULL, *rpc_parameter_2 = NULL;
|
|
const char *ssh_pubkey_path = NULL, *ssh_privkey_path = NULL;
|
|
|
|
struct option options[] = {
|
|
{"help", no_argument, NULL, 'h'},
|
|
{"port", required_argument, NULL, 'p'},
|
|
{"unix-path", required_argument, NULL, 'u'},
|
|
{"ssh-pubkey", required_argument, NULL, 'P'},
|
|
{"ssh-privkey", required_argument, NULL, 'i'},
|
|
{"debug", no_argument, NULL, 'd'},
|
|
{NULL, 0, NULL, 0}
|
|
};
|
|
|
|
if (argc == 1) {
|
|
help_print();
|
|
goto cleanup;
|
|
}
|
|
|
|
/* set the path to search for schemas */
|
|
nc_client_set_schema_searchpath(MODULES_DIR);
|
|
|
|
opterr = 0;
|
|
|
|
while ((opt = getopt_long(argc, argv, "hp:u:P:i:d", options, NULL)) != -1) {
|
|
switch (opt) {
|
|
case 'h':
|
|
help_print();
|
|
goto cleanup;
|
|
|
|
case 'p':
|
|
port = strtoul(optarg, NULL, 10);
|
|
break;
|
|
|
|
case 'u':
|
|
unix_socket_path = optarg;
|
|
break;
|
|
|
|
case 'P':
|
|
ssh_pubkey_path = optarg;
|
|
break;
|
|
|
|
case 'i':
|
|
ssh_privkey_path = optarg;
|
|
break;
|
|
|
|
case 'd':
|
|
nc_verbosity(NC_VERB_DEBUG);
|
|
nc_libssh_thread_verbosity(2);
|
|
break;
|
|
|
|
default:
|
|
ERR_MSG_CLEANUP("Invalid option or missing argument\n");
|
|
}
|
|
}
|
|
|
|
if (optind == argc) {
|
|
ERR_MSG_CLEANUP("Expected the name of RPC after options\n");
|
|
}
|
|
|
|
/* check invalid args combinations */
|
|
if (unix_socket_path && port) {
|
|
ERR_MSG_CLEANUP("Both UNIX socket path and port specified. Please choose either SSH or UNIX.\n");
|
|
} else if (unix_socket_path && (ssh_pubkey_path || ssh_privkey_path)) {
|
|
ERR_MSG_CLEANUP("Both UNIX socket path and a path to key(s) specified. Please choose either SSH or UNIX.\n");
|
|
} else if ((port == 10001) && (!ssh_pubkey_path || !ssh_privkey_path)) {
|
|
ERR_MSG_CLEANUP("You need to specify both paths to private and public keys, if you want to connect to a publickey endpoint.\n");
|
|
} else if ((port == 10000) && (ssh_pubkey_path || ssh_privkey_path)) {
|
|
ERR_MSG_CLEANUP("Public or private key specified, when connecting to the password endpoint.\n");
|
|
} else if (!unix_socket_path && !port) {
|
|
ERR_MSG_CLEANUP("Neither UNIX socket or SSH specified.\n");
|
|
}
|
|
|
|
/* connect to the server using the specified transport protocol */
|
|
if (unix_socket_path) {
|
|
/* it's UNIX socket */
|
|
session = nc_connect_unix(unix_socket_path, NULL);
|
|
} else {
|
|
/* it must be SSH, so set the client SSH username to always be used when connecting to the server */
|
|
if (nc_client_ssh_set_username(SSH_USERNAME)) {
|
|
ERR_MSG_CLEANUP("Couldn't set the SSH username\n");
|
|
}
|
|
|
|
if (ssh_pubkey_path && ssh_privkey_path) {
|
|
/* set the client's SSH keypair to be used for authentication if necessary */
|
|
if (nc_client_ssh_add_keypair(ssh_pubkey_path, ssh_privkey_path)) {
|
|
ERR_MSG_CLEANUP("Couldn't set client's SSH keypair.\n");
|
|
}
|
|
}
|
|
|
|
/* try to connect via SSH */
|
|
session = nc_connect_ssh(SSH_ADDRESS, port, NULL);
|
|
}
|
|
if (!session) {
|
|
ERR_MSG_CLEANUP("Couldn't connect to the server\n");
|
|
}
|
|
|
|
/* sending a get RPC */
|
|
if (!strcmp(argv[optind], "get")) {
|
|
if (optind + 1 < argc) {
|
|
/* use the specified XPath filter */
|
|
rpc_parameter_1 = argv[optind + 1];
|
|
}
|
|
if (send_rpc(session, NC_RPC_GET, rpc_parameter_1, rpc_parameter_2)) {
|
|
rc = 1;
|
|
goto cleanup;
|
|
}
|
|
/* sending a get-config RPC */
|
|
} else if (!strcmp(argv[optind], "get-config")) {
|
|
/* use the specified datastore and optional XPath filter */
|
|
if (optind + 2 < argc) {
|
|
rpc_parameter_1 = argv[optind + 1];
|
|
rpc_parameter_2 = argv[optind + 2];
|
|
} else if (optind + 1 < argc) {
|
|
rpc_parameter_1 = argv[optind + 1];
|
|
}
|
|
if (send_rpc(session, NC_RPC_GETCONFIG, rpc_parameter_1, rpc_parameter_2)) {
|
|
rc = 1;
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
ERR_MSG_CLEANUP("Invalid name of a RPC\n");
|
|
}
|
|
|
|
cleanup:
|
|
nc_session_free(session, NULL);
|
|
nc_client_destroy();
|
|
return rc;
|
|
}
|