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  	        g_strfreev(argv);
496  	        return pcmk_rc_ok;
497  	    }
498  	
499  	    peer_str = g_strjoinv(" ", peers);
500  	
501  	    if (pcmk__str_eq(remainder[0], "delete", pcmk__str_none)) {
502  	        out->info(out, "Deleting %s from %s ...", path, peer_str);
503  	    } else {
504  	        out->info(out, "Syncing %s to %s ...", path, peer_str);
505  	    }
506  	
507  	    dirname = g_path_get_dirname(path);
508  	
509  	    argv[0] = g_strdup("mkdir");
510  	    argv[1] = g_strdup("-p");
511  	    argv[2] = g_strdup(dirname);
512  	    argv[3] = NULL;
513  	
514  	    rc = rsh_fn(out, peers, argv);
515  	
516  	    if (rc != pcmk_rc_ok) {
517  	        goto done;
518  	    }
519  	
520  	    if (g_file_test(path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
521  	        char *sign_path = NULL;
522  	
523  	        rc = rcp_fn(out, peers, dirname, path);
524  	        if (rc != pcmk_rc_ok) {
525  	            goto done;
526  	        }
527  	
528  	        sign_path = pcmk__assert_asprintf("%s.sign", path);
529  	        rc = rcp_fn(out, peers, dirname, sign_path);
530  	        free(sign_path);
531  	
532  	    } else {
533  	        g_strfreev(argv);
534  	        argv = g_new0(gchar *, 5);
535  	
536  	        argv[0] = g_strdup("rm");
537  	        argv[1] = g_strdup("-f");
538  	        argv[2] = g_strdup(path);
539  	        argv[3] = g_strdup_printf("%s.sign", path);
540  	        argv[4] = NULL;
541  	
542  	        rc = rsh_fn(out, peers, argv);
543  	    }
544  	
545  	done:
546  	    g_free(dirname);
547  	    g_strfreev(argv);
548  	    g_strfreev(peers);
549  	    g_free(peer_str);
550  	    return rc;
551  	}
552  	
553  	static int
554  	check_cib_rsc(pcmk__output_t *out, const char *rsc)
555  	{
556  	    int rc = pcmk_rc_ok;
557  	    gchar **argv = g_new0(gchar *, 5);
558  	
559  	    if (no_cib) {
560  	        goto done;
561  	    }
562  	
563  	    argv[0] = g_strdup("crm_resource");
564  	    argv[1] = g_strdup("-r");
565  	    argv[2] = g_strdup(rsc);
566  	    argv[3] = g_strdup("-W");
567  	    argv[4] = NULL;
568  	
569  	    rc = run_cmdline(out, argv, NULL);
570  	
571  	done:
572  	    g_strfreev(argv);
573  	    return rc;
574  	}
575  	
576  	static bool
577  	is_secret(const char *s)
578  	{
579  	    if (no_cib) {
580  	        /* Assume that the secret is in the CIB if we can't connect */
581  	        return true;
582  	    }
583  	
584  	    return pcmk__str_eq(s, LRM_MAGIC, pcmk__str_none);
585  	}
586  	
587  	static char *
588  	get_cib_param(pcmk__output_t *out, const char *rsc, const char *param)
589  	{
590  	    int rc = pcmk_rc_ok;
591  	    gchar **argv = g_new0(gchar *, 7);
592  	    char *standard_out = NULL;
593  	    char *retval = NULL;
594  	    char *xpath = NULL;
595  	    xmlNode *xml = NULL;
596  	    xmlNode *node = NULL;
597  	    xmlChar *content = NULL;
598  	
599  	    if (no_cib) {
600  	        g_strfreev(argv);
601  	        return NULL;
602  	    }
603  	
604  	    argv[0] = g_strdup("crm_resource");
605  	    argv[1] = g_strdup("-r");
606  	    argv[2] = g_strdup(rsc);
607  	    argv[3] = g_strdup("-g");
608  	    argv[4] = g_strdup(param);
609  	    argv[5] = g_strdup("--output-as=xml");
610  	    argv[6] = NULL;
611  	
612  	    rc = run_cmdline(out, argv, &standard_out);
613  	    g_strfreev(argv);
614  	
615  	    if (rc != pcmk_rc_ok) {
616  	        goto done;
617  	    }
618  	
619  	    xml = pcmk__xml_parse(standard_out);
620  	
621  	    if (xml == NULL) {
622  	        goto done;
623  	    }
624  	
625  	    xpath = pcmk__assert_asprintf("//" PCMK_XE_ITEM "[@" PCMK_XA_NAME "='%s']",
626  	                                  param);
627  	    node = pcmk__xpath_find_one(xml->doc, xpath, LOG_DEBUG);
628  	
629  	    if (node == NULL) {
630  	        goto done;
631  	    }
632  	
633  	    content = xmlNodeGetContent(node);
634  	
635  	    if (content != NULL) {
636  	        retval = pcmk__str_copy((char *) content);
637  	        xmlFree(content);
638  	    }
639  	
640  	done:
641  	    free(standard_out);
642  	    free(xpath);
643  	    pcmk__xml_free(xml);
644  	    return retval;
645  	}
646  	
647  	static int
648  	remove_cib_param(pcmk__output_t *out, const char *rsc, const char *param)
649  	{
650  	    int rc = pcmk_rc_ok;
651  	    gchar **argv = g_new0(gchar *, 6);
652  	
653  	    if (no_cib) {
654  	        goto done;
655  	    }
656  	
657  	    argv[0] = g_strdup("crm_resource");
658  	    argv[1] = g_strdup("-r");
659  	    argv[2] = g_strdup(rsc);
660  	    argv[3] = g_strdup("-d");
661  	    argv[4] = g_strdup(param);
662  	    argv[5] = NULL;
663  	
664  	    rc = run_cmdline(out, argv, NULL);
665  	
666  	done:
667  	    g_strfreev(argv);
668  	    return rc;
669  	}
670  	
671  	static int
672  	set_cib_param(pcmk__output_t *out, const char *rsc, const char *param,
673  	              const char *value)
674  	{
675  	    int rc = pcmk_rc_ok;
676  	    gchar **argv = g_new0(gchar *, 8);
677  	
678  	    if (no_cib) {
679  	        goto done;
680  	    }
681  	
682  	    argv[0] = g_strdup("crm_resource");
683  	    argv[1] = g_strdup("-r");
684  	    argv[2] = g_strdup(rsc);
685  	    argv[3] = g_strdup("-p");
686  	    argv[4] = g_strdup(param);
687  	    argv[5] = g_strdup("-v");
688  	    argv[6] = g_strdup(value);
689  	    argv[7] = NULL;
690  	
691  	    rc = run_cmdline(out, argv, NULL);
692  	
693  	done:
694  	    g_strfreev(argv);
695  	    return rc;
696  	}
697  	
698  	static char *
699  	local_files_get(const char *rsc, const char *param)
700  	{
701  	    char *retval = NULL;
702  	    char *lf_file = NULL;
703  	    gchar *contents = NULL;
704  	
705  	    lf_file = pcmk__assert_asprintf(PCMK__CIB_SECRETS_DIR "/%s/%s", rsc, param);
706  	    if (g_file_get_contents(lf_file, &contents, NULL, NULL)) {
707  	        contents = g_strchomp(contents);
708  	        retval = pcmk__str_copy(contents);
709  	        g_free(contents);
710  	    }
711  	
712  	    free(lf_file);
713  	    return retval;
714  	}
715  	
716  	static char *
717  	local_files_getsum(const char *rsc, const char *param)
718  	{
719  	    char *retval = NULL;
720  	    char *lf_file = NULL;
721  	    gchar *contents = NULL;
722  	
723  	    lf_file = pcmk__assert_asprintf(PCMK__CIB_SECRETS_DIR "/%s/%s.sign", rsc,
724  	                                    param);
725  	    if (g_file_get_contents(lf_file, &contents, NULL, NULL)) {
726  	        contents = g_strchomp(contents);
727  	        retval = pcmk__str_copy(contents);
728  	        g_free(contents);
729  	    }
730  	
731  	    free(lf_file);
732  	    return retval;
733  	}
734  	
735  	static int
736  	local_files_remove(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn,
737  	                   const char *rsc, const char *param)
738  	{
739  	    int rc = pcmk_rc_ok;
740  	    char *lf_file = NULL;
741  	    gchar **argv = g_new0(gchar *, 5);
742  	
743  	    lf_file = pcmk__assert_asprintf(PCMK__CIB_SECRETS_DIR "/%s/%s", rsc, param);
744  	
745  	    argv[0] = g_strdup("rm");
746  	    argv[1] = g_strdup("-f");
747  	    argv[2] = g_strdup(lf_file);
748  	    argv[3] = g_strdup_printf("%s.sign", lf_file);
749  	    argv[4] = NULL;
750  	
751  	    rc = run_cmdline(out, argv, NULL);
752  	
753  	    if (rc == pcmk_rc_ok) {
754  	        rc = sync_one_file(out, rsh_fn, rcp_fn, lf_file);
755  	    }
756  	
757  	    free(lf_file);
758  	    g_strfreev(argv);
759  	    return rc;
760  	}
761  	
762  	static int
763  	local_files_set(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn,
764  	                const char *rsc, const char *param, const char *value)
765  	{
766  	    char *contents = NULL;
767  	    char *lf_dir = NULL;
768  	    char *lf_file = NULL;
769  	    char *sign_file = NULL;
770  	    char *calc_sum = NULL;
771  	    int rc = pcmk_rc_ok;
772  	
773  	    lf_dir = pcmk__assert_asprintf(PCMK__CIB_SECRETS_DIR "/%s", rsc);
774  	
775  	    if (g_mkdir_with_parents(lf_dir, 0700) != 0) {
776  	        rc = errno;
777  	        out->err(out, "Could not create directory %s: %s", lf_dir,
778  	                 pcmk_rc_str(rc));
779  	        goto done;
780  	    }
781  	
782  	    lf_file = pcmk__assert_asprintf("%s/%s", lf_dir, param);
783  	    contents = pcmk__assert_asprintf("%s\n", value);
784  	    if (!g_file_set_contents(lf_file, contents, -1, NULL)) {
785  	        rc = EIO;
786  	        out->err(out, "Could not create file %s: %s", lf_file,
787  	                 pcmk_rc_str(rc));
788  	        goto done;
789  	    }
790  	
791  	    free(contents);
792  	
793  	    sign_file = pcmk__assert_asprintf("%s/%s.sign", lf_dir, param);
794  	    calc_sum = pcmk__md5sum(value);
795  	    contents = pcmk__assert_asprintf("%s\n", calc_sum);
796  	
797  	    if (!g_file_set_contents(sign_file, contents, -1, NULL)) {
798  	        rc = EIO;
799  	        out->err(out, "Could not create file %s: %s", sign_file,
800  	                 pcmk_rc_str(rc));
801  	        goto done;
802  	    }
803  	
804  	    rc = sync_one_file(out, rsh_fn, rcp_fn, lf_file);
805  	
806  	done:
807  	    free(contents);
808  	    free(calc_sum);
809  	    free(sign_file);
810  	    free(lf_dir);
811  	    free(lf_file);
812  	    return rc;
813  	}
814  	
815  	static int
816  	subcommand_check(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
817  	{
818  	    int rc = pcmk_rc_ok;
819  	    const char *rsc = remainder[1];
820  	    const char *param = remainder[2];
821  	    char *value = NULL;
822  	    char *calc_sum = NULL;
823  	    char *local_sum = NULL;
824  	    char *local_value = NULL;
825  	
826  	    if (check_cib_rsc(out, rsc) != pcmk_rc_ok) {
827  	        rc = ENODEV;
828  	        goto done;
829  	    }
830  	
831  	    value = get_cib_param(out, rsc, param);
832  	    if ((value == NULL) || !is_secret(value)) {
833  	        out->err(out, "Resource %s parameter %s not set as secret, nothing to check",
834  	                 rsc, param);
835  	
836  	        /* I don't like this error code, but (1) it maps to CRM_EX_CONFIG which
837  	         * is what the old cibsecret.in would return in this case, and (2) we
838  	         * return it all over the place for a variety of CIB checking errors.
839  	         */
840  	        rc = pcmk_rc_unpack_error;
841  	        goto done;
842  	    }
843  	
844  	    local_sum = local_files_getsum(rsc, param);
845  	    if (local_sum == NULL) {
846  	        out->err(out, "No checksum for resource %s parameter %s", rsc, param);
847  	        rc = ENOENT;
848  	        goto done;
849  	    }
850  	
851  	    local_value = local_files_get(rsc, param);
852  	    if (local_value != NULL) {
853  	        calc_sum = pcmk__md5sum(local_value);
854  	    }
855  	
856  	    if ((local_value == NULL) || !pcmk__str_eq(calc_sum, local_sum, pcmk__str_none)) {
857  	        out->err(out, "Checksum mismatch for resource %s parameter %s", rsc, param);
858  	        rc = pcmk_rc_digest_mismatch;
859  	    }
860  	
861  	done:
862  	    free(local_sum);
863  	    free(local_value);
864  	    free(calc_sum);
865  	    free(value);
866  	    return rc;
867  	}
868  	
869  	static int
870  	subcommand_delete(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
871  	{
872  	    int rc = pcmk_rc_ok;
873  	    const char *rsc = remainder[1];
874  	    const char *param = remainder[2];
875  	
876  	    if (check_cib_rsc(out, rsc) != pcmk_rc_ok) {
877  	        return ENODEV;
878  	    }
879  	
880  	    rc = local_files_remove(out, rsh_fn, rcp_fn, rsc, param);
881  	    if (rc != pcmk_rc_ok) {
882  	        return rc;
883  	    }
884  	
885  	    return remove_cib_param(out, rsc, param);
886  	}
887  	
888  	static int
889  	subcommand_get(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
890  	{
891  	    int rc = subcommand_check(out, rsh_fn, rcp_fn);
892  	    char *value = NULL;
893  	    const char *rsc = remainder[1];
894  	    const char *param = remainder[2];
895  	
896  	    if (rc != pcmk_rc_ok) {
897  	        return rc;
898  	    }
899  	
900  	    value = local_files_get(rsc, param);
901  	    pcmk__assert(value != NULL);
902  	    out->info(out, "%s", value);
903  	
904  	    free(value);
905  	    return pcmk_rc_ok;
906  	}
907  	
908  	/* The previous shell implementation of cibsecret allowed passing the value
909  	 * to set (what would be remainder[3] here) via stdin, which we do not support
910  	 * here at the moment.
911  	 */
912  	static int
913  	subcommand_set(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
914  	{
915  	    int rc = pcmk_rc_ok;
916  	    const char *rsc = remainder[1];
917  	    const char *param = remainder[2];
918  	    const char *value = remainder[3];
919  	    char *current = NULL;
920  	
921  	    if (check_cib_rsc(out, rsc) != pcmk_rc_ok) {
922  	        rc = ENODEV;
923  	        goto done;
924  	    }
925  	
926  	    current = get_cib_param(out, rsc, param);
927  	    if ((current != NULL) && !pcmk__str_any_of(current, LRM_MAGIC, value, NULL)) {
928  	        out->err(out, "CIB value <%s> different for %s rsc parameter %s; please "
929  	                 "delete it first", current, rsc, param);
930  	
931  	        /* I don't like this error code, but (1) it maps to CRM_EX_CONFIG which
932  	         * is what the old cibsecret.in would return in this case, and (2) we
933  	         * return it all over the place for a variety of CIB checking errors.
934  	         */
935  	        rc = pcmk_rc_unpack_error;
936  	        goto done;
937  	    }
938  	
939  	    rc = local_files_set(out, rsh_fn, rcp_fn, rsc, param, value);
940  	    if (rc != pcmk_rc_ok) {
941  	        goto done;
942  	    }
943  	
944  	    rc = set_cib_param(out, rsc, param, LRM_MAGIC);
945  	
946  	done:
947  	    free(current);
948  	    return rc;
949  	}
950  	
951  	static int
952  	subcommand_stash(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
953  	{
954  	    int rc = pcmk_rc_ok;
955  	    const char *rsc = remainder[1];
956  	    const char *param = remainder[2];
957  	    char *value = NULL;
958  	
959  	    if (check_cib_rsc(out, rsc) != pcmk_rc_ok) {
960  	        rc = ENODEV;
961  	        goto done;
962  	    }
963  	
964  	    value = get_cib_param(out, rsc, param);
965  	    if ((value == NULL) || is_secret(value)) {
966  	        if (value == NULL) {
967  	            out->err(out, "Nothing to stash for resource %s parameter %s", rsc,
968  	                     param);
969  	            rc = ENOENT;
970  	        } else {
971  	            out->err(out, "Resource %s parameter %s already set as secret", rsc,
972  	                     param);
973  	            rc = EEXIST;
974  	        }
975  	
976  	        goto done;
977  	    }
978  	
979  	    remainder = g_realloc(remainder, sizeof(gchar *) * 5);
980  	    remainder[3] = g_strdup(value);
981  	    remainder[4] = NULL;
982  	
983  	    rc = subcommand_set(out, rsh_fn, rcp_fn);
984  	
985  	done:
986  	    free(value);
987  	    return rc;
988  	}
989  	
990  	static int
991  	subcommand_sync(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
992  	{
993  	    int rc = pcmk_rc_ok;
994  	    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]
995  	    gchar **argv = g_new0(gchar *, 4);
996  	    gchar **peers = get_live_peers(out);
997  	    gchar *peer_str = NULL;
998  	
(7) Event path: Condition "peers == NULL", taking true branch.
999  	    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]
1000 	        return pcmk_rc_ok;
1001 	    }
1002 	
1003 	    peer_str = g_strjoinv(" ", peers);
1004 	
1005 	    out->info(out, "Syncing %s to %s ...", PCMK__CIB_SECRETS_DIR, peer_str);
1006 	    g_free(peer_str);
1007 	
1008 	    dirname = g_path_get_dirname(PCMK__CIB_SECRETS_DIR);
1009 	
1010 	    argv[0] = g_strdup("rm");
1011 	    argv[1] = g_strdup("-rf");
1012 	    argv[2] = g_strdup(PCMK__CIB_SECRETS_DIR);
1013 	    argv[3] = NULL;
1014 	
1015 	    rc = rsh_fn(out, peers, argv);
1016 	    if (rc != pcmk_rc_ok) {
1017 	        goto done;
1018 	    }
1019 	
1020 	    g_strfreev(argv);
1021 	    argv = g_new0(gchar *, 4);
1022 	
1023 	    argv[0] = g_strdup("mkdir");
1024 	    argv[1] = g_strdup("-p");
1025 	    argv[2] = g_strdup(dirname);
1026 	    argv[3] = NULL;
1027 	
1028 	    rc = rsh_fn(out, peers, argv);
1029 	    if (rc != pcmk_rc_ok) {
1030 	        goto done;
1031 	    }
1032 	
1033 	    rc = rcp_fn(out, peers, dirname, PCMK__CIB_SECRETS_DIR);
1034 	
1035 	done:
1036 	    g_strfreev(argv);
1037 	    g_strfreev(peers);
1038 	    g_free(dirname);
1039 	    return rc;
1040 	}
1041 	
1042 	static int
1043 	subcommand_unstash(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
1044 	{
1045 	    int rc = pcmk_rc_ok;
1046 	    const char *rsc = remainder[1];
1047 	    const char *param = remainder[2];
1048 	    char *local_value = NULL;
1049 	    char *cib_value = NULL;
1050 	
1051 	    local_value = local_files_get(rsc, param);
1052 	    if (local_value == NULL) {
1053 	        out->err(out, "Nothing to unstash for resource %s parameter %s",
1054 	                 rsc, param);
1055 	        rc = ENOENT;
1056 	        goto done;
1057 	    }
1058 	
1059 	    if (check_cib_rsc(out, rsc) != pcmk_rc_ok) {
1060 	        rc = ENODEV;
1061 	        goto done;
1062 	    }
1063 	
1064 	    cib_value = get_cib_param(out, rsc, param);
1065 	    if (!is_secret(cib_value)) {
1066 	        out->info(out, "Resource %s parameter %s is not set as secret, but we "
1067 	                  "have a local value so proceeding anyway", rsc, param);
1068 	    }
1069 	
1070 	    rc = local_files_remove(out, rsh_fn, rcp_fn, rsc, param);
1071 	    if (rc != pcmk_rc_ok) {
1072 	        goto done;
1073 	    }
1074 	
1075 	    rc = set_cib_param(out, rsc, param, local_value);
1076 	
1077 	done:
1078 	    free(cib_value);
1079 	    free(local_value);
1080 	    return rc;
1081 	}
1082 	
1083 	static struct subcommand_entry subcommand_table[] = {
1084 	    { "check", 2, "check <resource-id> <resource-parameter>", false,
1085 	      subcommand_check },
1086 	    { "delete", 2, "delete <resource-id> <resource-parameter>", false,
1087 	      subcommand_delete },
1088 	    { "get", 2, "get <resource-id> <resource-parameter>", false,
1089 	      subcommand_get },
1090 	    { "set", 3, "set <resource-id> <resource-parameter> <value>", false,
1091 	      subcommand_set },
1092 	    { "stash", 2, "stash <resource-id> <resource-parameter>", true,
1093 	      subcommand_stash },
1094 	    { "sync", 0, "sync", false, subcommand_sync },
1095 	    { "unstash", 2, "unstash <resource-id> <resource-parameter>", true,
1096 	      subcommand_unstash },
1097 	    { NULL },
1098 	};
1099 	
1100 	static bool
1101 	tools_installed(pcmk__output_t *out, rsh_fn_t *rsh_fn, rcp_fn_t *rcp_fn,
1102 	                GError **error)
1103 	{
1104 	    gchar *path = NULL;
1105 	
1106 	    path = g_find_program_in_path("pssh");
1107 	    if (path != NULL) {
1108 	        g_free(path);
1109 	        *rsh_fn = pssh;
1110 	        *rcp_fn = pscp;
1111 	        return true;
1112 	    }
1113 	
1114 	    path = g_find_program_in_path("pdsh");
1115 	    if (path != NULL) {
1116 	        g_free(path);
1117 	        *rsh_fn = pdsh;
1118 	        *rcp_fn = pdcp;
1119 	        return true;
1120 	    }
1121 	
1122 	    path = g_find_program_in_path("ssh");
1123 	    if (path != NULL) {
1124 	        g_free(path);
1125 	        *rsh_fn = ssh;
1126 	        *rcp_fn = scp;
1127 	        return true;
1128 	    }
1129 	
1130 	    out->err(out, "Please install one of pssh, pdsh, or ssh");
1131 	    return false;
1132 	}
1133 	
1134 	static pcmk__supported_format_t formats[] = {
1135 	    PCMK__SUPPORTED_FORMAT_NONE,
1136 	    PCMK__SUPPORTED_FORMAT_TEXT,
1137 	    PCMK__SUPPORTED_FORMAT_XML,
1138 	    { NULL, NULL, NULL }
1139 	};
1140 	
1141 	static GOptionContext *
1142 	build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
1143 	    const char *desc = NULL;
1144 	    GOptionContext *context = NULL;
1145 	
1146 	    desc = "This command manages sensitive resource parameter values that should not be\n"
1147 	           "stored directly in Pacemaker's Cluster Information Base (CIB). Such values\n"
1148 	           "are handled by storing a special string directly in the CIB that tells\n"
1149 	           "Pacemaker to look in a separate, protected file for the actual value.\n\n"
1150 	
1151 	           "The secret files are not encrypted, but protected by file system permissions\n"
1152 	           "such that only root can read or modify them.\n\n"
1153 	
1154 	           "Since the secret files are stored locally, they must be synchronized across all\n"
1155 	           "cluster nodes. This command handles the synchronization using (in order of\n"
1156 	           "preference) pssh, pdsh, or ssh, so one of those must be installed. Before\n"
1157 	           "synchronizing, this command will ping the cluster nodes to determine which are\n"
1158 	           "alive, using fping if it is installed, otherwise the ping command. Installing\n"
1159 	           "fping is strongly recommended for better performance.\n\n"
1160 	
1161 	           "Commands and their parameters:\n\n"
1162 	           "check <resource-id> <resource-parameter>\n"
1163 	           "\tVerify that the locally stored value of a sensitive resource parameter\n"
1164 	           "\tmatches its locally stored MD5 hash.\n\n"
1165 	           "delete <resource-id> <resource-parameter>\n"
1166 	           "\tRemove a sensitive resource parameter value.\n\n"
1167 	           "get <resource-id> <resource-parameter>\n"
1168 	           "\tDisplay the locally stored value of a sensitive resource parameter.\n\n"
1169 	           "set <resource-id> <resource-parameter> <value>\n"
1170 	           "\tSet the value of a sensitive resource parameter.\n\n"
1171 	           "stash <resource-id> <resource-parameter>\n"
1172 	           "\tMake a non-sensitive resource parameter that is already in the CIB\n"
1173 	           "\tsensitive (move its value to a locally stored and protected file).\n"
1174 	           "\tThis may not be used with -C.\n\n"
1175 	           "sync\n"
1176 	           "\tCopy all locally stored secrets to all other nodes.\n\n"
1177 	           "unstash <resource-id> <resource-parameter>\n"
1178 	           "\tMake a sensitive resource parameter that is already in the CIB\n"
1179 	           "\tnon-sensitive (move its value from the locally stored file to the CIB).\n"
1180 	           "\tThis may not be used with -C.\n\n\n"
1181 	
1182 	           "Known limitations:\n\n"
1183 	
1184 	           "This command can only be run from full cluster nodes (not Pacemaker Remote\n"
1185 	           "nodes).\n\n"
1186 	
1187 	           "Changes are not atomic, so the cluster may use different values while a\n"
1188 	           "change is in progress. To avoid problems, it is recommended to put the\n"
1189 	           "cluster in maintenance mode when making changes with this command.\n\n"
1190 	
1191 	           "Changes in secret values do not trigger an agent reload or restart of the\n"
1192 	           "affected resource, since they do not change the CIB. If a response is\n"
1193 	           "desired before the next cluster recheck interval, any CIB change (such as\n"
1194 	           "setting a node attribute) will trigger it.\n\n"
1195 	
1196 	           "If any node is down when changes to secrets are made, or a new node is\n"
1197 	           "later added to the cluster, it may have different values when it joins the\n"
1198 	           "cluster, before 'cibsecret sync' is run. To avoid this, it is recommended to\n"
1199 	           "run the sync command (from another node) before starting Pacemaker on the\n"
1200 	           "node.\n\n"
1201 	
1202 	           "Examples:\n\n"
1203 	
1204 	           "# cibsecret set ipmi_node1 passwd SecreT_PASS\n\n"
1205 	           "# cibsecret get ipmi_node1 passwd\n\n"
1206 	           "# cibsecret check ipmi_node1 passwd\n\n"
1207 	           "# cibsecret stash ipmi_node2 passwd\n\n"
1208 	           "# cibsecret sync\n";
1209 	
1210 	    context = pcmk__build_arg_context(args, "text (default), xml", group,
1211 	                                      "<subcommand> [options]");
1212 	    g_option_context_set_description(context, desc);
1213 	    pcmk__add_main_args(context, entries);
1214 	    return context;
1215 	}
1216 	
1217 	int
1218 	main(int argc, char **argv)
1219 	{
1220 	    crm_exit_t exit_code = CRM_EX_OK;
1221 	    int rc = pcmk_rc_ok;
1222 	
1223 	    pcmk__output_t *out = NULL;
1224 	
1225 	    GError *error = NULL;
1226 	
1227 	    GOptionGroup *output_group = NULL;
1228 	    pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
1229 	    gchar **processed_args = pcmk__cmdline_preproc(argv, NULL);
1230 	    GOptionContext *context = build_arg_context(args, &output_group);
1231 	
1232 	    struct subcommand_entry cmd;
1233 	    rsh_fn_t rsh_fn;
1234 	    rcp_fn_t rcp_fn;
1235 	
1236 	    // Load locale information for the local host from the environment
1237 	    setlocale(LC_ALL, "");
1238 	
1239 	    pcmk__register_formats(output_group, formats);
1240 	    if (!g_option_context_parse_strv(context, &processed_args, &error)) {
1241 	        exit_code = CRM_EX_USAGE;
1242 	        goto done;
1243 	    }
1244 	
1245 	    pcmk__cli_init_logging("cibsecret", args->verbosity);
1246 	
1247 	    rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
1248 	    if (rc != pcmk_rc_ok) {
1249 	        exit_code = CRM_EX_ERROR;
1250 	        g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
1251 	                    "Error creating output format %s: %s", args->output_ty,
1252 	                    pcmk_rc_str(rc));
1253 	        goto done;
1254 	    }
1255 	
1256 	    if (args->version) {
1257 	        out->version(out);
1258 	        goto done;
1259 	    }
1260 	
1261 	    /* No subcommand was given */
1262 	    if ((remainder == NULL) || (g_strv_length(remainder) == 0)) {
1263 	        gchar *help = g_option_context_get_help(context, TRUE, NULL);
1264 	
1265 	        exit_code = CRM_EX_USAGE;
1266 	        g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
1267 	                    "Must specify a command option\n\n%s", help);
1268 	        g_free(help);
1269 	        goto done;
1270 	    }
1271 	
1272 	    /* Traverse the subcommand table looking for a match. */
1273 	    for (int i = 0; i < PCMK__NELEM(subcommand_table); i++) {
1274 	        cmd = subcommand_table[i];
1275 	
1276 	        if (!pcmk__str_eq(remainder[0], cmd.name, pcmk__str_none)) {
1277 	            continue;
1278 	        }
1279 	
1280 	        /* We found a match.  Check that enough arguments were given and
1281 	         * display a usage message if not.  The "+ 1" is because the table
1282 	         * entry lists how many arguments the subcommand takes, which does not
1283 	         * include the subcommand itself.
1284 	         */
1285 	        if (g_strv_length(remainder) != cmd.args + 1) {
1286 	            exit_code = CRM_EX_USAGE;
1287 	            g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Usage: %s",
1288 	                        cmd.usage);
1289 	            goto done;
1290 	        }
1291 	
1292 	        /* We've found the subcommand handler and it's used correctly. */
1293 	        break;
1294 	    }
1295 	
1296 	    /* If we didn't find a match, a valid subcommand wasn't given. */
1297 	    if (cmd.name == NULL) {
1298 	        exit_code = CRM_EX_USAGE;
1299 	        g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
1300 	                    "Invalid subcommand given; valid subcommands: "
1301 	                    "check, delete, get, set, stash, sync, unstash");
1302 	        goto done;
1303 	    }
1304 	
1305 	    /* Check that we have the tools necessary to manage secrets */
1306 	    if (!tools_installed(out, &rsh_fn, &rcp_fn, &error)) {
1307 	        exit_code = CRM_EX_NOT_INSTALLED;
1308 	        goto done;
1309 	    }
1310 	
1311 	    /* Set a default umask so files we create are only accessible by the
1312 	     * cluster user.
1313 	     */
1314 	    umask(S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH);
1315 	
1316 	    /* Call the subcommand handler.  If the handler fails, it will have already
1317 	     * set exit_code to the reason why so there's no need to worry with
1318 	     * additional error checking here at the moment.
1319 	     */
1320 	    if (cmd.requires_cib && no_cib) {
1321 	        out->err(out, "No access to Pacemaker, %s not supported", cmd.name);
1322 	        exit_code = CRM_EX_USAGE;
1323 	        goto done;
1324 	    }
1325 	
1326 	    rc = cmd.handler(out, rsh_fn, rcp_fn);
1327 	    exit_code = pcmk_rc2exitc(rc);
1328 	
1329 	 done:
1330 	    g_strfreev(processed_args);
1331 	    g_strfreev(remainder);
1332 	    pcmk__free_arg_context(context);
1333 	
1334 	    pcmk__output_and_clear_error(&error, out);
1335 	
1336 	    if (out != NULL) {
1337 	        out->finish(out, exit_code, true, NULL);
1338 	        pcmk__output_free(out);
1339 	    }
1340 	    pcmk__unregister_formats();
1341 	    crm_exit(exit_code);
1342 	}
1343