1
-/*
2
- * Copyright (C) 2011 John Szakmeister <john@szakmeister.net>
3
- * 2012 Philipp A. Hartmann <pah@qo.cx>
4
- *
5
- * This program is free software; you can redistribute it and/or modify
6
- * it under the terms of the GNU General Public License as published by
7
- * the Free Software Foundation; either version 2 of the License, or
8
- * (at your option) any later version.
9
- *
10
- * This program is distributed in the hope that it will be useful,
11
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
- * GNU General Public License for more details.
14
- *
15
- * You should have received a copy of the GNU General Public License
16
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
17
- */
18
-
19
-/*
20
- * Credits:
21
- * - GNOME Keyring API handling originally written by John Szakmeister
22
- * - ported to credential helper API by Philipp A. Hartmann
23
- */
24
-
25
-#include <stdio.h>
26
-#include <string.h>
27
-#include <stdlib.h>
28
-#include <glib.h>
29
-#include <gnome-keyring.h>
30
-
31
-#ifdef GNOME_KEYRING_DEFAULT
32
-
33
- /* Modern gnome-keyring */
34
-
35
-#include <gnome-keyring-memory.h>
36
-
37
-#else
38
-
39
- /*
40
- * Support ancient gnome-keyring, circ. RHEL 5.X.
41
- * GNOME_KEYRING_DEFAULT seems to have been introduced with Gnome 2.22,
42
- * and the other features roughly around Gnome 2.20, 6 months before.
43
- * Ubuntu 8.04 used Gnome 2.22 (I think). Not sure any distro used 2.20.
44
- * So the existence/non-existence of GNOME_KEYRING_DEFAULT seems like
45
- * a decent thing to use as an indicator.
46
- */
47
-
48
-#define GNOME_KEYRING_DEFAULT NULL
49
-
50
-/*
51
- * ancient gnome-keyring returns DENIED when an entry is not found.
52
- * Setting NO_MATCH to DENIED will prevent us from reporting DENIED
53
- * errors during get and erase operations, but we will still report
54
- * DENIED errors during a store.
55
- */
56
-#define GNOME_KEYRING_RESULT_NO_MATCH GNOME_KEYRING_RESULT_DENIED
57
-
58
-#define gnome_keyring_memory_alloc g_malloc
59
-#define gnome_keyring_memory_free gnome_keyring_free_password
60
-#define gnome_keyring_memory_strdup g_strdup
61
-
62
-static const char *gnome_keyring_result_to_message(GnomeKeyringResult result)
63
-{
64
- switch (result) {
65
- case GNOME_KEYRING_RESULT_OK:
66
- return "OK";
67
- case GNOME_KEYRING_RESULT_DENIED:
68
- return "Denied";
69
- case GNOME_KEYRING_RESULT_NO_KEYRING_DAEMON:
70
- return "No Keyring Daemon";
71
- case GNOME_KEYRING_RESULT_ALREADY_UNLOCKED:
72
- return "Already UnLocked";
73
- case GNOME_KEYRING_RESULT_NO_SUCH_KEYRING:
74
- return "No Such Keyring";
75
- case GNOME_KEYRING_RESULT_BAD_ARGUMENTS:
76
- return "Bad Arguments";
77
- case GNOME_KEYRING_RESULT_IO_ERROR:
78
- return "IO Error";
79
- case GNOME_KEYRING_RESULT_CANCELLED:
80
- return "Cancelled";
81
- case GNOME_KEYRING_RESULT_ALREADY_EXISTS:
82
- return "Already Exists";
83
- default:
84
- return "Unknown Error";
85
- }
86
-}
87
-
88
-/*
89
- * Support really ancient gnome-keyring, circ. RHEL 4.X.
90
- * Just a guess for the Glib version. Glib 2.8 was roughly Gnome 2.12 ?
91
- * Which was released with gnome-keyring 0.4.3 ??
92
- */
93
-#if GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 8
94
-
95
-static void gnome_keyring_done_cb(GnomeKeyringResult result, gpointer user_data)
96
-{
97
- gpointer *data = (gpointer *)user_data;
98
- int *done = (int *)data[0];
99
- GnomeKeyringResult *r = (GnomeKeyringResult *)data[1];
100
-
101
- *r = result;
102
- *done = 1;
103
-}
104
-
105
-static void wait_for_request_completion(int *done)
106
-{
107
- GMainContext *mc = g_main_context_default();
108
- while (!*done)
109
- g_main_context_iteration(mc, TRUE);
110
-}
111
-
112
-static GnomeKeyringResult gnome_keyring_item_delete_sync(const char *keyring, guint32 id)
113
-{
114
- int done = 0;
115
- GnomeKeyringResult result;
116
- gpointer data[] = { &done, &result };
117
-
118
- gnome_keyring_item_delete(keyring, id, gnome_keyring_done_cb, data,
119
- NULL);
120
-
121
- wait_for_request_completion(&done);
122
-
123
- return result;
124
-}
125
-
126
-#endif
127
-#endif
128
-
129
-/*
130
- * This credential struct and API is simplified from git's credential.{h,c}
131
- */
132
-struct credential {
133
- char *protocol;
134
- char *host;
135
- unsigned short port;
136
- char *path;
137
- char *username;
138
- char *password;
139
-};
140
-
141
-#define CREDENTIAL_INIT { 0 }
142
-
143
-typedef int (*credential_op_cb)(struct credential *);
144
-
145
-struct credential_operation {
146
- char *name;
147
- credential_op_cb op;
148
-};
149
-
150
-#define CREDENTIAL_OP_END { NULL, NULL }
151
-
152
-/* ----------------- GNOME Keyring functions ----------------- */
153
-
154
-/* create a special keyring option string, if path is given */
155
-static char *keyring_object(struct credential *c)
156
-{
157
- if (!c->path)
158
- return NULL;
159
-
160
- if (c->port)
161
- return g_strdup_printf("%s:%hd/%s", c->host, c->port, c->path);
162
-
163
- return g_strdup_printf("%s/%s", c->host, c->path);
164
-}
165
-
166
-static int keyring_get(struct credential *c)
167
-{
168
- char *object = NULL;
169
- GList *entries;
170
- GnomeKeyringNetworkPasswordData *password_data;
171
- GnomeKeyringResult result;
172
-
173
- if (!c->protocol || !(c->host || c->path))
174
- return EXIT_FAILURE;
175
-
176
- object = keyring_object(c);
177
-
178
- result = gnome_keyring_find_network_password_sync(
179
- c->username,
180
- NULL /* domain */,
181
- c->host,
182
- object,
183
- c->protocol,
184
- NULL /* authtype */,
185
- c->port,
186
- &entries);
187
-
188
- g_free(object);
189
-
190
- if (result == GNOME_KEYRING_RESULT_NO_MATCH)
191
- return EXIT_SUCCESS;
192
-
193
- if (result == GNOME_KEYRING_RESULT_CANCELLED)
194
- return EXIT_SUCCESS;
195
-
196
- if (result != GNOME_KEYRING_RESULT_OK) {
197
- g_critical("%s", gnome_keyring_result_to_message(result));
198
- return EXIT_FAILURE;
199
- }
200
-
201
- /* pick the first one from the list */
202
- password_data = (GnomeKeyringNetworkPasswordData *)entries->data;
203
-
204
- gnome_keyring_memory_free(c->password);
205
- c->password = gnome_keyring_memory_strdup(password_data->password);
206
-
207
- if (!c->username)
208
- c->username = g_strdup(password_data->user);
209
-
210
- gnome_keyring_network_password_list_free(entries);
211
-
212
- return EXIT_SUCCESS;
213
-}
214
-
215
-
216
-static int keyring_store(struct credential *c)
217
-{
218
- guint32 item_id;
219
- char *object = NULL;
220
- GnomeKeyringResult result;
221
-
222
- /*
223
- * Sanity check that what we are storing is actually sensible.
224
- * In particular, we can't make a URL without a protocol field.
225
- * Without either a host or pathname (depending on the scheme),
226
- * we have no primary key. And without a username and password,
227
- * we are not actually storing a credential.
228
- */
229
- if (!c->protocol || !(c->host || c->path) ||
230
- !c->username || !c->password)
231
- return EXIT_FAILURE;
232
-
233
- object = keyring_object(c);
234
-
235
- result = gnome_keyring_set_network_password_sync(
236
- GNOME_KEYRING_DEFAULT,
237
- c->username,
238
- NULL /* domain */,
239
- c->host,
240
- object,
241
- c->protocol,
242
- NULL /* authtype */,
243
- c->port,
244
- c->password,
245
- &item_id);
246
-
247
- g_free(object);
248
-
249
- if (result != GNOME_KEYRING_RESULT_OK &&
250
- result != GNOME_KEYRING_RESULT_CANCELLED) {
251
- g_critical("%s", gnome_keyring_result_to_message(result));
252
- return EXIT_FAILURE;
253
- }
254
-
255
- return EXIT_SUCCESS;
256
-}
257
-
258
-static int keyring_erase(struct credential *c)
259
-{
260
- char *object = NULL;
261
- GList *entries;
262
- GnomeKeyringNetworkPasswordData *password_data;
263
- GnomeKeyringResult result;
264
-
265
- /*
266
- * Sanity check that we actually have something to match
267
- * against. The input we get is a restrictive pattern,
268
- * so technically a blank credential means "erase everything".
269
- * But it is too easy to accidentally send this, since it is equivalent
270
- * to empty input. So explicitly disallow it, and require that the
271
- * pattern have some actual content to match.
272
- */
273
- if (!c->protocol && !c->host && !c->path && !c->username)
274
- return EXIT_FAILURE;
275
-
276
- object = keyring_object(c);
277
-
278
- result = gnome_keyring_find_network_password_sync(
279
- c->username,
280
- NULL /* domain */,
281
- c->host,
282
- object,
283
- c->protocol,
284
- NULL /* authtype */,
285
- c->port,
286
- &entries);
287
-
288
- g_free(object);
289
-
290
- if (result == GNOME_KEYRING_RESULT_NO_MATCH)
291
- return EXIT_SUCCESS;
292
-
293
- if (result == GNOME_KEYRING_RESULT_CANCELLED)
294
- return EXIT_SUCCESS;
295
-
296
- if (result != GNOME_KEYRING_RESULT_OK) {
297
- g_critical("%s", gnome_keyring_result_to_message(result));
298
- return EXIT_FAILURE;
299
- }
300
-
301
- /* pick the first one from the list (delete all matches?) */
302
- password_data = (GnomeKeyringNetworkPasswordData *)entries->data;
303
-
304
- result = gnome_keyring_item_delete_sync(
305
- password_data->keyring, password_data->item_id);
306
-
307
- gnome_keyring_network_password_list_free(entries);
308
-
309
- if (result != GNOME_KEYRING_RESULT_OK) {
310
- g_critical("%s", gnome_keyring_result_to_message(result));
311
- return EXIT_FAILURE;
312
- }
313
-
314
- return EXIT_SUCCESS;
315
-}
316
-
317
-/*
318
- * Table with helper operation callbacks, used by generic
319
- * credential helper main function.
320
- */
321
-static struct credential_operation const credential_helper_ops[] = {
322
- { "get", keyring_get },
323
- { "store", keyring_store },
324
- { "erase", keyring_erase },
325
- CREDENTIAL_OP_END
326
-};
327
-
328
-/* ------------------ credential functions ------------------ */
329
-
330
-static void credential_init(struct credential *c)
331
-{
332
- memset(c, 0, sizeof(*c));
333
-}
334
-
335
-static void credential_clear(struct credential *c)
336
-{
337
- g_free(c->protocol);
338
- g_free(c->host);
339
- g_free(c->path);
340
- g_free(c->username);
341
- gnome_keyring_memory_free(c->password);
342
-
343
- credential_init(c);
344
-}
345
-
346
-static int credential_read(struct credential *c)
347
-{
348
- char *buf;
349
- size_t line_len;
350
- char *key;
351
- char *value;
352
-
353
- key = buf = gnome_keyring_memory_alloc(1024);
354
-
355
- while (fgets(buf, 1024, stdin)) {
356
- line_len = strlen(buf);
357
-
358
- if (line_len && buf[line_len-1] == '\n')
359
- buf[--line_len] = '\0';
360
-
361
- if (!line_len)
362
- break;
363
-
364
- value = strchr(buf, '=');
365
- if (!value) {
366
- g_warning("invalid credential line: %s", key);
367
- gnome_keyring_memory_free(buf);
368
- return -1;
369
- }
370
- *value++ = '\0';
371
-
372
- if (!strcmp(key, "protocol")) {
373
- g_free(c->protocol);
374
- c->protocol = g_strdup(value);
375
- } else if (!strcmp(key, "host")) {
376
- g_free(c->host);
377
- c->host = g_strdup(value);
378
- value = strrchr(c->host, ':');
379
- if (value) {
380
- *value++ = '\0';
381
- c->port = atoi(value);
382
- }
383
- } else if (!strcmp(key, "path")) {
384
- g_free(c->path);
385
- c->path = g_strdup(value);
386
- } else if (!strcmp(key, "username")) {
387
- g_free(c->username);
388
- c->username = g_strdup(value);
389
- } else if (!strcmp(key, "password")) {
390
- gnome_keyring_memory_free(c->password);
391
- c->password = gnome_keyring_memory_strdup(value);
392
- while (*value)
393
- *value++ = '\0';
394
- }
395
- /*
396
- * Ignore other lines; we don't know what they mean, but
397
- * this future-proofs us when later versions of git do
398
- * learn new lines, and the helpers are updated to match.
399
- */
400
- }
401
-
402
- gnome_keyring_memory_free(buf);
403
-
404
- return 0;
405
-}
406
-
407
-static void credential_write_item(FILE *fp, const char *key, const char *value)
408
-{
409
- if (!value)
410
- return;
411
- fprintf(fp, "%s=%s\n", key, value);
412
-}
413
-
414
-static void credential_write(const struct credential *c)
415
-{
416
- /* only write username/password, if set */
417
- credential_write_item(stdout, "username", c->username);
418
- credential_write_item(stdout, "password", c->password);
419
-}
420
-
421
-static void usage(const char *name)
422
-{
423
- struct credential_operation const *try_op = credential_helper_ops;
424
- const char *basename = strrchr(name, '/');
425
-
426
- basename = (basename) ? basename + 1 : name;
427
- fprintf(stderr, "usage: %s <", basename);
428
- while (try_op->name) {
429
- fprintf(stderr, "%s", (try_op++)->name);
430
- if (try_op->name)
431
- fprintf(stderr, "%s", "|");
432
- }
433
- fprintf(stderr, "%s", ">\n");
434
-}
435
-
436
-int main(int argc, char *argv[])
437
-{
438
- int ret = EXIT_SUCCESS;
439
-
440
- struct credential_operation const *try_op = credential_helper_ops;
441
- struct credential cred = CREDENTIAL_INIT;
442
-
443
- if (!argv[1]) {
444
- usage(argv[0]);
445
- exit(EXIT_FAILURE);
446
- }
447
-
448
- g_set_application_name("Git Credential Helper");
449
-
450
- /* lookup operation callback */
451
- while (try_op->name && strcmp(argv[1], try_op->name))
452
- try_op++;
453
-
454
- /* unsupported operation given -- ignore silently */
455
- if (!try_op->name || !try_op->op)
456
- goto out;
457
-
458
- ret = credential_read(&cred);
459
- if (ret)
460
- goto out;
461
-
462
- /* perform credential operation */
463
- ret = (*try_op->op)(&cred);
464
-
465
- credential_write(&cred);
466
-
467
-out:
468
- credential_clear(&cred);
469
- return ret;
470
-}