contrib: add credential helper for libsecret
This is based on the existing gnome-keyring helper, but instead of libgnome-keyring (which was specific to GNOME and is deprecated), it uses libsecret which can support other implementations of XDG Secret Service API. Passes t0303-credential-external.sh. Signed-off-by: Mantas Mikulėnas <grawity@gmail.com> Reviewed-by: Dennis Kaarsemaker <dennis@kaarsemaker.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Mantas Mikulėnas committed
Oct 9, 2016 at 15:34 UTC
87d1353a6a133aafd2ef29ba6c740a8d3d71dadb
2 files changed
+395
contrib/credential/libsecret/Makefile
new
+25
@@ -0,0 +1,25 @@
1
+MAIN:=git-credential-libsecret
2
+all:: $(MAIN)
3
+
4
+CC = gcc
5
+RM = rm -f
6
+CFLAGS = -g -O2 -Wall
7
+PKG_CONFIG = pkg-config
8
+
9
+-include ../../../config.mak.autogen
10
+-include ../../../config.mak
11
+
12
+INCS:=$(shell $(PKG_CONFIG) --cflags libsecret-1 glib-2.0)
13
+LIBS:=$(shell $(PKG_CONFIG) --libs libsecret-1 glib-2.0)
14
+
15
+SRCS:=$(MAIN).c
16
+OBJS:=$(SRCS:.c=.o)
17
+
18
+%.o: %.c
19
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(INCS) -o $@ -c $<
20
+
21
+$(MAIN): $(OBJS)
22
+ $(CC) -o $@ $(LDFLAGS) $^ $(LIBS)
23
+
24
+clean:
25
+ @$(RM) $(MAIN) $(OBJS)
contrib/credential/libsecret/git-credential-libsecret.c
new
+370
@@ -0,0 +1,370 @@
1
+/*
2
+ * Copyright (C) 2011 John Szakmeister <john@szakmeister.net>
3
+ * 2012 Philipp A. Hartmann <pah@qo.cx>
4
+ * 2016 Mantas Mikulėnas <grawity@gmail.com>
5
+ *
6
+ * This program is free software; you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation; either version 2 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with this program; if not, write to the Free Software
18
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
+ */
20
+
21
+/*
22
+ * Credits:
23
+ * - GNOME Keyring API handling originally written by John Szakmeister
24
+ * - ported to credential helper API by Philipp A. Hartmann
25
+ */
26
+
27
+#include <stdio.h>
28
+#include <string.h>
29
+#include <stdlib.h>
30
+#include <glib.h>
31
+#include <libsecret/secret.h>
32
+
33
+/*
34
+ * This credential struct and API is simplified from git's credential.{h,c}
35
+ */
36
+struct credential {
37
+ char *protocol;
38
+ char *host;
39
+ unsigned short port;
40
+ char *path;
41
+ char *username;
42
+ char *password;
43
+};
44
+
45
+#define CREDENTIAL_INIT { NULL, NULL, 0, NULL, NULL, NULL }
46
+
47
+typedef int (*credential_op_cb)(struct credential *);
48
+
49
+struct credential_operation {
50
+ char *name;
51
+ credential_op_cb op;
52
+};
53
+
54
+#define CREDENTIAL_OP_END { NULL, NULL }
55
+
56
+/* ----------------- Secret Service functions ----------------- */
57
+
58
+static char *make_label(struct credential *c)
59
+{
60
+ if (c->port)
61
+ return g_strdup_printf("Git: %s://%s:%hu/%s",
62
+ c->protocol, c->host, c->port, c->path ? c->path : "");
63
+ else
64
+ return g_strdup_printf("Git: %s://%s/%s",
65
+ c->protocol, c->host, c->path ? c->path : "");
66
+}
67
+
68
+static GHashTable *make_attr_list(struct credential *c)
69
+{
70
+ GHashTable *al = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
71
+
72
+ if (c->username)
73
+ g_hash_table_insert(al, "user", g_strdup(c->username));
74
+ if (c->protocol)
75
+ g_hash_table_insert(al, "protocol", g_strdup(c->protocol));
76
+ if (c->host)
77
+ g_hash_table_insert(al, "server", g_strdup(c->host));
78
+ if (c->port)
79
+ g_hash_table_insert(al, "port", g_strdup_printf("%hu", c->port));
80
+ if (c->path)
81
+ g_hash_table_insert(al, "object", g_strdup(c->path));
82
+
83
+ return al;
84
+}
85
+
86
+static int keyring_get(struct credential *c)
87
+{
88
+ SecretService *service = NULL;
89
+ GHashTable *attributes = NULL;
90
+ GError *error = NULL;
91
+ GList *items = NULL;
92
+
93
+ if (!c->protocol || !(c->host || c->path))
94
+ return EXIT_FAILURE;
95
+
96
+ service = secret_service_get_sync(0, NULL, &error);
97
+ if (error != NULL) {
98
+ g_critical("could not connect to Secret Service: %s", error->message);
99
+ g_error_free(error);
100
+ return EXIT_FAILURE;
101
+ }
102
+
103
+ attributes = make_attr_list(c);
104
+ items = secret_service_search_sync(service,
105
+ SECRET_SCHEMA_COMPAT_NETWORK,
106
+ attributes,
107
+ SECRET_SEARCH_LOAD_SECRETS,
108
+ NULL,
109
+ &error);
110
+ g_hash_table_unref(attributes);
111
+ if (error != NULL) {
112
+ g_critical("lookup failed: %s", error->message);
113
+ g_error_free(error);
114
+ return EXIT_FAILURE;
115
+ }
116
+
117
+ if (items != NULL) {
118
+ SecretItem *item;
119
+ SecretValue *secret;
120
+ const char *s;
121
+
122
+ item = items->data;
123
+ secret = secret_item_get_secret(item);
124
+ attributes = secret_item_get_attributes(item);
125
+
126
+ s = g_hash_table_lookup(attributes, "user");
127
+ if (s) {
128
+ g_free(c->username);
129
+ c->username = g_strdup(s);
130
+ }
131
+
132
+ s = secret_value_get_text(secret);
133
+ if (s) {
134
+ g_free(c->password);
135
+ c->password = g_strdup(s);
136
+ }
137
+
138
+ g_hash_table_unref(attributes);
139
+ secret_value_unref(secret);
140
+ g_list_free_full(items, g_object_unref);
141
+ }
142
+
143
+ return EXIT_SUCCESS;
144
+}
145
+
146
+
147
+static int keyring_store(struct credential *c)
148
+{
149
+ char *label = NULL;
150
+ GHashTable *attributes = NULL;
151
+ GError *error = NULL;
152
+
153
+ /*
154
+ * Sanity check that what we are storing is actually sensible.
155
+ * In particular, we can't make a URL without a protocol field.
156
+ * Without either a host or pathname (depending on the scheme),
157
+ * we have no primary key. And without a username and password,
158
+ * we are not actually storing a credential.
159
+ */
160
+ if (!c->protocol || !(c->host || c->path) ||
161
+ !c->username || !c->password)
162
+ return EXIT_FAILURE;
163
+
164
+ label = make_label(c);
165
+ attributes = make_attr_list(c);
166
+ secret_password_storev_sync(SECRET_SCHEMA_COMPAT_NETWORK,
167
+ attributes,
168
+ NULL,
169
+ label,
170
+ c->password,
171
+ NULL,
172
+ &error);
173
+ g_free(label);
174
+ g_hash_table_unref(attributes);
175
+
176
+ if (error != NULL) {
177
+ g_critical("store failed: %s", error->message);
178
+ g_error_free(error);
179
+ return EXIT_FAILURE;
180
+ }
181
+
182
+ return EXIT_SUCCESS;
183
+}
184
+
185
+static int keyring_erase(struct credential *c)
186
+{
187
+ GHashTable *attributes = NULL;
188
+ GError *error = NULL;
189
+
190
+ /*
191
+ * Sanity check that we actually have something to match
192
+ * against. The input we get is a restrictive pattern,
193
+ * so technically a blank credential means "erase everything".
194
+ * But it is too easy to accidentally send this, since it is equivalent
195
+ * to empty input. So explicitly disallow it, and require that the
196
+ * pattern have some actual content to match.
197
+ */
198
+ if (!c->protocol && !c->host && !c->path && !c->username)
199
+ return EXIT_FAILURE;
200
+
201
+ attributes = make_attr_list(c);
202
+ secret_password_clearv_sync(SECRET_SCHEMA_COMPAT_NETWORK,
203
+ attributes,
204
+ NULL,
205
+ &error);
206
+ g_hash_table_unref(attributes);
207
+
208
+ if (error != NULL) {
209
+ g_critical("erase failed: %s", error->message);
210
+ g_error_free(error);
211
+ return EXIT_FAILURE;
212
+ }
213
+
214
+ return EXIT_SUCCESS;
215
+}
216
+
217
+/*
218
+ * Table with helper operation callbacks, used by generic
219
+ * credential helper main function.
220
+ */
221
+static struct credential_operation const credential_helper_ops[] = {
222
+ { "get", keyring_get },
223
+ { "store", keyring_store },
224
+ { "erase", keyring_erase },
225
+ CREDENTIAL_OP_END
226
+};
227
+
228
+/* ------------------ credential functions ------------------ */
229
+
230
+static void credential_init(struct credential *c)
231
+{
232
+ memset(c, 0, sizeof(*c));
233
+}
234
+
235
+static void credential_clear(struct credential *c)
236
+{
237
+ g_free(c->protocol);
238
+ g_free(c->host);
239
+ g_free(c->path);
240
+ g_free(c->username);
241
+ g_free(c->password);
242
+
243
+ credential_init(c);
244
+}
245
+
246
+static int credential_read(struct credential *c)
247
+{
248
+ char *buf;
249
+ size_t line_len;
250
+ char *key;
251
+ char *value;
252
+
253
+ key = buf = g_malloc(1024);
254
+
255
+ while (fgets(buf, 1024, stdin)) {
256
+ line_len = strlen(buf);
257
+
258
+ if (line_len && buf[line_len-1] == '\n')
259
+ buf[--line_len] = '\0';
260
+
261
+ if (!line_len)
262
+ break;
263
+
264
+ value = strchr(buf, '=');
265
+ if (!value) {
266
+ g_warning("invalid credential line: %s", key);
267
+ g_free(buf);
268
+ return -1;
269
+ }
270
+ *value++ = '\0';
271
+
272
+ if (!strcmp(key, "protocol")) {
273
+ g_free(c->protocol);
274
+ c->protocol = g_strdup(value);
275
+ } else if (!strcmp(key, "host")) {
276
+ g_free(c->host);
277
+ c->host = g_strdup(value);
278
+ value = strrchr(c->host, ':');
279
+ if (value) {
280
+ *value++ = '\0';
281
+ c->port = atoi(value);
282
+ }
283
+ } else if (!strcmp(key, "path")) {
284
+ g_free(c->path);
285
+ c->path = g_strdup(value);
286
+ } else if (!strcmp(key, "username")) {
287
+ g_free(c->username);
288
+ c->username = g_strdup(value);
289
+ } else if (!strcmp(key, "password")) {
290
+ g_free(c->password);
291
+ c->password = g_strdup(value);
292
+ while (*value)
293
+ *value++ = '\0';
294
+ }
295
+ /*
296
+ * Ignore other lines; we don't know what they mean, but
297
+ * this future-proofs us when later versions of git do
298
+ * learn new lines, and the helpers are updated to match.
299
+ */
300
+ }
301
+
302
+ g_free(buf);
303
+
304
+ return 0;
305
+}
306
+
307
+static void credential_write_item(FILE *fp, const char *key, const char *value)
308
+{
309
+ if (!value)
310
+ return;
311
+ fprintf(fp, "%s=%s\n", key, value);
312
+}
313
+
314
+static void credential_write(const struct credential *c)
315
+{
316
+ /* only write username/password, if set */
317
+ credential_write_item(stdout, "username", c->username);
318
+ credential_write_item(stdout, "password", c->password);
319
+}
320
+
321
+static void usage(const char *name)
322
+{
323
+ struct credential_operation const *try_op = credential_helper_ops;
324
+ const char *basename = strrchr(name, '/');
325
+
326
+ basename = (basename) ? basename + 1 : name;
327
+ fprintf(stderr, "usage: %s <", basename);
328
+ while (try_op->name) {
329
+ fprintf(stderr, "%s", (try_op++)->name);
330
+ if (try_op->name)
331
+ fprintf(stderr, "%s", "|");
332
+ }
333
+ fprintf(stderr, "%s", ">\n");
334
+}
335
+
336
+int main(int argc, char *argv[])
337
+{
338
+ int ret = EXIT_SUCCESS;
339
+
340
+ struct credential_operation const *try_op = credential_helper_ops;
341
+ struct credential cred = CREDENTIAL_INIT;
342
+
343
+ if (!argv[1]) {
344
+ usage(argv[0]);
345
+ exit(EXIT_FAILURE);
346
+ }
347
+
348
+ g_set_application_name("Git Credential Helper");
349
+
350
+ /* lookup operation callback */
351
+ while (try_op->name && strcmp(argv[1], try_op->name))
352
+ try_op++;
353
+
354
+ /* unsupported operation given -- ignore silently */
355
+ if (!try_op->name || !try_op->op)
356
+ goto out;
357
+
358
+ ret = credential_read(&cred);
359
+ if (ret)
360
+ goto out;
361
+
362
+ /* perform credential operation */
363
+ ret = (*try_op->op)(&cred);
364
+
365
+ credential_write(&cred);
366
+
367
+out:
368
+ credential_clear(&cred);
369
+ return ret;
370
+}