]>
Commit | Line | Data |
---|---|---|
0e7afb18 PH |
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 | |
48425792 | 16 | * along with this program; if not, see <http://www.gnu.org/licenses/>. |
0e7afb18 PH |
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> | |
0e7afb18 | 27 | #include <stdlib.h> |
ff55c47d | 28 | #include <glib.h> |
0e7afb18 | 29 | #include <gnome-keyring.h> |
5a3db110 BC |
30 | |
31 | #ifdef GNOME_KEYRING_DEFAULT | |
32 | ||
33 | /* Modern gnome-keyring */ | |
34 | ||
9fe3e6cf | 35 | #include <gnome-keyring-memory.h> |
0e7afb18 | 36 | |
5a3db110 BC |
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 | ||
0162b3c4 | 62 | static const char *gnome_keyring_result_to_message(GnomeKeyringResult result) |
5a3db110 BC |
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 | ||
15f72216 BC |
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 | { | |
0162b3c4 JS |
97 | gpointer *data = (gpointer *)user_data; |
98 | int *done = (int *)data[0]; | |
99 | GnomeKeyringResult *r = (GnomeKeyringResult *)data[1]; | |
15f72216 BC |
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 | |
5a3db110 BC |
127 | #endif |
128 | ||
0e7afb18 PH |
129 | /* |
130 | * This credential struct and API is simplified from git's credential.{h,c} | |
131 | */ | |
0162b3c4 JS |
132 | struct credential { |
133 | char *protocol; | |
134 | char *host; | |
0e7afb18 | 135 | unsigned short port; |
0162b3c4 JS |
136 | char *path; |
137 | char *username; | |
138 | char *password; | |
0e7afb18 PH |
139 | }; |
140 | ||
9865b6e6 | 141 | #define CREDENTIAL_INIT { 0 } |
0e7afb18 | 142 | |
0162b3c4 | 143 | typedef int (*credential_op_cb)(struct credential *); |
0e7afb18 | 144 | |
0162b3c4 JS |
145 | struct credential_operation { |
146 | char *name; | |
0e7afb18 PH |
147 | credential_op_cb op; |
148 | }; | |
149 | ||
0162b3c4 | 150 | #define CREDENTIAL_OP_END { NULL, NULL } |
0e7afb18 | 151 | |
0e7afb18 PH |
152 | /* ----------------- GNOME Keyring functions ----------------- */ |
153 | ||
154 | /* create a special keyring option string, if path is given */ | |
0162b3c4 | 155 | static char *keyring_object(struct credential *c) |
0e7afb18 | 156 | { |
0e7afb18 | 157 | if (!c->path) |
8bb7a54c | 158 | return NULL; |
0e7afb18 | 159 | |
4bc47cc0 | 160 | if (c->port) |
8bb7a54c | 161 | return g_strdup_printf("%s:%hd/%s", c->host, c->port, c->path); |
0e7afb18 | 162 | |
8bb7a54c | 163 | return g_strdup_printf("%s/%s", c->host, c->path); |
0e7afb18 PH |
164 | } |
165 | ||
18fe5add | 166 | static int keyring_get(struct credential *c) |
0e7afb18 | 167 | { |
0162b3c4 | 168 | char *object = NULL; |
0e7afb18 PH |
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 | ||
68a65f5f | 188 | g_free(object); |
0e7afb18 PH |
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) { | |
3006297a | 197 | g_critical("%s", gnome_keyring_result_to_message(result)); |
0e7afb18 PH |
198 | return EXIT_FAILURE; |
199 | } | |
200 | ||
201 | /* pick the first one from the list */ | |
0162b3c4 | 202 | password_data = (GnomeKeyringNetworkPasswordData *)entries->data; |
0e7afb18 | 203 | |
9fe3e6cf BC |
204 | gnome_keyring_memory_free(c->password); |
205 | c->password = gnome_keyring_memory_strdup(password_data->password); | |
0e7afb18 PH |
206 | |
207 | if (!c->username) | |
68a65f5f | 208 | c->username = g_strdup(password_data->user); |
0e7afb18 PH |
209 | |
210 | gnome_keyring_network_password_list_free(entries); | |
211 | ||
212 | return EXIT_SUCCESS; | |
213 | } | |
214 | ||
215 | ||
18fe5add | 216 | static int keyring_store(struct credential *c) |
0e7afb18 PH |
217 | { |
218 | guint32 item_id; | |
0162b3c4 | 219 | char *object = NULL; |
81c57e2c | 220 | GnomeKeyringResult result; |
0e7afb18 PH |
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 | ||
81c57e2c | 235 | result = gnome_keyring_set_network_password_sync( |
0e7afb18 PH |
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 | ||
68a65f5f | 247 | g_free(object); |
81c57e2c BC |
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 | ||
0e7afb18 PH |
255 | return EXIT_SUCCESS; |
256 | } | |
257 | ||
18fe5add | 258 | static int keyring_erase(struct credential *c) |
0e7afb18 | 259 | { |
0162b3c4 | 260 | char *object = NULL; |
0e7afb18 PH |
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 | ||
68a65f5f | 288 | g_free(object); |
0e7afb18 PH |
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 | ||
0162b3c4 | 296 | if (result != GNOME_KEYRING_RESULT_OK) { |
3006297a | 297 | g_critical("%s", gnome_keyring_result_to_message(result)); |
0e7afb18 PH |
298 | return EXIT_FAILURE; |
299 | } | |
300 | ||
301 | /* pick the first one from the list (delete all matches?) */ | |
0162b3c4 | 302 | password_data = (GnomeKeyringNetworkPasswordData *)entries->data; |
0e7afb18 PH |
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 | ||
0162b3c4 | 309 | if (result != GNOME_KEYRING_RESULT_OK) { |
3006297a | 310 | g_critical("%s", gnome_keyring_result_to_message(result)); |
0e7afb18 PH |
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 | */ | |
0162b3c4 JS |
321 | static struct credential_operation const credential_helper_ops[] = { |
322 | { "get", keyring_get }, | |
0e7afb18 PH |
323 | { "store", keyring_store }, |
324 | { "erase", keyring_erase }, | |
325 | CREDENTIAL_OP_END | |
326 | }; | |
327 | ||
328 | /* ------------------ credential functions ------------------ */ | |
329 | ||
18fe5add | 330 | static void credential_init(struct credential *c) |
0e7afb18 PH |
331 | { |
332 | memset(c, 0, sizeof(*c)); | |
333 | } | |
334 | ||
18fe5add | 335 | static void credential_clear(struct credential *c) |
0e7afb18 | 336 | { |
68a65f5f BC |
337 | g_free(c->protocol); |
338 | g_free(c->host); | |
339 | g_free(c->path); | |
340 | g_free(c->username); | |
9fe3e6cf | 341 | gnome_keyring_memory_free(c->password); |
0e7afb18 PH |
342 | |
343 | credential_init(c); | |
344 | } | |
345 | ||
18fe5add | 346 | static int credential_read(struct credential *c) |
0e7afb18 | 347 | { |
0162b3c4 | 348 | char *buf; |
fb276374 | 349 | size_t line_len; |
0162b3c4 JS |
350 | char *key; |
351 | char *value; | |
0e7afb18 | 352 | |
da2727f2 BC |
353 | key = buf = gnome_keyring_memory_alloc(1024); |
354 | ||
0162b3c4 | 355 | while (fgets(buf, 1024, stdin)) { |
0e7afb18 PH |
356 | line_len = strlen(buf); |
357 | ||
73bbc079 | 358 | if (line_len && buf[line_len-1] == '\n') |
0162b3c4 | 359 | buf[--line_len] = '\0'; |
0e7afb18 | 360 | |
4bc47cc0 | 361 | if (!line_len) |
0e7afb18 PH |
362 | break; |
363 | ||
0162b3c4 | 364 | value = strchr(buf, '='); |
4bc47cc0 | 365 | if (!value) { |
3006297a | 366 | g_warning("invalid credential line: %s", key); |
da2727f2 | 367 | gnome_keyring_memory_free(buf); |
0e7afb18 PH |
368 | return -1; |
369 | } | |
370 | *value++ = '\0'; | |
371 | ||
372 | if (!strcmp(key, "protocol")) { | |
68a65f5f BC |
373 | g_free(c->protocol); |
374 | c->protocol = g_strdup(value); | |
0e7afb18 | 375 | } else if (!strcmp(key, "host")) { |
68a65f5f BC |
376 | g_free(c->host); |
377 | c->host = g_strdup(value); | |
0162b3c4 | 378 | value = strrchr(c->host, ':'); |
0e7afb18 PH |
379 | if (value) { |
380 | *value++ = '\0'; | |
381 | c->port = atoi(value); | |
382 | } | |
383 | } else if (!strcmp(key, "path")) { | |
68a65f5f BC |
384 | g_free(c->path); |
385 | c->path = g_strdup(value); | |
0e7afb18 | 386 | } else if (!strcmp(key, "username")) { |
68a65f5f BC |
387 | g_free(c->username); |
388 | c->username = g_strdup(value); | |
0e7afb18 | 389 | } else if (!strcmp(key, "password")) { |
9fe3e6cf BC |
390 | gnome_keyring_memory_free(c->password); |
391 | c->password = gnome_keyring_memory_strdup(value); | |
0162b3c4 JS |
392 | while (*value) |
393 | *value++ = '\0'; | |
0e7afb18 PH |
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 | } | |
da2727f2 BC |
401 | |
402 | gnome_keyring_memory_free(buf); | |
403 | ||
0e7afb18 PH |
404 | return 0; |
405 | } | |
406 | ||
18fe5add | 407 | static void credential_write_item(FILE *fp, const char *key, const char *value) |
0e7afb18 PH |
408 | { |
409 | if (!value) | |
410 | return; | |
411 | fprintf(fp, "%s=%s\n", key, value); | |
412 | } | |
413 | ||
18fe5add | 414 | static void credential_write(const struct credential *c) |
0e7afb18 PH |
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; | |
0162b3c4 | 424 | const char *basename = strrchr(name, '/'); |
0e7afb18 PH |
425 | |
426 | basename = (basename) ? basename + 1 : name; | |
c358ed75 | 427 | fprintf(stderr, "usage: %s <", basename); |
4bc47cc0 | 428 | while (try_op->name) { |
0162b3c4 | 429 | fprintf(stderr, "%s", (try_op++)->name); |
4bc47cc0 | 430 | if (try_op->name) |
0162b3c4 | 431 | fprintf(stderr, "%s", "|"); |
0e7afb18 | 432 | } |
0162b3c4 | 433 | fprintf(stderr, "%s", ">\n"); |
0e7afb18 PH |
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; | |
0162b3c4 | 441 | struct credential cred = CREDENTIAL_INIT; |
0e7afb18 PH |
442 | |
443 | if (!argv[1]) { | |
444 | usage(argv[0]); | |
7a6d6423 | 445 | exit(EXIT_FAILURE); |
0e7afb18 PH |
446 | } |
447 | ||
ff55c47d BC |
448 | g_set_application_name("Git Credential Helper"); |
449 | ||
0e7afb18 | 450 | /* lookup operation callback */ |
4bc47cc0 | 451 | while (try_op->name && strcmp(argv[1], try_op->name)) |
0e7afb18 PH |
452 | try_op++; |
453 | ||
454 | /* unsupported operation given -- ignore silently */ | |
4bc47cc0 | 455 | if (!try_op->name || !try_op->op) |
0e7afb18 PH |
456 | goto out; |
457 | ||
458 | ret = credential_read(&cred); | |
4bc47cc0 | 459 | if (ret) |
0e7afb18 PH |
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 | } |