1    	/*
2    	 * Copyright 2025-2026 the Pacemaker project contributors
3    	 *
4    	 * The version control history for this file may have further details.
5    	 *
6    	 * This source code is licensed under the GNU General Public License version 2
7    	 * or later (GPLv2+) WITHOUT ANY WARRANTY.
8    	 */
9    	
10   	#include <crm_internal.h>
11   	
12   	#include <errno.h>              // EINVAL, ENODEV, ENOENT, ENOTCONN
13   	#include <locale.h>             // setlocale
14   	#include <stdbool.h>
15   	#include <stddef.h>
16   	#include <stdlib.h>             // setenv, unsetenv
17   	#include <syslog.h>             // LOG_DEBUG
18   	#include <sys/stat.h>           // umask, S_IRGRP, S_IROTH, ...
19   	#include <sys/wait.h>           // WEXITSTATUS
20   	#include <unistd.h>             // geteuid
21   	
22   	#include <glib.h>
23   	#include <libxml/tree.h>        // xmlNode, xmlNodeGetContent
24   	#include <libxml/xmlmemory.h>   // xmlFree
25   	#include <libxml/xmlstring.h>   // xmlChar
26   	
27   	#include <crm/cib/internal.h>   // cib__clean_up_connection, cib__signon_query
28   	#include <crm/common/internal.h>
29   	#include <crm/common/results.h>
30   	#include <crm/common/xml.h>     // crm_element_value, PCMK_XA_*
31   	
32   	#include <pacemaker-internal.h> // pcmk__query_node_name
33   	
34   	#define SUMMARY "cibsecret - manage sensitive information in Pacemaker CIB"
35   	
36   	#define LRM_MAGIC "lrm://"
37   	#define SSH_OPTS "-o StrictHostKeyChecking=no"
38   	
39   	static gchar **remainder = NULL;
40   	static gboolean no_cib = FALSE;
41   	
42   	static GOptionEntry entries[] = {
43   	    { "no-cib", 'C', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &no_cib,
44   	      "Don't read or write the CIB",
45   	      NULL },
46   	
47   	    { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING_ARRAY, &remainder,
48   	      NULL,
49   	      NULL },
50   	
51   	    { NULL }
52   	};
53   	
54   	/*!
55   	 * \internal
56   	 * \brief A function for running a command on remote hosts
57   	 *
58   	 * \param[in,out] out     Output object
59   	 * \param[in]     nodes   A list of remote hosts
60   	 * \param[in]     argv    The list of command line arguments to run
61   	 *
62   	 * \return Standard Pacemaker return code
63   	 *
64   	 * \note On error, \p out->err() will be called to record stderr of the process
65   	 */
66   	typedef int (*rsh_fn_t)(pcmk__output_t *out, char **nodes, char **argv);
67   	
68   	/*!
69   	 * \internal
70   	 * \brief A function for copying a file to remote hosts
71   	 *
72   	 * \param[in,out] out     Output object
73   	 * \param[in]     nodes   A list of remote hosts
74   	 * \param[in]     to      The destination path on the remote host
75   	 * \param[in]     from    The local file (or directory) to copy
76   	 *
77   	 * \return Standard Pacemaker return code
78   	 *
79   	 * \note \p from can either be a single file or a directory.  It cannot be
80   	 *       be multiple files in a space-separated string.  If multiple files need
81   	 *       to be copied, either copy the entire directory at once or call this
82   	 *       function multiple times.
83   	 *
84   	 * \note On error, \p out->err() will be called to record stderr of the process
85   	 */
86   	typedef int (*rcp_fn_t)(pcmk__output_t *out, char **nodes, const char *to,
87   	                        const char *from);
88   	
89   	struct subcommand_entry {
90   	    const char *name;
91   	    int args;
92   	    const char *usage;
93   	    bool requires_cib;
94   	    int (*handler)(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn);
95   	};
96   	
97   	/*!
98   	 * \internal
99   	 * \brief Run a command line process
100  	 *
101  	 * \param[in,out] out          Output object
102  	 * \param[in]     argv         The list of command line arguments to run
103  	 * \param[out]    standard_out If not NULL, where to save stdout of the process
104  	 *
105  	 * \return Standard Pacemaker return code
106  	 *
107  	 * \note On error, \p out->err() will be called to record stderr of the process
108  	 */
109  	static int
110  	run_cmdline(pcmk__output_t *out, char **argv, char **standard_out)
111  	{
112  	    int rc = pcmk_rc_ok;
113  	    gboolean success = FALSE;
114  	    GError *error = NULL;
115  	    gchar *sout = NULL;
116  	    gchar *serr = NULL;
117  	    int status;
118  	
119  	    /* A failure here is a failure starting the program (for example, it doesn't
120  	     * exist on the $PATH), not that it ran but exited with an error code.
121  	     */
122  	    success = g_spawn_sync(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
123  	                           &sout, &serr, &status, &error);
124  	    if (!success) {
125  	        out->err(out, "%s", error->message);
126  	        rc = pcmk_rc_error;
127  	        goto done;
128  	    }
129  	
130  	    /* A failure here indicates that the program exited with a non-zero exit
131  	     * code or due to a fatal signal.
132  	     */
133  	    /* @FIXME @COMPAT g_spawn_check_exit_status is deprecated as of glib 2.70
134  	     * and is replaced with g_spawn_check_wait_status.
135  	     */
136  	    success = g_spawn_check_exit_status(status, &error);
137  	
138  	    if (!success) {
139  	        out->err(out, "%s",  error->message);
140  	        out->subprocess_output(out, WEXITSTATUS(status), sout, serr);
141  	        rc = pcmk_rc_error;
142  	    }
143  	
144  	done:
145  	    pcmk__str_update(standard_out, sout);
146  	
147  	    g_free(sout);
148  	    g_free(serr);
149  	    g_clear_error(&error);
150  	
151  	    return rc;
152  	}
153  	
154  	static int
155  	pssh(pcmk__output_t *out, char **nodes, char **cmd)
156  	{
157  	    int rc = pcmk_rc_ok;
158  	    gchar *hosts = g_strjoinv(" ", nodes);
159  	
160  	    int cmd_len = g_strv_length(cmd);
161  	    // Length of our args, plus the length of cmd, plus 1 for terminating NULL
162  	    int total_len = 7 + cmd_len + 1;
163  	    gchar **argv = g_new0(gchar *, total_len);
164  	
165  	    argv[0] = g_strdup("pssh");
166  	    argv[1] = g_strdup("-i");
167  	    argv[2] = g_strdup("-H");
168  	    argv[3] = hosts;
169  	    argv[4] = g_strdup("-O");
170  	    argv[5] = g_strdup("StrictHostKeyChecking=no");
171  	    argv[6] = g_strdup("--");
172  	
173  	    // Append passed in args to argv
174  	    for (int i = 0; i < cmd_len; i++) {
175  	        argv[i+7] = g_strdup(cmd[i]);
176  	    }
177  	
178  	    argv[total_len-1] = NULL;
179  	
180  	    rc = run_cmdline(out, argv, NULL);
181  	    g_strfreev(argv);
182  	    return rc;
183  	}
184  	
185  	static int
186  	pdsh(pcmk__output_t *out, char **nodes, char **cmd)
187  	{
188  	    int rc = pcmk_rc_ok;
189  	    gchar *hosts = g_strjoinv(",", nodes);
190  	
191  	    int cmd_len = g_strv_length(cmd);
192  	    int total_len = 4 + cmd_len + 1;
193  	    gchar **argv = g_new0(gchar *, total_len);
194  	
195  	    argv[0] = g_strdup("pdsh");
196  	    argv[1] = g_strdup("-w");
197  	    argv[2] = hosts;
198  	    argv[3] = g_strdup("--");
199  	
200  	    for (int i = 0; i < cmd_len; i++) {
201  	        argv[i+4] = g_strdup(cmd[i]);
202  	    }
203  	
204  	    argv[total_len-1] = NULL;
205  	
206  	    setenv("PDSH_SSH_ARGS_APPEND", SSH_OPTS, 1);
207  	    rc = run_cmdline(out, argv, NULL);
208  	    unsetenv("PDSH_SSH_ARGS_APPEND");
209  	
210  	    g_strfreev(argv);
211  	    return rc;
212  	}
213  	
214  	static int
215  	ssh(pcmk__output_t *out, char **nodes, char **cmd)
216  	{
217  	    int cmd_len = g_strv_length(cmd);
218  	    int total_len = 5 + cmd_len + 1;
219  	    int rc = pcmk_rc_ok;
220  	
221  	    for (char **node = nodes; *node != NULL; node++) {
222  	        gchar **argv = g_new0(gchar *, total_len);
223  	
224  	        argv[0] = g_strdup("ssh");
225  	        argv[1] = g_strdup("-o");
226  	        argv[2] = g_strdup("StrictHostKeyChecking=no");
227  	        argv[3] = g_strdup(*node);
228  	        argv[4] = g_strdup("--");
229  	
230  	        for (int i = 0; i < cmd_len; i++) {
231  	            argv[i+5] = g_strdup(cmd[i]);
232  	        }
233  	
234  	        argv[total_len-1] = NULL;
235  	
236  	        rc = run_cmdline(out, argv, NULL);
237  	        g_strfreev(argv);
238  	
239  	        if (rc != pcmk_rc_ok) {
240  	            return rc;
241  	        }
242  	    }
243  	
244  	    return rc;
245  	}
246  	
247  	static int
248  	pscp(pcmk__output_t *out, char **nodes, const char *to, const char *from)
249  	{
250  	    int rc = pcmk_rc_ok;
251  	    gchar **argv = g_new0(gchar *, 11);
252  	    gchar *hosts = g_strjoinv(" ", nodes);
253  	
254  	    argv[0] = g_strdup("pscp.pssh");
255  	    argv[1] = g_strdup("-H");
256  	    argv[2] = hosts;
257  	    argv[3] = g_strdup("-x");
258  	    argv[4] = g_strdup("-pr");
259  	    argv[5] = g_strdup("-O");
260  	    argv[6] = g_strdup("StrictHostKeyChecking=no");
261  	    argv[7] = g_strdup("--");
262  	    argv[8] = g_strdup(from);
263  	    argv[9] = g_strdup(to);
264  	    argv[10] = NULL;
265  	
266  	    rc = run_cmdline(out, argv, NULL);
267  	
268  	    g_strfreev(argv);
269  	    return rc;
270  	}
271  	
272  	static int
273  	pdcp(pcmk__output_t *out, char **nodes, const char *to, const char *from)
274  	{
275  	    int rc = pcmk_rc_ok;
276  	    gchar **argv = g_new0(gchar *, 8);
277  	    gchar *hosts = g_strjoinv(",", nodes);
278  	
279  	    argv[0] = g_strdup("pdcp");
280  	    argv[1] = g_strdup("-pr");
281  	    argv[2] = g_strdup("-w");
282  	    argv[3] = hosts;
283  	    argv[4] = g_strdup("--");
284  	    argv[5] = g_strdup(from);
285  	    argv[6] = g_strdup(to);
286  	    argv[7] = NULL;
287  	
288  	    setenv("PDSH_SSH_ARGS_APPEND", SSH_OPTS, 1);
289  	    rc = run_cmdline(out, argv, NULL);
290  	    unsetenv("PDSH_SSH_ARGS_APPEND");
291  	
292  	    g_strfreev(argv);
293  	    return rc;
294  	}
295  	
296  	static int
297  	scp(pcmk__output_t *out, char **nodes, const char *to, const char *from)
298  	{
299  	    int rc = pcmk_rc_ok;
300  	
301  	    for (char **node = nodes; *node != NULL; node++) {
302  	        gchar **argv = g_new0(gchar *, 7);
303  	
304  	        argv[0] = g_strdup("scp");
305  	        argv[1] = g_strdup("-pqr");
306  	        argv[2] = g_strdup("-o");
307  	        argv[3] = g_strdup("StrictHostKeyChecking=no");
308  	        argv[4] = g_strdup(from);
309  	        argv[5] = g_strdup_printf("%s:%s", *node, to);
310  	        argv[6] = NULL;
311  	
312  	        rc = run_cmdline(out, argv, NULL);
313  	        g_strfreev(argv);
314  	
315  	        if (rc != pcmk_rc_ok) {
316  	            return rc;
317  	        }
318  	    }
319  	
320  	    return rc;
321  	}
322  	
323  	static gchar **
324  	reachable_hosts(pcmk__output_t *out, GList *all)
325  	{
326  	    GPtrArray *reachable = NULL;
327  	    gchar *path = NULL;
328  	
329  	    path = g_find_program_in_path("fping");
330  	
331  	    reachable = g_ptr_array_new();
332  	
333  	    if ((path == NULL) || (geteuid() != 0)) {
334  	        for (GList *host = all; host != NULL; host = host->next) {
335  	            int rc = pcmk_rc_ok;
336  	            gchar **argv = g_new0(gchar *, 6);
337  	
338  	            argv[0] = g_strdup("ping");
339  	            argv[1] = g_strdup("-c");
340  	            argv[2] = g_strdup("2");
341  	            argv[3] = g_strdup("-q");
342  	            argv[4] = g_strdup(host->data);
343  	            argv[5] = NULL;
344  	
345  	            rc = run_cmdline(out, argv, NULL);
346  	            g_strfreev(argv);
347  	
348  	            if (rc == pcmk_rc_ok) {
349  	                g_ptr_array_add(reachable, g_strdup(host->data));
350  	            }
351  	        }
352  	
353  	    } else {
354  	        GString *all_str = g_string_sized_new(64);
355  	        gchar **parts = NULL;
356  	        gchar **argv = g_new0(gchar *, 5);
357  	        char *standard_out = NULL;
358  	
359  	        for (GList *host = all; host != NULL; host = host->next) {
360  	            pcmk__add_word(&all_str, 64, host->data);
361  	        }
362  	
363  	        argv[0] = g_strdup("fping");
364  	        argv[1] = g_strdup("-a");
365  	        argv[2] = g_strdup("-q");
366  	        argv[3] = g_strdup(all_str->str);
367  	        argv[4] = NULL;
368  	
369  	        run_cmdline(out, argv, &standard_out);
370  	        g_strfreev(argv);
371  	
372  	        parts = g_strsplit(standard_out, "\n", 0);
373  	        for (gchar **p = parts; *p != NULL; p++) {
374  	            if (pcmk__str_empty(*p)) {
375  	                continue;
376  	            }
377  	
378  	            g_ptr_array_add(reachable, g_strdup(*p));
379  	        }
380  	
381  	        free(standard_out);
382  	        g_string_free(all_str, TRUE);
383  	        g_strfreev(parts);
384  	    }
385  	
386  	    g_free(path);
387  	    g_ptr_array_add(reachable, NULL);
388  	    return (char **) g_ptr_array_free(reachable, FALSE);
389  	}
390  	
391  	struct node_data {
392  	    pcmk__output_t *out;
393  	    char *local_node;
394  	    const char *field;
395  	    GList *all_nodes;
396  	};
397  	
398  	static void
399  	node_iter_helper(xmlNode *result, void *user_data)
400  	{
401  	    struct node_data *data = user_data;
402  	    const char *uname = pcmk__xe_get(result, PCMK_XA_UNAME);
403  	    const char *id = pcmk__xe_get(result, data->field);
404  	    const char *name = pcmk__s(uname, id);
405  	
406  	    /* Filter out the local node */
407  	    if (pcmk__str_eq(name, data->local_node, pcmk__str_null_matches)) {
408  	        return;
409  	    }
410  	
411  	    data->all_nodes = g_list_append(data->all_nodes, g_strdup(name));
412  	}
413  	
414  	static gchar **
415  	get_live_peers(pcmk__output_t *out)
416  	{
417  	    int rc = pcmk_rc_ok;
418  	    xmlNode *xml_node = NULL;
419  	    gchar **reachable = NULL;
420  	    cib_t *cib = NULL;
421  	
422  	    struct node_data nd = {
423  	        .out = out,
424  	        .local_node = NULL,
425  	        .all_nodes = NULL
426  	    };
427  	
428  	    rc = cib__signon_query(out, &cib, &xml_node);
429  	    if (rc != pcmk_rc_ok) {
430  	        out->err(out, "Could not get list of cluster nodes");
431  	        goto done;
432  	    }
433  	
434  	    /* Get the local node name if possible. */
435  	    if (cib->variant != cib_file) {
436  	        rc = pcmk__query_node_name(out, 0, &(nd.local_node), 0);
437  	        if (rc != pcmk_rc_ok) {
438  	            out->err(out, "Could not get local node name");
439  	            goto done;
440  	        }
441  	    }
442  	
443  	    /* Filter out the local node from the list of all node names.  If we don't
444  	     * have a local node (for instance, because CIB_file is set) then we'll
445  	     * just use the list of all node names instead.
446  	     */
447  	    nd.field = PCMK_XA_ID;
448  	    pcmk__xpath_foreach_result(xml_node->doc, PCMK__XP_MEMBER_NODE_CONFIG,
449  	                               node_iter_helper, &nd);
450  	    nd.field = PCMK_XA_VALUE;
451  	    pcmk__xpath_foreach_result(xml_node->doc, PCMK__XP_GUEST_NODE_CONFIG,
452  	                               node_iter_helper, &nd);
453  	    nd.field = PCMK_XA_ID;
454  	    pcmk__xpath_foreach_result(xml_node->doc, PCMK__XP_REMOTE_NODE_CONFIG,
455  	                               node_iter_helper, &nd);
456  	
457  	    if (nd.all_nodes == NULL) {
458  	        goto done;
459  	    }
460  	
461  	    /* Get a list of all nodes that respond to pings */
462  	    reachable = reachable_hosts(out, nd.all_nodes);
463  	
464  	    /* Warn the user about any that didn't respond to pings */
465  	    for (const GList *iter = nd.all_nodes; iter != NULL; iter = iter->next) {
466  	        const char *node_name = iter->data;
467  	
468  	        if (!pcmk__g_strv_contains(reachable, node_name)) {
469  	            out->info(out, "Node %s is down - you'll need to update it "
470  	                      "with `cibsecret sync` later", node_name);
471  	        }
472  	    }
473  	
474  	done:
475  	    cib__clean_up_connection(&cib);
476  	
477  	    free(nd.local_node);
478  	    free(xml_node);
479  	    g_list_free_full(nd.all_nodes, g_free);
480  	
481  	    return reachable;
482  	}
483  	
484  	static int
485  	sync_one_file(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn,
486  	              const char *path)
487  	{
488  	    int rc = pcmk_rc_ok;
489  	    gchar *dirname = NULL;
490  	    gchar **peers = get_live_peers(out);
491  	    gchar *peer_str = NULL;
492  	    gchar **argv = g_new0(gchar *, 4);
493  	
494  	    if (peers == NULL) {
495  	        return pcmk_rc_ok;
496  	    }
497  	
498  	    peer_str = g_strjoinv(" ", peers);
499  	
500  	    if (pcmk__str_eq(remainder[0], "delete", pcmk__str_none)) {
501  	        out->info(out, "Deleting %s from %s ...", path, peer_str);
502  	    } else {
503  	        out->info(out, "Syncing %s to %s ...", path, peer_str);
504  	    }
505  	
506  	    dirname = g_path_get_dirname(path);
507  	
508  	    argv[0] = g_strdup("mkdir");
509  	    argv[1] = g_strdup("-p");
510  	    argv[2] = g_strdup(dirname);
511  	    argv[3] = NULL;
512  	
513  	    rc = rsh_fn(out, peers, argv);
514  	
515  	    if (rc != pcmk_rc_ok) {
516  	        goto done;
517  	    }
518  	
519  	    if (g_file_test(path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
520  	        char *sign_path = NULL;
521  	
522  	        rc = rcp_fn(out, peers, dirname, path);
523  	        if (rc != pcmk_rc_ok) {
524  	            goto done;
525  	        }
526  	
527  	        sign_path = pcmk__assert_asprintf("%s.sign", path);
528  	        rc = rcp_fn(out, peers, dirname, sign_path);
529  	        free(sign_path);
530  	
531  	    } else {
532  	        g_strfreev(argv);
533  	        argv = g_new0(gchar *, 5);
534  	
535  	        argv[0] = g_strdup("rm");
536  	        argv[1] = g_strdup("-f");
537  	        argv[2] = g_strdup(path);
538  	        argv[3] = g_strdup_printf("%s.sign", path);
539  	        argv[4] = NULL;
540  	
541  	        rc = rsh_fn(out, peers, argv);
542  	    }
543  	
544  	done:
545  	    g_free(dirname);
546  	    g_strfreev(argv);
547  	    g_strfreev(peers);
548  	    g_free(peer_str);
549  	    return rc;
550  	}
551  	
552  	static int
553  	check_cib_rsc(pcmk__output_t *out, const char *rsc)
554  	{
555  	    int rc = pcmk_rc_ok;
556  	    gchar **argv = g_new0(gchar *, 5);
557  	
558  	    if (no_cib) {
559  	        goto done;
560  	    }
561  	
562  	    argv[0] = g_strdup("crm_resource");
563  	    argv[1] = g_strdup("-r");
564  	    argv[2] = g_strdup(rsc);
565  	    argv[3] = g_strdup("-W");
566  	    argv[4] = NULL;
567  	
568  	    rc = run_cmdline(out, argv, NULL);
569  	
570  	done:
571  	    g_strfreev(argv);
572  	    return rc;
573  	}
574  	
575  	static bool
576  	is_secret(const char *s)
577  	{
578  	    if (no_cib) {
579  	        /* Assume that the secret is in the CIB if we can't connect */
580  	        return true;
581  	    }
582  	
583  	    return pcmk__str_eq(s, LRM_MAGIC, pcmk__str_none);
584  	}
585  	
586  	static char *
587  	get_cib_param(pcmk__output_t *out, const char *rsc, const char *param)
588  	{
589  	    int rc = pcmk_rc_ok;
590  	    gchar **argv = g_new0(gchar *, 7);
591  	    char *standard_out = NULL;
592  	    char *retval = NULL;
593  	    char *xpath = NULL;
594  	    xmlNode *xml = NULL;
595  	    xmlNode *node = NULL;
596  	    xmlChar *content = NULL;
597  	
598  	    if (no_cib) {
599  	        g_strfreev(argv);
600  	        return NULL;
601  	    }
602  	
603  	    argv[0] = g_strdup("crm_resource");
604  	    argv[1] = g_strdup("-r");
605  	    argv[2] = g_strdup(rsc);
606  	    argv[3] = g_strdup("-g");
607  	    argv[4] = g_strdup(param);
608  	    argv[5] = g_strdup("--output-as=xml");
609  	    argv[6] = NULL;
610  	
611  	    rc = run_cmdline(out, argv, &standard_out);
612  	    g_strfreev(argv);
613  	
614  	    if (rc != pcmk_rc_ok) {
615  	        goto done;
616  	    }
617  	
618  	    xml = pcmk__xml_parse(standard_out);
619  	
620  	    if (xml == NULL) {
621  	        goto done;
622  	    }
623  	
624  	    xpath = pcmk__assert_asprintf("//" PCMK_XE_ITEM "[@" PCMK_XA_NAME "='%s']",
625  	                                  param);
626  	    node = pcmk__xpath_find_one(xml->doc, xpath, LOG_DEBUG);
627  	
628  	    if (node == NULL) {
629  	        goto done;
630  	    }
631  	
632  	    content = xmlNodeGetContent(node);
633  	
634  	    if (content != NULL) {
635  	        retval = pcmk__str_copy((char *) content);
636  	        xmlFree(content);
637  	    }
638  	
639  	done:
640  	    free(standard_out);
641  	    free(xpath);
642  	    pcmk__xml_free(xml);
643  	    return retval;
644  	}
645  	
646  	static int
647  	remove_cib_param(pcmk__output_t *out, const char *rsc, const char *param)
648  	{
649  	    int rc = pcmk_rc_ok;
650  	    gchar **argv = g_new0(gchar *, 6);
651  	
652  	    if (no_cib) {
653  	        goto done;
654  	    }
655  	
656  	    argv[0] = g_strdup("crm_resource");
657  	    argv[1] = g_strdup("-r");
658  	    argv[2] = g_strdup(rsc);
659  	    argv[3] = g_strdup("-d");
660  	    argv[4] = g_strdup(param);
661  	    argv[5] = NULL;
662  	
663  	    rc = run_cmdline(out, argv, NULL);
664  	
665  	done:
666  	    g_strfreev(argv);
667  	    return rc;
668  	}
669  	
670  	static int
671  	set_cib_param(pcmk__output_t *out, const char *rsc, const char *param,
672  	              const char *value)
673  	{
674  	    int rc = pcmk_rc_ok;
675  	    gchar **argv = g_new0(gchar *, 8);
676  	
677  	    if (no_cib) {
678  	        goto done;
679  	    }
680  	
681  	    argv[0] = g_strdup("crm_resource");
682  	    argv[1] = g_strdup("-r");
683  	    argv[2] = g_strdup(rsc);
684  	    argv[3] = g_strdup("-p");
685  	    argv[4] = g_strdup(param);
686  	    argv[5] = g_strdup("-v");
687  	    argv[6] = g_strdup(value);
688  	    argv[7] = NULL;
689  	
690  	    rc = run_cmdline(out, argv, NULL);
691  	
692  	done:
693  	    g_strfreev(argv);
694  	    return rc;
695  	}
696  	
697  	static char *
698  	local_files_get(const char *rsc, const char *param)
699  	{
700  	    char *retval = NULL;
701  	    char *lf_file = NULL;
702  	    gchar *contents = NULL;
703  	
704  	    lf_file = pcmk__assert_asprintf(PCMK__CIB_SECRETS_DIR "/%s/%s", rsc, param);
705  	    if (g_file_get_contents(lf_file, &contents, NULL, NULL)) {
706  	        contents = g_strchomp(contents);
707  	        retval = pcmk__str_copy(contents);
708  	        g_free(contents);
709  	    }
710  	
711  	    free(lf_file);
712  	    return retval;
713  	}
714  	
715  	static char *
716  	local_files_getsum(const char *rsc, const char *param)
717  	{
718  	    char *retval = NULL;
719  	    char *lf_file = NULL;
720  	    gchar *contents = NULL;
721  	
722  	    lf_file = pcmk__assert_asprintf(PCMK__CIB_SECRETS_DIR "/%s/%s.sign", rsc,
723  	                                    param);
724  	    if (g_file_get_contents(lf_file, &contents, NULL, NULL)) {
725  	        contents = g_strchomp(contents);
726  	        retval = pcmk__str_copy(contents);
727  	        g_free(contents);
728  	    }
729  	
730  	    free(lf_file);
731  	    return retval;
732  	}
733  	
734  	static int
735  	local_files_remove(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn,
736  	                   const char *rsc, const char *param)
737  	{
738  	    int rc = pcmk_rc_ok;
739  	    char *lf_file = NULL;
740  	    gchar **argv = g_new0(gchar *, 5);
741  	
742  	    lf_file = pcmk__assert_asprintf(PCMK__CIB_SECRETS_DIR "/%s/%s", rsc, param);
743  	
744  	    argv[0] = g_strdup("rm");
745  	    argv[1] = g_strdup("-f");
746  	    argv[2] = g_strdup(lf_file);
747  	    argv[3] = g_strdup_printf("%s.sign", lf_file);
748  	    argv[4] = NULL;
749  	
750  	    rc = run_cmdline(out, argv, NULL);
751  	
752  	    if (rc == pcmk_rc_ok) {
753  	        rc = sync_one_file(out, rsh_fn, rcp_fn, lf_file);
754  	    }
755  	
756  	    free(lf_file);
757  	    g_strfreev(argv);
758  	    return rc;
759  	}
760  	
761  	static int
762  	local_files_set(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn,
763  	                const char *rsc, const char *param, const char *value)
764  	{
765  	    char *contents = NULL;
766  	    char *lf_dir = NULL;
767  	    char *lf_file = NULL;
768  	    char *sign_file = NULL;
769  	    char *calc_sum = NULL;
770  	    int rc = pcmk_rc_ok;
771  	
772  	    lf_dir = pcmk__assert_asprintf(PCMK__CIB_SECRETS_DIR "/%s", rsc);
773  	
774  	    if (g_mkdir_with_parents(lf_dir, 0700) != 0) {
775  	        rc = errno;
776  	        out->err(out, "Could not create directory %s: %s", lf_dir,
777  	                 pcmk_rc_str(rc));
778  	        goto done;
779  	    }
780  	
781  	    lf_file = pcmk__assert_asprintf("%s/%s", lf_dir, param);
782  	    contents = pcmk__assert_asprintf("%s\n", value);
783  	    if (!g_file_set_contents(lf_file, contents, -1, NULL)) {
784  	        rc = EIO;
785  	        out->err(out, "Could not create file %s: %s", lf_file,
786  	                 pcmk_rc_str(rc));
787  	        goto done;
788  	    }
789  	
790  	    free(contents);
791  	
792  	    sign_file = pcmk__assert_asprintf("%s/%s.sign", lf_dir, param);
793  	    calc_sum = pcmk__md5sum(value);
794  	    contents = pcmk__assert_asprintf("%s\n", calc_sum);
795  	
796  	    if (!g_file_set_contents(sign_file, contents, -1, NULL)) {
797  	        rc = EIO;
798  	        out->err(out, "Could not create file %s: %s", sign_file,
799  	                 pcmk_rc_str(rc));
800  	        goto done;
801  	    }
802  	
803  	    rc = sync_one_file(out, rsh_fn, rcp_fn, lf_file);
804  	
805  	done:
806  	    free(contents);
807  	    free(calc_sum);
808  	    free(sign_file);
809  	    free(lf_dir);
810  	    free(lf_file);
811  	    return rc;
812  	}
813  	
814  	static int
815  	subcommand_check(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
816  	{
817  	    int rc = pcmk_rc_ok;
818  	    const char *rsc = remainder[1];
819  	    const char *param = remainder[2];
820  	    char *value = NULL;
821  	    char *calc_sum = NULL;
822  	    char *local_sum = NULL;
823  	    char *local_value = NULL;
824  	
825  	    if (check_cib_rsc(out, rsc) != pcmk_rc_ok) {
826  	        rc = ENODEV;
827  	        goto done;
828  	    }
829  	
830  	    value = get_cib_param(out, rsc, param);
831  	    if ((value == NULL) || !is_secret(value)) {
832  	        out->err(out, "Resource %s parameter %s not set as secret, nothing to check",
833  	                 rsc, param);
834  	
835  	        /* I don't like this error code, but (1) it maps to CRM_EX_CONFIG which
836  	         * is what the old cibsecret.in would return in this case, and (2) we
837  	         * return it all over the place for a variety of CIB checking errors.
838  	         */
839  	        rc = pcmk_rc_unpack_error;
840  	        goto done;
841  	    }
842  	
843  	    local_sum = local_files_getsum(rsc, param);
844  	    if (local_sum == NULL) {
845  	        out->err(out, "No checksum for resource %s parameter %s", rsc, param);
846  	        rc = ENOENT;
847  	        goto done;
848  	    }
849  	
850  	    local_value = local_files_get(rsc, param);
851  	    if (local_value != NULL) {
852  	        calc_sum = pcmk__md5sum(local_value);
853  	    }
854  	
855  	    if ((local_value == NULL) || !pcmk__str_eq(calc_sum, local_sum, pcmk__str_none)) {
856  	        out->err(out, "Checksum mismatch for resource %s parameter %s", rsc, param);
857  	        rc = pcmk_rc_digest_mismatch;
858  	    }
859  	
860  	done:
861  	    free(local_sum);
862  	    free(local_value);
863  	    free(calc_sum);
864  	    free(value);
865  	    return rc;
866  	}
867  	
868  	static int
869  	subcommand_delete(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
870  	{
871  	    int rc = pcmk_rc_ok;
872  	    const char *rsc = remainder[1];
873  	    const char *param = remainder[2];
874  	
875  	    if (check_cib_rsc(out, rsc) != pcmk_rc_ok) {
876  	        return ENODEV;
877  	    }
878  	
879  	    rc = local_files_remove(out, rsh_fn, rcp_fn, rsc, param);
880  	    if (rc != pcmk_rc_ok) {
881  	        return rc;
882  	    }
883  	
884  	    return remove_cib_param(out, rsc, param);
885  	}
886  	
887  	static int
888  	subcommand_get(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
889  	{
890  	    int rc = subcommand_check(out, rsh_fn, rcp_fn);
891  	    char *value = NULL;
892  	    const char *rsc = remainder[1];
893  	    const char *param = remainder[2];
894  	
895  	    if (rc != pcmk_rc_ok) {
896  	        return rc;
897  	    }
898  	
899  	    value = local_files_get(rsc, param);
900  	    pcmk__assert(value != NULL);
901  	    out->info(out, "%s", value);
902  	
903  	    free(value);
904  	    return pcmk_rc_ok;
905  	}
906  	
907  	/* The previous shell implementation of cibsecret allowed passing the value
908  	 * to set (what would be remainder[3] here) via stdin, which we do not support
909  	 * here at the moment.
910  	 */
911  	static int
912  	subcommand_set(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
913  	{
914  	    int rc = pcmk_rc_ok;
915  	    const char *rsc = remainder[1];
916  	    const char *param = remainder[2];
917  	    const char *value = remainder[3];
918  	    char *current = NULL;
919  	
920  	    if (check_cib_rsc(out, rsc) != pcmk_rc_ok) {
921  	        rc = ENODEV;
922  	        goto done;
923  	    }
924  	
925  	    current = get_cib_param(out, rsc, param);
926  	    if ((current != NULL) && !pcmk__str_any_of(current, LRM_MAGIC, value, NULL)) {
927  	        out->err(out, "CIB value <%s> different for %s rsc parameter %s; please "
928  	                 "delete it first", current, rsc, param);
929  	
930  	        /* I don't like this error code, but (1) it maps to CRM_EX_CONFIG which
931  	         * is what the old cibsecret.in would return in this case, and (2) we
932  	         * return it all over the place for a variety of CIB checking errors.
933  	         */
934  	        rc = pcmk_rc_unpack_error;
935  	        goto done;
936  	    }
937  	
938  	    rc = local_files_set(out, rsh_fn, rcp_fn, rsc, param, value);
939  	    if (rc != pcmk_rc_ok) {
940  	        goto done;
941  	    }
942  	
943  	    rc = set_cib_param(out, rsc, param, LRM_MAGIC);
944  	
945  	done:
946  	    free(current);
947  	    return rc;
948  	}
949  	
950  	static int
951  	subcommand_stash(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
952  	{
953  	    int rc = pcmk_rc_ok;
954  	    const char *rsc = remainder[1];
955  	    const char *param = remainder[2];
956  	    char *value = NULL;
957  	
958  	    if (check_cib_rsc(out, rsc) != pcmk_rc_ok) {
959  	        rc = ENODEV;
960  	        goto done;
961  	    }
962  	
963  	    value = get_cib_param(out, rsc, param);
964  	    if ((value == NULL) || is_secret(value)) {
965  	        if (value == NULL) {
966  	            out->err(out, "Nothing to stash for resource %s parameter %s", rsc,
967  	                     param);
968  	            rc = ENOENT;
969  	        } else {
970  	            out->err(out, "Resource %s parameter %s already set as secret", rsc,
971  	                     param);
972  	            rc = EEXIST;
973  	        }
974  	
975  	        goto done;
976  	    }
977  	
978  	    remainder = g_realloc(remainder, sizeof(gchar *) * 5);
979  	    remainder[3] = g_strdup(value);
980  	    remainder[4] = NULL;
981  	
982  	    rc = subcommand_set(out, rsh_fn, rcp_fn);
983  	
984  	done:
985  	    free(value);
986  	    return rc;
987  	}
988  	
989  	static int
990  	subcommand_sync(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
991  	{
992  	    int rc = pcmk_rc_ok;
993  	    gchar *dirname = NULL;
(1) Event path: Condition "__s == 1", taking false branch.
(2) Event path: Condition "0", taking false branch.
(3) Event alloc_fn: Storage is returned from allocation function "g_malloc0_n".
(4) Event var_assign: Assigning: "__p" = storage returned from "g_malloc0_n(__n, __s)".
(5) Event out_of_scope: Variable "__p" goes out of scope.
(6) Event var_assign: Assigning: "argv" = "({...; __p;})".
Also see events: [leaked_storage]
994  	    gchar **argv = g_new0(gchar *, 4);
995  	    gchar **peers = get_live_peers(out);
996  	    gchar *peer_str = NULL;
997  	
(7) Event path: Condition "peers == NULL", taking true branch.
998  	    if (peers == NULL) {
CID (unavailable; MK=97acb324a3cf6de34bd37751adc5a3e6) (#1 of 1): Resource leak (RESOURCE_LEAK):
(8) Event leaked_storage: Variable "argv" going out of scope leaks the storage it points to.
Also see events: [alloc_fn][var_assign][out_of_scope][var_assign]
999  	        return pcmk_rc_ok;
1000 	    }
1001 	
1002 	    peer_str = g_strjoinv(" ", peers);
1003 	
1004 	    out->info(out, "Syncing %s to %s ...", PCMK__CIB_SECRETS_DIR, peer_str);
1005 	    g_free(peer_str);
1006 	
1007 	    dirname = g_path_get_dirname(PCMK__CIB_SECRETS_DIR);
1008 	
1009 	    argv[0] = g_strdup("rm");
1010 	    argv[1] = g_strdup("-rf");
1011 	    argv[2] = g_strdup(PCMK__CIB_SECRETS_DIR);
1012 	    argv[3] = NULL;
1013 	
1014 	    rc = rsh_fn(out, peers, argv);
1015 	    if (rc != pcmk_rc_ok) {
1016 	        goto done;
1017 	    }
1018 	
1019 	    g_strfreev(argv);
1020 	    argv = g_new0(gchar *, 4);
1021 	
1022 	    argv[0] = g_strdup("mkdir");
1023 	    argv[1] = g_strdup("-p");
1024 	    argv[2] = g_strdup(dirname);
1025 	    argv[3] = NULL;
1026 	
1027 	    rc = rsh_fn(out, peers, argv);
1028 	    if (rc != pcmk_rc_ok) {
1029 	        goto done;
1030 	    }
1031 	
1032 	    rc = rcp_fn(out, peers, dirname, PCMK__CIB_SECRETS_DIR);
1033 	
1034 	done:
1035 	    g_strfreev(argv);
1036 	    g_strfreev(peers);
1037 	    g_free(dirname);
1038 	    return rc;
1039 	}
1040 	
1041 	static int
1042 	subcommand_unstash(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
1043 	{
1044 	    int rc = pcmk_rc_ok;
1045 	    const char *rsc = remainder[1];
1046 	    const char *param = remainder[2];
1047 	    char *local_value = NULL;
1048 	    char *cib_value = NULL;
1049 	
1050 	    local_value = local_files_get(rsc, param);
1051 	    if (local_value == NULL) {
1052 	        out->err(out, "Nothing to unstash for resource %s parameter %s",
1053 	                 rsc, param);
1054 	        rc = ENOENT;
1055 	        goto done;
1056 	    }
1057 	
1058 	    if (check_cib_rsc(out, rsc) != pcmk_rc_ok) {
1059 	        rc = ENODEV;
1060 	        goto done;
1061 	    }
1062 	
1063 	    cib_value = get_cib_param(out, rsc, param);
1064 	    if (!is_secret(cib_value)) {
1065 	        out->info(out, "Resource %s parameter %s is not set as secret, but we "
1066 	                  "have a local value so proceeding anyway", rsc, param);
1067 	    }
1068 	
1069 	    rc = local_files_remove(out, rsh_fn, rcp_fn, rsc, param);
1070 	    if (rc != pcmk_rc_ok) {
1071 	        goto done;
1072 	    }
1073 	
1074 	    rc = set_cib_param(out, rsc, param, local_value);
1075 	
1076 	done:
1077 	    free(cib_value);
1078 	    free(local_value);
1079 	    return rc;
1080 	}
1081 	
1082 	static struct subcommand_entry subcommand_table[] = {
1083 	    { "check", 2, "check <resource-id> <resource-parameter>", false,
1084 	      subcommand_check },
1085 	    { "delete", 2, "delete <resource-id> <resource-parameter>", false,
1086 	      subcommand_delete },
1087 	    { "get", 2, "get <resource-id> <resource-parameter>", false,
1088 	      subcommand_get },
1089 	    { "set", 3, "set <resource-id> <resource-parameter> <value>", false,
1090 	      subcommand_set },
1091 	    { "stash", 2, "stash <resource-id> <resource-parameter>", true,
1092 	      subcommand_stash },
1093 	    { "sync", 0, "sync", false, subcommand_sync },
1094 	    { "unstash", 2, "unstash <resource-id> <resource-parameter>", true,
1095 	      subcommand_unstash },
1096 	    { NULL },
1097 	};
1098 	
1099 	static bool
1100 	tools_installed(pcmk__output_t *out, rsh_fn_t *rsh_fn, rcp_fn_t *rcp_fn,
1101 	                GError **error)
1102 	{
1103 	    gchar *path = NULL;
1104 	
1105 	    path = g_find_program_in_path("pssh");
1106 	    if (path != NULL) {
1107 	        g_free(path);
1108 	        *rsh_fn = pssh;
1109 	        *rcp_fn = pscp;
1110 	        return true;
1111 	    }
1112 	
1113 	    path = g_find_program_in_path("pdsh");
1114 	    if (path != NULL) {
1115 	        g_free(path);
1116 	        *rsh_fn = pdsh;
1117 	        *rcp_fn = pdcp;
1118 	        return true;
1119 	    }
1120 	
1121 	    path = g_find_program_in_path("ssh");
1122 	    if (path != NULL) {
1123 	        g_free(path);
1124 	        *rsh_fn = ssh;
1125 	        *rcp_fn = scp;
1126 	        return true;
1127 	    }
1128 	
1129 	    out->err(out, "Please install one of pssh, pdsh, or ssh");
1130 	    return false;
1131 	}
1132 	
1133 	static pcmk__supported_format_t formats[] = {
1134 	    PCMK__SUPPORTED_FORMAT_NONE,
1135 	    PCMK__SUPPORTED_FORMAT_TEXT,
1136 	    PCMK__SUPPORTED_FORMAT_XML,
1137 	    { NULL, NULL, NULL }
1138 	};
1139 	
1140 	static GOptionContext *
1141 	build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
1142 	    const char *desc = NULL;
1143 	    GOptionContext *context = NULL;
1144 	
1145 	    desc = "This command manages sensitive resource parameter values that should not be\n"
1146 	           "stored directly in Pacemaker's Cluster Information Base (CIB). Such values\n"
1147 	           "are handled by storing a special string directly in the CIB that tells\n"
1148 	           "Pacemaker to look in a separate, protected file for the actual value.\n\n"
1149 	
1150 	           "The secret files are not encrypted, but protected by file system permissions\n"
1151 	           "such that only root can read or modify them.\n\n"
1152 	
1153 	           "Since the secret files are stored locally, they must be synchronized across all\n"
1154 	           "cluster nodes. This command handles the synchronization using (in order of\n"
1155 	           "preference) pssh, pdsh, or ssh, so one of those must be installed. Before\n"
1156 	           "synchronizing, this command will ping the cluster nodes to determine which are\n"
1157 	           "alive, using fping if it is installed, otherwise the ping command. Installing\n"
1158 	           "fping is strongly recommended for better performance.\n\n"
1159 	
1160 	           "Commands and their parameters:\n\n"
1161 	           "check <resource-id> <resource-parameter>\n"
1162 	           "\tVerify that the locally stored value of a sensitive resource parameter\n"
1163 	           "\tmatches its locally stored MD5 hash.\n\n"
1164 	           "delete <resource-id> <resource-parameter>\n"
1165 	           "\tRemove a sensitive resource parameter value.\n\n"
1166 	           "get <resource-id> <resource-parameter>\n"
1167 	           "\tDisplay the locally stored value of a sensitive resource parameter.\n\n"
1168 	           "set <resource-id> <resource-parameter> <value>\n"
1169 	           "\tSet the value of a sensitive resource parameter.\n\n"
1170 	           "stash <resource-id> <resource-parameter>\n"
1171 	           "\tMake a non-sensitive resource parameter that is already in the CIB\n"
1172 	           "\tsensitive (move its value to a locally stored and protected file).\n"
1173 	           "\tThis may not be used with -C.\n\n"
1174 	           "sync\n"
1175 	           "\tCopy all locally stored secrets to all other nodes.\n\n"
1176 	           "unstash <resource-id> <resource-parameter>\n"
1177 	           "\tMake a sensitive resource parameter that is already in the CIB\n"
1178 	           "\tnon-sensitive (move its value from the locally stored file to the CIB).\n"
1179 	           "\tThis may not be used with -C.\n\n\n"
1180 	
1181 	           "Known limitations:\n\n"
1182 	
1183 	           "This command can only be run from full cluster nodes (not Pacemaker Remote\n"
1184 	           "nodes).\n\n"
1185 	
1186 	           "Changes are not atomic, so the cluster may use different values while a\n"
1187 	           "change is in progress. To avoid problems, it is recommended to put the\n"
1188 	           "cluster in maintenance mode when making changes with this command.\n\n"
1189 	
1190 	           "Changes in secret values do not trigger an agent reload or restart of the\n"
1191 	           "affected resource, since they do not change the CIB. If a response is\n"
1192 	           "desired before the next cluster recheck interval, any CIB change (such as\n"
1193 	           "setting a node attribute) will trigger it.\n\n"
1194 	
1195 	           "If any node is down when changes to secrets are made, or a new node is\n"
1196 	           "later added to the cluster, it may have different values when it joins the\n"
1197 	           "cluster, before 'cibsecret sync' is run. To avoid this, it is recommended to\n"
1198 	           "run the sync command (from another node) before starting Pacemaker on the\n"
1199 	           "node.\n\n"
1200 	
1201 	           "Examples:\n\n"
1202 	
1203 	           "# cibsecret set ipmi_node1 passwd SecreT_PASS\n\n"
1204 	           "# cibsecret get ipmi_node1 passwd\n\n"
1205 	           "# cibsecret check ipmi_node1 passwd\n\n"
1206 	           "# cibsecret stash ipmi_node2 passwd\n\n"
1207 	           "# cibsecret sync\n";
1208 	
1209 	    context = pcmk__build_arg_context(args, "text (default), xml", group,
1210 	                                      "<subcommand> [options]");
1211 	    g_option_context_set_description(context, desc);
1212 	    pcmk__add_main_args(context, entries);
1213 	    return context;
1214 	}
1215 	
1216 	int
1217 	main(int argc, char **argv)
1218 	{
1219 	    crm_exit_t exit_code = CRM_EX_OK;
1220 	    int rc = pcmk_rc_ok;
1221 	
1222 	    pcmk__output_t *out = NULL;
1223 	
1224 	    GError *error = NULL;
1225 	
1226 	    GOptionGroup *output_group = NULL;
1227 	    pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
1228 	    gchar **processed_args = pcmk__cmdline_preproc(argv, NULL);
1229 	    GOptionContext *context = build_arg_context(args, &output_group);
1230 	
1231 	    struct subcommand_entry cmd;
1232 	    rsh_fn_t rsh_fn;
1233 	    rcp_fn_t rcp_fn;
1234 	
1235 	    // Load locale information for the local host from the environment
1236 	    setlocale(LC_ALL, "");
1237 	
1238 	    pcmk__register_formats(output_group, formats);
1239 	    if (!g_option_context_parse_strv(context, &processed_args, &error)) {
1240 	        exit_code = CRM_EX_USAGE;
1241 	        goto done;
1242 	    }
1243 	
1244 	    pcmk__cli_init_logging("cibsecret", args->verbosity);
1245 	
1246 	    rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
1247 	    if (rc != pcmk_rc_ok) {
1248 	        exit_code = CRM_EX_ERROR;
1249 	        g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
1250 	                    "Error creating output format %s: %s", args->output_ty,
1251 	                    pcmk_rc_str(rc));
1252 	        goto done;
1253 	    }
1254 	
1255 	    if (args->version) {
1256 	        out->version(out);
1257 	        goto done;
1258 	    }
1259 	
1260 	    /* No subcommand was given */
1261 	    if ((remainder == NULL) || (g_strv_length(remainder) == 0)) {
1262 	        gchar *help = g_option_context_get_help(context, TRUE, NULL);
1263 	
1264 	        exit_code = CRM_EX_USAGE;
1265 	        g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
1266 	                    "Must specify a command option\n\n%s", help);
1267 	        g_free(help);
1268 	        goto done;
1269 	    }
1270 	
1271 	    /* Traverse the subcommand table looking for a match. */
1272 	    for (int i = 0; i < PCMK__NELEM(subcommand_table); i++) {
1273 	        cmd = subcommand_table[i];
1274 	
1275 	        if (!pcmk__str_eq(remainder[0], cmd.name, pcmk__str_none)) {
1276 	            continue;
1277 	        }
1278 	
1279 	        /* We found a match.  Check that enough arguments were given and
1280 	         * display a usage message if not.  The "+ 1" is because the table
1281 	         * entry lists how many arguments the subcommand takes, which does not
1282 	         * include the subcommand itself.
1283 	         */
1284 	        if (g_strv_length(remainder) != cmd.args + 1) {
1285 	            exit_code = CRM_EX_USAGE;
1286 	            g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Usage: %s",
1287 	                        cmd.usage);
1288 	            goto done;
1289 	        }
1290 	
1291 	        /* We've found the subcommand handler and it's used correctly. */
1292 	        break;
1293 	    }
1294 	
1295 	    /* If we didn't find a match, a valid subcommand wasn't given. */
1296 	    if (cmd.name == NULL) {
1297 	        exit_code = CRM_EX_USAGE;
1298 	        g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
1299 	                    "Invalid subcommand given; valid subcommands: "
1300 	                    "check, delete, get, set, stash, sync, unstash");
1301 	        goto done;
1302 	    }
1303 	
1304 	    /* Check that we have the tools necessary to manage secrets */
1305 	    if (!tools_installed(out, &rsh_fn, &rcp_fn, &error)) {
1306 	        exit_code = CRM_EX_NOT_INSTALLED;
1307 	        goto done;
1308 	    }
1309 	
1310 	    /* Set a default umask so files we create are only accessible by the
1311 	     * cluster user.
1312 	     */
1313 	    umask(S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH);
1314 	
1315 	    /* Call the subcommand handler.  If the handler fails, it will have already
1316 	     * set exit_code to the reason why so there's no need to worry with
1317 	     * additional error checking here at the moment.
1318 	     */
1319 	    if (cmd.requires_cib && no_cib) {
1320 	        out->err(out, "No access to Pacemaker, %s not supported", cmd.name);
1321 	        exit_code = CRM_EX_USAGE;
1322 	        goto done;
1323 	    }
1324 	
1325 	    rc = cmd.handler(out, rsh_fn, rcp_fn);
1326 	    exit_code = pcmk_rc2exitc(rc);
1327 	
1328 	 done:
1329 	    g_strfreev(processed_args);
1330 	    g_strfreev(remainder);
1331 	    pcmk__free_arg_context(context);
1332 	
1333 	    pcmk__output_and_clear_error(&error, out);
1334 	
1335 	    if (out != NULL) {
1336 	        out->finish(out, exit_code, true, NULL);
1337 	        pcmk__output_free(out);
1338 	    }
1339 	    pcmk__unregister_formats();
1340 	    crm_exit(exit_code);
1341 	}
1342