Add secret-tool command
[platform/upstream/libsecret.git] / tool / secret-tool.c
1 /* libsecret - GLib wrapper for Secret Service
2  *
3  * Copyright 2012 Red Hat Inc.
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published
7  * by the Free Software Foundation; either version 2 of the licence or (at
8  * your option) any later version.
9  *
10  * See the included COPYING file for more information.
11  *
12  * Author: Stef Walter <stefw@gnome.org>
13  */
14
15 #include "config.h"
16
17 #include "secret-password.h"
18 #include "secret-service.h"
19 #include "secret-value.h"
20
21 #include <glib/gi18n.h>
22
23 #include <errno.h>
24 #include <limits.h>
25 #include <stdlib.h>
26
27 #define SECRET_ALIAS_PREFIX "/org/freedesktop/secrets/aliases/"
28
29 static gchar **attribute_args = NULL;
30 static gchar *store_label = NULL;
31 static gchar *store_collection = NULL;
32
33 /* secret-tool store --label="blah" --collection="xxxx" name:xxxx name:yyyy */
34 static const GOptionEntry STORE_OPTIONS[] = {
35         { "label", 'l', 0, G_OPTION_ARG_STRING, &store_label,
36           N_("the label for the new stored item"), NULL },
37         { "collection", 'c', 0, G_OPTION_ARG_STRING, &store_collection,
38           N_("the collection in which to place the stored item"), NULL },
39         { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &attribute_args,
40           N_("attribute value pairs of item to lookup"), NULL },
41         { NULL }
42 };
43
44 /* secret-tool lookup name:xxxx yyyy:zzzz */
45 static const GOptionEntry LOOKUP_OPTIONS[] = {
46         { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &attribute_args,
47           N_("attribute value pairs of item to lookup"), NULL },
48         { NULL }
49 };
50
51 /* secret-tool remove name:xxxx yyyy:zzzz */
52 static const GOptionEntry REMOVE_OPTIONS[] = {
53         { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &attribute_args,
54           N_("attribute value pairs which match item to remove"), NULL },
55         { NULL }
56 };
57
58 typedef int       (* SecretToolAction)          (int argc, char *argv[]);
59
60 static void       usage                         (void) G_GNUC_NORETURN;
61
62 static void
63 usage (void)
64 {
65         g_printerr ("usage: secret-tool store --label='label' attribute value ...\n");
66         g_printerr ("       secret-tool lookup attribute value ...\n");
67         g_printerr ("       secret-tool remove attribute value ...\n");
68         exit (2);
69 }
70
71 static gboolean
72 is_password_value (SecretValue *value)
73 {
74         const gchar *content_type;
75         const gchar *data;
76         gsize length;
77
78         content_type = secret_value_get_content_type (value);
79         if (content_type && g_str_equal (content_type, "text/plain"))
80                 return TRUE;
81
82         data = secret_value_get (value, &length);
83         /* gnome-keyring-daemon used to return passwords like this, so support this, but validate */
84         if (!content_type || g_str_equal (content_type, "application/octet-stream"))
85                 return g_utf8_validate (data, length, NULL);
86
87         return FALSE;
88 }
89
90 static GHashTable *
91 attributes_from_arguments (gchar **args)
92 {
93         GHashTable *attributes;
94
95         if (args == NULL || args[0] == NULL) {
96                 g_printerr ("%s: must specfy attribute and value pairs\n", g_get_prgname ());
97                 usage ();
98         }
99
100         attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
101
102         while (args[0] != NULL) {
103                 if (args[1] == NULL) {
104                         g_printerr ("%s: must specfy attributes and values in pairs\n", g_get_prgname ());
105                         usage ();
106                 }
107
108                 g_hash_table_insert (attributes, g_strdup (args[0]), g_strdup (args[1]));
109                 args += 2;
110         }
111
112         return attributes;
113 }
114
115 static int
116 secret_tool_action_remove (int argc,
117                            char *argv[])
118 {
119         GError *error = NULL;
120         GOptionContext *context;
121         SecretService *service;
122         GHashTable *attributes;
123
124         context = g_option_context_new ("attribute value ...");
125         g_option_context_add_main_entries (context, REMOVE_OPTIONS, GETTEXT_PACKAGE);
126         if (!g_option_context_parse (context, &argc, &argv, &error)) {
127                 g_printerr ("%s\n", error->message);
128                 usage();
129         }
130
131         g_option_context_free (context);
132
133         attributes = attributes_from_arguments (attribute_args);
134         g_strfreev (attribute_args);
135
136         service = secret_service_get_sync (SECRET_SERVICE_NONE, NULL, &error);
137         if (error == NULL)
138                 secret_service_removev_sync (service, NULL, attributes, NULL, &error);
139
140         g_object_unref (service);
141         g_hash_table_unref (attributes);
142
143         if (error != NULL) {
144                 g_printerr ("%s: %s\n", g_get_prgname (), error->message);
145                 return 1;
146         }
147
148         return 0;
149 }
150
151 static void
152 write_password_stdout (SecretValue *value)
153 {
154         const gchar *at;
155         gsize length;
156         int r;
157
158         if (!is_password_value (value)) {
159                 g_printerr ("%s: secret does not contain a textual password\n", g_get_prgname ());
160                 exit (1);
161         }
162
163         at = secret_value_get (value, &length);
164
165         while (length > 0) {
166                 r = write (1, at, length);
167                 if (r == -1) {
168                         if (errno != EAGAIN && errno != EINTR) {
169                                 g_printerr ("%s: couldn't write password: %s\n",
170                                             g_get_prgname (), g_strerror (errno));
171                                 exit (1);
172                         }
173                 } else {
174                         at += r;
175                         length -= r;
176                 }
177         }
178
179         /* Add a new line if we're writing out to a tty */
180         if (isatty (1))
181                 write (1, "\n", 1);
182 }
183
184 static int
185 secret_tool_action_lookup (int argc,
186                            char *argv[])
187 {
188         GError *error = NULL;
189         GOptionContext *context;
190         SecretService *service;
191         GHashTable *attributes;
192         SecretValue *value;
193
194         context = g_option_context_new ("attribute value ...");
195         g_option_context_add_main_entries (context, LOOKUP_OPTIONS, GETTEXT_PACKAGE);
196         if (!g_option_context_parse (context, &argc, &argv, &error)) {
197                 g_printerr ("%s\n", error->message);
198                 usage();
199         }
200
201         g_option_context_free (context);
202
203         attributes = attributes_from_arguments (attribute_args);
204         g_strfreev (attribute_args);
205
206         service = secret_service_get_sync (SECRET_SERVICE_NONE, NULL, &error);
207         if (error == NULL)
208                 value = secret_service_lookupv_sync (service, NULL, attributes, NULL, &error);
209
210         g_object_unref (service);
211         g_hash_table_unref (attributes);
212
213         if (error != NULL) {
214                 g_printerr ("%s: %s\n", g_get_prgname (), error->message);
215                 return 1;
216         }
217
218         if (value == NULL)
219                 return 1;
220
221         write_password_stdout (value);
222         secret_value_unref (value);
223         return 0;
224 }
225
226 static SecretValue *
227 read_password_stdin (void)
228 {
229         gchar *password;
230         gchar *at;
231         gsize length = 0;
232         gsize remaining = 8192;
233         int r;
234
235         at = password = g_malloc0 (remaining + 1);
236
237         for (;;) {
238                 r = read (0, at, remaining);
239                 if (r == 0) {
240                         break;
241                 } else if (r < 0) {
242                         if (errno != EAGAIN && errno != EINTR) {
243                                 g_printerr ("%s: couldn't read password: %s\n",
244                                             g_get_prgname (), g_strerror (errno));
245                                 exit (1);
246                         }
247                 } else {
248                         /* TODO: This restriction is due purely to laziness. */
249                         if (r == remaining)
250                                 g_printerr ("%s: password is too long\n", g_get_prgname ());
251                         at += r;
252                         remaining -= r;
253                         length += r;
254                 }
255         }
256
257         /* TODO: Verify that the password really is utf-8 text. */
258         return secret_value_new_full (password, length, "text/plain",
259                                       (GDestroyNotify)secret_password_free);
260 }
261
262 static SecretValue *
263 read_password_tty (void)
264 {
265         gchar *password;
266
267         password = getpass ("Password: ");
268         return secret_value_new_full (password, -1, "text/plain",
269                                       (GDestroyNotify)secret_password_clear);
270 }
271
272 static int
273 secret_tool_action_store (int argc,
274                           char *argv[])
275 {
276         GError *error = NULL;
277         GOptionContext *context;
278         SecretService *service;
279         GHashTable *attributes;
280         SecretValue *value;
281         gchar *collection = NULL;
282
283         context = g_option_context_new ("attribute value ...");
284         g_option_context_add_main_entries (context, STORE_OPTIONS, GETTEXT_PACKAGE);
285         if (!g_option_context_parse (context, &argc, &argv, &error)) {
286                 g_printerr ("%s\n", error->message);
287                 usage();
288         }
289
290         g_option_context_free (context);
291
292         if (store_label == NULL) {
293                 g_printerr ("%s: must specify a label for the new item\n", g_get_prgname ());
294                 usage ();
295         }
296
297         attributes = attributes_from_arguments (attribute_args);
298         g_strfreev (attribute_args);
299
300         if (store_collection) {
301                 /* TODO: Verify that the collection is a valid path or path element */
302                 if (g_str_has_prefix (store_collection, "/"))
303                         collection = g_strdup (store_collection);
304                 else
305                         collection = g_strconcat (SECRET_ALIAS_PREFIX, store_collection, NULL);
306         }
307
308         service = secret_service_get_sync (SECRET_SERVICE_NONE, NULL, &error);
309         if (error == NULL) {
310                 if (isatty (0))
311                         value = read_password_tty ();
312                 else
313                         value = read_password_stdin ();
314
315                 secret_service_storev_sync (service, NULL, attributes, collection, store_label, value, NULL, &error);
316                 secret_value_unref (value);
317         }
318
319         g_object_unref (service);
320         g_hash_table_unref (attributes);
321         g_free (store_label);
322         g_free (store_collection);
323         g_free (collection);
324
325         if (error != NULL) {
326                 g_printerr ("%s: %s\n", g_get_prgname (), error->message);
327                 return 1;
328         }
329
330         return 0;
331 }
332
333 int
334 main (int argc,
335       char *argv[])
336 {
337         SecretToolAction action;
338
339         g_type_init ();
340
341         if (argc < 2)
342                 usage();
343
344         if (g_str_equal (argv[1], "store")) {
345                 action = secret_tool_action_store;
346         } else if (g_str_equal (argv[1], "lookup")) {
347                 action = secret_tool_action_lookup;
348         } else if (g_str_equal (argv[1], "remove")) {
349                 action = secret_tool_action_remove;
350         } else {
351                 usage ();
352         }
353
354         argv[1] = argv[0];
355         return (action) (argc - 1, argv + 1);
356 }