]> git.ipfire.org Git - thirdparty/git.git/blame - contrib/credential/libsecret/git-credential-libsecret.c
Merge branch 'bb/unicode-width-table-15'
[thirdparty/git.git] / contrib / credential / libsecret / git-credential-libsecret.c
CommitLineData
87d1353a
MM
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
48425792 17 * along with this program; if not, see <http://www.gnu.org/licenses/>.
87d1353a
MM
18 */
19
20/*
21 * Credits:
22 * - GNOME Keyring API handling originally written by John Szakmeister
23 * - ported to credential helper API by Philipp A. Hartmann
24 */
25
26#include <stdio.h>
27#include <string.h>
28#include <stdlib.h>
29#include <glib.h>
30#include <libsecret/secret.h>
31
32/*
33 * This credential struct and API is simplified from git's credential.{h,c}
34 */
35struct credential {
36 char *protocol;
37 char *host;
38 unsigned short port;
39 char *path;
40 char *username;
41 char *password;
0ce02e2f
H
42 char *password_expiry_utc;
43 char *oauth_refresh_token;
87d1353a
MM
44};
45
9865b6e6 46#define CREDENTIAL_INIT { 0 }
87d1353a
MM
47
48typedef int (*credential_op_cb)(struct credential *);
49
50struct credential_operation {
51 char *name;
52 credential_op_cb op;
53};
54
55#define CREDENTIAL_OP_END { NULL, NULL }
56
7144dee3
H
57static void credential_clear(struct credential *c);
58
87d1353a
MM
59/* ----------------- Secret Service functions ----------------- */
60
0ce02e2f
H
61static const SecretSchema schema = {
62 "org.git.Password",
63 /* Ignore schema name during search for backwards compatibility */
64 SECRET_SCHEMA_DONT_MATCH_NAME,
65 {
66 /*
67 * libsecret assumes attribute values are non-confidential and
68 * unchanging, so we can't include oauth_refresh_token or
69 * password_expiry_utc.
70 */
71 { "user", SECRET_SCHEMA_ATTRIBUTE_STRING },
72 { "object", SECRET_SCHEMA_ATTRIBUTE_STRING },
73 { "protocol", SECRET_SCHEMA_ATTRIBUTE_STRING },
74 { "port", SECRET_SCHEMA_ATTRIBUTE_INTEGER },
75 { "server", SECRET_SCHEMA_ATTRIBUTE_STRING },
76 { NULL, 0 },
77 }
78};
79
87d1353a
MM
80static char *make_label(struct credential *c)
81{
82 if (c->port)
83 return g_strdup_printf("Git: %s://%s:%hu/%s",
84 c->protocol, c->host, c->port, c->path ? c->path : "");
85 else
86 return g_strdup_printf("Git: %s://%s/%s",
87 c->protocol, c->host, c->path ? c->path : "");
88}
89
90static GHashTable *make_attr_list(struct credential *c)
91{
92 GHashTable *al = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
93
94 if (c->username)
95 g_hash_table_insert(al, "user", g_strdup(c->username));
96 if (c->protocol)
97 g_hash_table_insert(al, "protocol", g_strdup(c->protocol));
98 if (c->host)
99 g_hash_table_insert(al, "server", g_strdup(c->host));
100 if (c->port)
101 g_hash_table_insert(al, "port", g_strdup_printf("%hu", c->port));
102 if (c->path)
103 g_hash_table_insert(al, "object", g_strdup(c->path));
104
105 return al;
106}
107
108static int keyring_get(struct credential *c)
109{
110 SecretService *service = NULL;
111 GHashTable *attributes = NULL;
112 GError *error = NULL;
113 GList *items = NULL;
114
115 if (!c->protocol || !(c->host || c->path))
116 return EXIT_FAILURE;
117
118 service = secret_service_get_sync(0, NULL, &error);
119 if (error != NULL) {
120 g_critical("could not connect to Secret Service: %s", error->message);
121 g_error_free(error);
122 return EXIT_FAILURE;
123 }
124
125 attributes = make_attr_list(c);
126 items = secret_service_search_sync(service,
0ce02e2f 127 &schema,
87d1353a 128 attributes,
9c109e9b 129 SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_UNLOCK,
87d1353a
MM
130 NULL,
131 &error);
132 g_hash_table_unref(attributes);
133 if (error != NULL) {
134 g_critical("lookup failed: %s", error->message);
135 g_error_free(error);
136 return EXIT_FAILURE;
137 }
138
139 if (items != NULL) {
140 SecretItem *item;
141 SecretValue *secret;
142 const char *s;
0ce02e2f 143 gchar **parts;
87d1353a
MM
144
145 item = items->data;
146 secret = secret_item_get_secret(item);
147 attributes = secret_item_get_attributes(item);
148
149 s = g_hash_table_lookup(attributes, "user");
150 if (s) {
151 g_free(c->username);
152 c->username = g_strdup(s);
153 }
154
155 s = secret_value_get_text(secret);
156 if (s) {
0ce02e2f
H
157 /*
158 * Passwords and other attributes encoded in following format:
159 * hunter2
160 * password_expiry_utc=1684189401
161 * oauth_refresh_token=xyzzy
162 */
163 parts = g_strsplit(s, "\n", 0);
164 if (g_strv_length(parts) >= 1) {
165 g_free(c->password);
166 c->password = g_strdup(parts[0]);
167 }
168 for (int i = 1; i < g_strv_length(parts); i++) {
169 if (g_str_has_prefix(parts[i], "password_expiry_utc=")) {
170 g_free(c->password_expiry_utc);
171 c->password_expiry_utc = g_strdup(&parts[i][20]);
172 } else if (g_str_has_prefix(parts[i], "oauth_refresh_token=")) {
173 g_free(c->oauth_refresh_token);
174 c->oauth_refresh_token = g_strdup(&parts[i][20]);
175 }
176 }
177 g_strfreev(parts);
87d1353a
MM
178 }
179
180 g_hash_table_unref(attributes);
181 secret_value_unref(secret);
182 g_list_free_full(items, g_object_unref);
183 }
184
185 return EXIT_SUCCESS;
186}
187
188
189static int keyring_store(struct credential *c)
190{
191 char *label = NULL;
192 GHashTable *attributes = NULL;
193 GError *error = NULL;
0ce02e2f 194 GString *secret = NULL;
87d1353a
MM
195
196 /*
197 * Sanity check that what we are storing is actually sensible.
198 * In particular, we can't make a URL without a protocol field.
199 * Without either a host or pathname (depending on the scheme),
200 * we have no primary key. And without a username and password,
201 * we are not actually storing a credential.
202 */
203 if (!c->protocol || !(c->host || c->path) ||
204 !c->username || !c->password)
205 return EXIT_FAILURE;
206
207 label = make_label(c);
208 attributes = make_attr_list(c);
0ce02e2f
H
209 secret = g_string_new(c->password);
210 if (c->password_expiry_utc) {
211 g_string_append_printf(secret, "\npassword_expiry_utc=%s",
212 c->password_expiry_utc);
213 }
214 if (c->oauth_refresh_token) {
215 g_string_append_printf(secret, "\noauth_refresh_token=%s",
216 c->oauth_refresh_token);
217 }
218 secret_password_storev_sync(&schema,
87d1353a
MM
219 attributes,
220 NULL,
221 label,
0ce02e2f 222 secret->str,
87d1353a
MM
223 NULL,
224 &error);
0ce02e2f 225 g_string_free(secret, TRUE);
87d1353a
MM
226 g_free(label);
227 g_hash_table_unref(attributes);
228
229 if (error != NULL) {
230 g_critical("store failed: %s", error->message);
231 g_error_free(error);
232 return EXIT_FAILURE;
233 }
234
235 return EXIT_SUCCESS;
236}
237
238static int keyring_erase(struct credential *c)
239{
240 GHashTable *attributes = NULL;
241 GError *error = NULL;
7144dee3 242 struct credential existing = CREDENTIAL_INIT;
87d1353a
MM
243
244 /*
245 * Sanity check that we actually have something to match
246 * against. The input we get is a restrictive pattern,
247 * so technically a blank credential means "erase everything".
248 * But it is too easy to accidentally send this, since it is equivalent
249 * to empty input. So explicitly disallow it, and require that the
250 * pattern have some actual content to match.
251 */
252 if (!c->protocol && !c->host && !c->path && !c->username)
253 return EXIT_FAILURE;
254
7144dee3
H
255 if (c->password) {
256 existing.host = g_strdup(c->host);
257 existing.path = g_strdup(c->path);
258 existing.port = c->port;
259 existing.protocol = g_strdup(c->protocol);
260 existing.username = g_strdup(c->username);
261 keyring_get(&existing);
262 if (existing.password && strcmp(c->password, existing.password)) {
263 credential_clear(&existing);
264 return EXIT_SUCCESS;
265 }
266 credential_clear(&existing);
267 }
268
87d1353a 269 attributes = make_attr_list(c);
0ce02e2f 270 secret_password_clearv_sync(&schema,
87d1353a
MM
271 attributes,
272 NULL,
273 &error);
274 g_hash_table_unref(attributes);
275
276 if (error != NULL) {
277 g_critical("erase failed: %s", error->message);
278 g_error_free(error);
279 return EXIT_FAILURE;
280 }
281
282 return EXIT_SUCCESS;
283}
284
285/*
286 * Table with helper operation callbacks, used by generic
287 * credential helper main function.
288 */
289static struct credential_operation const credential_helper_ops[] = {
290 { "get", keyring_get },
291 { "store", keyring_store },
292 { "erase", keyring_erase },
293 CREDENTIAL_OP_END
294};
295
296/* ------------------ credential functions ------------------ */
297
298static void credential_init(struct credential *c)
299{
300 memset(c, 0, sizeof(*c));
301}
302
303static void credential_clear(struct credential *c)
304{
305 g_free(c->protocol);
306 g_free(c->host);
307 g_free(c->path);
308 g_free(c->username);
309 g_free(c->password);
0ce02e2f
H
310 g_free(c->password_expiry_utc);
311 g_free(c->oauth_refresh_token);
87d1353a
MM
312
313 credential_init(c);
314}
315
316static int credential_read(struct credential *c)
317{
64f1e658
TB
318 char *buf = NULL;
319 size_t alloc;
320 ssize_t line_len;
87d1353a
MM
321 char *key;
322 char *value;
323
64f1e658
TB
324 while ((line_len = getline(&buf, &alloc, stdin)) > 0) {
325 key = buf;
87d1353a 326
64f1e658 327 if (buf[line_len-1] == '\n')
87d1353a
MM
328 buf[--line_len] = '\0';
329
330 if (!line_len)
331 break;
332
333 value = strchr(buf, '=');
334 if (!value) {
335 g_warning("invalid credential line: %s", key);
336 g_free(buf);
337 return -1;
338 }
339 *value++ = '\0';
340
341 if (!strcmp(key, "protocol")) {
342 g_free(c->protocol);
343 c->protocol = g_strdup(value);
344 } else if (!strcmp(key, "host")) {
345 g_free(c->host);
346 c->host = g_strdup(value);
347 value = strrchr(c->host, ':');
348 if (value) {
349 *value++ = '\0';
350 c->port = atoi(value);
351 }
352 } else if (!strcmp(key, "path")) {
353 g_free(c->path);
354 c->path = g_strdup(value);
355 } else if (!strcmp(key, "username")) {
356 g_free(c->username);
357 c->username = g_strdup(value);
0ce02e2f
H
358 } else if (!strcmp(key, "password_expiry_utc")) {
359 g_free(c->password_expiry_utc);
360 c->password_expiry_utc = g_strdup(value);
87d1353a
MM
361 } else if (!strcmp(key, "password")) {
362 g_free(c->password);
363 c->password = g_strdup(value);
364 while (*value)
365 *value++ = '\0';
0ce02e2f
H
366 } else if (!strcmp(key, "oauth_refresh_token")) {
367 g_free(c->oauth_refresh_token);
368 c->oauth_refresh_token = g_strdup(value);
369 while (*value)
370 *value++ = '\0';
87d1353a
MM
371 }
372 /*
373 * Ignore other lines; we don't know what they mean, but
374 * this future-proofs us when later versions of git do
375 * learn new lines, and the helpers are updated to match.
376 */
377 }
378
64f1e658 379 free(buf);
87d1353a
MM
380
381 return 0;
382}
383
384static void credential_write_item(FILE *fp, const char *key, const char *value)
385{
386 if (!value)
387 return;
388 fprintf(fp, "%s=%s\n", key, value);
389}
390
391static void credential_write(const struct credential *c)
392{
393 /* only write username/password, if set */
394 credential_write_item(stdout, "username", c->username);
395 credential_write_item(stdout, "password", c->password);
0ce02e2f
H
396 credential_write_item(stdout, "password_expiry_utc",
397 c->password_expiry_utc);
398 credential_write_item(stdout, "oauth_refresh_token",
399 c->oauth_refresh_token);
87d1353a
MM
400}
401
402static void usage(const char *name)
403{
404 struct credential_operation const *try_op = credential_helper_ops;
405 const char *basename = strrchr(name, '/');
406
407 basename = (basename) ? basename + 1 : name;
408 fprintf(stderr, "usage: %s <", basename);
409 while (try_op->name) {
410 fprintf(stderr, "%s", (try_op++)->name);
411 if (try_op->name)
412 fprintf(stderr, "%s", "|");
413 }
414 fprintf(stderr, "%s", ">\n");
415}
416
417int main(int argc, char *argv[])
418{
419 int ret = EXIT_SUCCESS;
420
421 struct credential_operation const *try_op = credential_helper_ops;
422 struct credential cred = CREDENTIAL_INIT;
423
424 if (!argv[1]) {
425 usage(argv[0]);
426 exit(EXIT_FAILURE);
427 }
428
429 g_set_application_name("Git Credential Helper");
430
431 /* lookup operation callback */
432 while (try_op->name && strcmp(argv[1], try_op->name))
433 try_op++;
434
435 /* unsupported operation given -- ignore silently */
436 if (!try_op->name || !try_op->op)
437 goto out;
438
439 ret = credential_read(&cred);
440 if (ret)
441 goto out;
442
443 /* perform credential operation */
444 ret = (*try_op->op)(&cred);
445
446 credential_write(&cred);
447
448out:
449 credential_clear(&cred);
450 return ret;
451}