blob: 8295fb77c1270c11f2de462a56514f9ad6f8ce2c [file] [log] [blame]
Roman Janota907cf932022-06-03 12:33:34 +02001/**
2 * @file client.c
3 * @author Roman Janota <xjanot04@fit.vutbr.cz>
4 * @brief libnetconf2 client example
5 *
6 * @copyright
7 * Copyright (c) 2022 CESNET, z.s.p.o.
8 *
9 * This source code is licensed under BSD 3-Clause License (the "License").
10 * You may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
12 *
13 * https://opensource.org/licenses/BSD-3-Clause
14 */
15
16#include "example.h"
17
18#include <getopt.h>
19#include <stdint.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#include <unistd.h>
24
25#include <libyang/libyang.h>
26
27#include "log.h"
28#include "messages_client.h"
29#include "netconf.h"
30#include "session_client.h"
31#include "session_client_ch.h"
32
33static void
34help_print()
35{
36 printf("Example usage:\n"
roman3adc7c02023-10-25 11:10:58 +020037 " client get\n"
Roman Janota907cf932022-06-03 12:33:34 +020038 "\n"
39 " Available options:\n"
40 " -h, --help\t \tPrint usage help.\n"
roman3adc7c02023-10-25 11:10:58 +020041 " -p, --port\t\t<port>\tSpecify the port to connect to.\n"
42 " -u, --unix-path\t<path>\tConnect to a UNIX socket located at <path>.\n"
43 " -P, --ssh-pubkey\t<path>\tSet the path to an SSH Public key.\n"
44 " -i, --ssh-privkey\t<path>\tSet the path to an SSH Private key.\n\n"
Roman Janota907cf932022-06-03 12:33:34 +020045 " Available RPCs:\n"
46 " get [xpath-filter]\t\t\t\t\t send a <get> RPC with optional XPath filter\n"
47 " 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");
48}
49
50static enum NC_DATASTORE_TYPE
51string2datastore(const char *str)
52{
53 if (!str) {
54 return NC_DATASTORE_RUNNING;
55 }
56
57 if (!strcmp(str, "candidate")) {
58 return NC_DATASTORE_CANDIDATE;
59 } else if (!strcmp(str, "running")) {
60 return NC_DATASTORE_RUNNING;
61 } else if (!strcmp(str, "startup")) {
62 return NC_DATASTORE_STARTUP;
63 } else {
64 return 0;
65 }
66}
67
68static int
69send_rpc(struct nc_session *session, NC_RPC_TYPE rpc_type, const char *param1, const char *param2)
70{
71 enum NC_DATASTORE_TYPE datastore;
72 int r = 0, rc = 0;
73 uint64_t msg_id = 0;
74 struct lyd_node *envp = NULL, *op = NULL;
75 struct nc_rpc *rpc = NULL;
76
77 /* decide which type of RPC to send */
78 switch (rpc_type) {
79 case NC_RPC_GET:
80 /* create get RPC with an optional filter */
81 rpc = nc_rpc_get(param1, NC_WD_UNKNOWN, NC_PARAMTYPE_CONST);
82 break;
83
84 case NC_RPC_GETCONFIG:
85 /* create get-config RPC with a source datastore and an optional filter */
86 datastore = string2datastore(param1);
87 if (!datastore) {
88 ERR_MSG_CLEANUP("Invalid name of a datastore. Use candidate, running, startup or neither.\n");
89 }
90 rpc = nc_rpc_getconfig(datastore, param2, NC_WD_UNKNOWN, NC_PARAMTYPE_CONST);
91 break;
92
93 default:
94 break;
95 }
96 if (!rpc) {
97 ERR_MSG_CLEANUP("Error while creating a RPC\n");
98 }
99
100 /* send the RPC on the session and remember NETCONF message ID */
101 r = nc_send_rpc(session, rpc, 100, &msg_id);
102 if (r != NC_MSG_RPC) {
103 ERR_MSG_CLEANUP("Couldn't send a RPC\n");
104 }
105
106 /* receive the server's reply with the expected message ID
107 * as separate rpc-reply NETCONF envelopes and the parsed YANG output itself, if any */
108 r = nc_recv_reply(session, rpc, msg_id, 100, &envp, &op);
109 if (r != NC_MSG_REPLY) {
110 ERR_MSG_CLEANUP("Couldn't receive a reply from the server\n");
111 }
112
113 /* print the whole reply */
114 if (!op) {
115 r = lyd_print_file(stdout, envp, LYD_XML, 0);
116 } else {
117 r = lyd_print_file(stdout, op, LYD_XML, 0);
118 if (r) {
119 ERR_MSG_CLEANUP("Couldn't print the RPC to stdout\n");
120 }
121 r = lyd_print_file(stdout, envp, LYD_XML, 0);
122 }
123 if (r) {
124 ERR_MSG_CLEANUP("Couldn't print the RPC to stdout\n");
125 }
126
127cleanup:
128 lyd_free_all(envp);
129 lyd_free_all(op);
130 nc_rpc_free(rpc);
131 return rc;
132}
133
134int
135main(int argc, char **argv)
136{
roman3adc7c02023-10-25 11:10:58 +0200137 int rc = 0, opt, port = 0;
Roman Janota907cf932022-06-03 12:33:34 +0200138 struct nc_session *session = NULL;
139 const char *unix_socket_path = NULL, *rpc_parameter_1 = NULL, *rpc_parameter_2 = NULL;
roman3adc7c02023-10-25 11:10:58 +0200140 const char *ssh_pubkey_path = NULL, *ssh_privkey_path = NULL;
Roman Janota907cf932022-06-03 12:33:34 +0200141
142 struct option options[] = {
roman3adc7c02023-10-25 11:10:58 +0200143 {"help", no_argument, NULL, 'h'},
144 {"port", required_argument, NULL, 'p'},
145 {"unix-path", required_argument, NULL, 'u'},
146 {"ssh-pubkey", required_argument, NULL, 'P'},
147 {"ssh-privkey", required_argument, NULL, 'i'},
148 {"debug", no_argument, NULL, 'd'},
149 {NULL, 0, NULL, 0}
Roman Janota907cf932022-06-03 12:33:34 +0200150 };
151
152 if (argc == 1) {
153 help_print();
154 goto cleanup;
155 }
156
Roman Janota907cf932022-06-03 12:33:34 +0200157 /* set the path to search for schemas */
158 nc_client_set_schema_searchpath(MODULES_DIR);
159
160 opterr = 0;
161
roman3adc7c02023-10-25 11:10:58 +0200162 while ((opt = getopt_long(argc, argv, "hp:u:P:i:d", options, NULL)) != -1) {
Roman Janota907cf932022-06-03 12:33:34 +0200163 switch (opt) {
164 case 'h':
165 help_print();
166 goto cleanup;
167
roman3adc7c02023-10-25 11:10:58 +0200168 case 'p':
169 port = strtoul(optarg, NULL, 10);
Roman Janota907cf932022-06-03 12:33:34 +0200170 break;
171
roman3adc7c02023-10-25 11:10:58 +0200172 case 'u':
173 unix_socket_path = optarg;
174 break;
175
176 case 'P':
177 ssh_pubkey_path = optarg;
178 break;
179
180 case 'i':
181 ssh_privkey_path = optarg;
Roman Janota907cf932022-06-03 12:33:34 +0200182 break;
183
184 case 'd':
185 nc_verbosity(NC_VERB_DEBUG);
romanc1d2b092023-02-02 08:58:27 +0100186 nc_libssh_thread_verbosity(2);
Roman Janota907cf932022-06-03 12:33:34 +0200187 break;
188
189 default:
190 ERR_MSG_CLEANUP("Invalid option or missing argument\n");
191 }
192 }
193
194 if (optind == argc) {
195 ERR_MSG_CLEANUP("Expected the name of RPC after options\n");
196 }
197
roman3adc7c02023-10-25 11:10:58 +0200198 /* check invalid args combinations */
199 if (unix_socket_path && port) {
200 ERR_MSG_CLEANUP("Both UNIX socket path and port specified. Please choose either SSH or UNIX.\n");
201 } else if (unix_socket_path && (ssh_pubkey_path || ssh_privkey_path)) {
202 ERR_MSG_CLEANUP("Both UNIX socket path and a path to key(s) specified. Please choose either SSH or UNIX.\n");
203 } else if ((port == 10001) && (!ssh_pubkey_path || !ssh_privkey_path)) {
204 ERR_MSG_CLEANUP("You need to specify both paths to private and public keys, if you want to connect to a publickey endpoint.\n");
205 } else if ((port == 10000) && (ssh_pubkey_path || ssh_privkey_path)) {
206 ERR_MSG_CLEANUP("Public or private key specified, when connecting to the password endpoint.\n");
207 } else if (!unix_socket_path && !port) {
208 ERR_MSG_CLEANUP("Neither UNIX socket or SSH specified.\n");
209 }
210
Roman Janota907cf932022-06-03 12:33:34 +0200211 /* connect to the server using the specified transport protocol */
roman3adc7c02023-10-25 11:10:58 +0200212 if (unix_socket_path) {
213 /* it's UNIX socket */
Roman Janota907cf932022-06-03 12:33:34 +0200214 session = nc_connect_unix(unix_socket_path, NULL);
roman3adc7c02023-10-25 11:10:58 +0200215 } else {
216 /* it must be SSH, so set the client SSH username to always be used when connecting to the server */
217 if (nc_client_ssh_set_username(SSH_USERNAME)) {
218 ERR_MSG_CLEANUP("Couldn't set the SSH username\n");
219 }
Roman Janota907cf932022-06-03 12:33:34 +0200220
roman3adc7c02023-10-25 11:10:58 +0200221 if (ssh_pubkey_path && ssh_privkey_path) {
222 /* set the client's SSH keypair to be used for authentication if necessary */
223 if (nc_client_ssh_add_keypair(ssh_pubkey_path, ssh_privkey_path)) {
224 ERR_MSG_CLEANUP("Couldn't set client's SSH keypair.\n");
225 }
226 }
Roman Janota907cf932022-06-03 12:33:34 +0200227
roman3adc7c02023-10-25 11:10:58 +0200228 /* try to connect via SSH */
229 session = nc_connect_ssh(SSH_ADDRESS, port, NULL);
Roman Janota907cf932022-06-03 12:33:34 +0200230 }
231 if (!session) {
232 ERR_MSG_CLEANUP("Couldn't connect to the server\n");
233 }
234
235 /* sending a get RPC */
236 if (!strcmp(argv[optind], "get")) {
237 if (optind + 1 < argc) {
238 /* use the specified XPath filter */
239 rpc_parameter_1 = argv[optind + 1];
240 }
241 if (send_rpc(session, NC_RPC_GET, rpc_parameter_1, rpc_parameter_2)) {
242 rc = 1;
243 goto cleanup;
244 }
245 /* sending a get-config RPC */
246 } else if (!strcmp(argv[optind], "get-config")) {
247 /* use the specified datastore and optional XPath filter */
248 if (optind + 2 < argc) {
249 rpc_parameter_1 = argv[optind + 1];
250 rpc_parameter_2 = argv[optind + 2];
251 } else if (optind + 1 < argc) {
252 rpc_parameter_1 = argv[optind + 1];
253 }
254 if (send_rpc(session, NC_RPC_GETCONFIG, rpc_parameter_1, rpc_parameter_2)) {
255 rc = 1;
256 goto cleanup;
257 }
258 } else {
259 ERR_MSG_CLEANUP("Invalid name of a RPC\n");
260 }
261
262cleanup:
263 nc_session_free(session, NULL);
264 nc_client_destroy();
265 return rc;
266}