Git 2.45
[git.git] / contrib / credential / libsecret / git-credential-libsecret.c
blob90034d0cf1eb3d04c04872f38f2c8ba3a25fd904
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>
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.
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.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, see <http://www.gnu.org/licenses/>.
21 * Credits:
22 * - GNOME Keyring API handling originally written by John Szakmeister
23 * - ported to credential helper API by Philipp A. Hartmann
26 #include <stdio.h>
27 #include <string.h>
28 #include <stdlib.h>
29 #include <glib.h>
30 #include <libsecret/secret.h>
33 * This credential struct and API is simplified from git's credential.{h,c}
35 struct credential {
36 char *protocol;
37 char *host;
38 unsigned short port;
39 char *path;
40 char *username;
41 char *password;
42 char *password_expiry_utc;
43 char *oauth_refresh_token;
46 #define CREDENTIAL_INIT { 0 }
48 typedef int (*credential_op_cb)(struct credential *);
50 struct credential_operation {
51 char *name;
52 credential_op_cb op;
55 #define CREDENTIAL_OP_END { NULL, NULL }
57 static void credential_clear(struct credential *c);
59 /* ----------------- Secret Service functions ----------------- */
61 static const SecretSchema schema = {
62 "org.git.Password",
63 /* Ignore schema name during search for backwards compatibility */
64 SECRET_SCHEMA_DONT_MATCH_NAME,
67 * libsecret assumes attribute values are non-confidential and
68 * unchanging, so we can't include oauth_refresh_token or
69 * password_expiry_utc.
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 },
80 static char *make_label(struct credential *c)
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 : "");
90 static GHashTable *make_attr_list(struct credential *c)
92 GHashTable *al = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
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));
105 return al;
108 static int keyring_get(struct credential *c)
110 SecretService *service = NULL;
111 GHashTable *attributes = NULL;
112 GError *error = NULL;
113 GList *items = NULL;
115 if (!c->protocol || !(c->host || c->path))
116 return EXIT_FAILURE;
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;
125 attributes = make_attr_list(c);
126 items = secret_service_search_sync(service,
127 &schema,
128 attributes,
129 SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_UNLOCK,
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;
139 if (items != NULL) {
140 SecretItem *item;
141 SecretValue *secret;
142 const char *s;
143 gchar **parts;
145 item = items->data;
146 secret = secret_item_get_secret(item);
147 attributes = secret_item_get_attributes(item);
149 s = g_hash_table_lookup(attributes, "user");
150 if (s) {
151 g_free(c->username);
152 c->username = g_strdup(s);
155 s = secret_value_get_text(secret);
156 if (s) {
158 * Passwords and other attributes encoded in following format:
159 * hunter2
160 * password_expiry_utc=1684189401
161 * oauth_refresh_token=xyzzy
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 } else {
168 g_free(c->password);
169 c->password = g_strdup("");
171 for (int i = 1; i < g_strv_length(parts); i++) {
172 if (g_str_has_prefix(parts[i], "password_expiry_utc=")) {
173 g_free(c->password_expiry_utc);
174 c->password_expiry_utc = g_strdup(&parts[i][20]);
175 } else if (g_str_has_prefix(parts[i], "oauth_refresh_token=")) {
176 g_free(c->oauth_refresh_token);
177 c->oauth_refresh_token = g_strdup(&parts[i][20]);
180 g_strfreev(parts);
183 g_hash_table_unref(attributes);
184 secret_value_unref(secret);
185 g_list_free_full(items, g_object_unref);
188 return EXIT_SUCCESS;
192 static int keyring_store(struct credential *c)
194 char *label = NULL;
195 GHashTable *attributes = NULL;
196 GError *error = NULL;
197 GString *secret = NULL;
200 * Sanity check that what we are storing is actually sensible.
201 * In particular, we can't make a URL without a protocol field.
202 * Without either a host or pathname (depending on the scheme),
203 * we have no primary key. And without a username and password,
204 * we are not actually storing a credential.
206 if (!c->protocol || !(c->host || c->path) ||
207 !c->username || !c->password)
208 return EXIT_FAILURE;
210 label = make_label(c);
211 attributes = make_attr_list(c);
212 secret = g_string_new(c->password);
213 if (c->password_expiry_utc) {
214 g_string_append_printf(secret, "\npassword_expiry_utc=%s",
215 c->password_expiry_utc);
217 if (c->oauth_refresh_token) {
218 g_string_append_printf(secret, "\noauth_refresh_token=%s",
219 c->oauth_refresh_token);
221 secret_password_storev_sync(&schema,
222 attributes,
223 NULL,
224 label,
225 secret->str,
226 NULL,
227 &error);
228 g_string_free(secret, TRUE);
229 g_free(label);
230 g_hash_table_unref(attributes);
232 if (error != NULL) {
233 g_critical("store failed: %s", error->message);
234 g_error_free(error);
235 return EXIT_FAILURE;
238 return EXIT_SUCCESS;
241 static int keyring_erase(struct credential *c)
243 GHashTable *attributes = NULL;
244 GError *error = NULL;
245 struct credential existing = CREDENTIAL_INIT;
248 * Sanity check that we actually have something to match
249 * against. The input we get is a restrictive pattern,
250 * so technically a blank credential means "erase everything".
251 * But it is too easy to accidentally send this, since it is equivalent
252 * to empty input. So explicitly disallow it, and require that the
253 * pattern have some actual content to match.
255 if (!c->protocol && !c->host && !c->path && !c->username)
256 return EXIT_FAILURE;
258 if (c->password) {
259 existing.host = g_strdup(c->host);
260 existing.path = g_strdup(c->path);
261 existing.port = c->port;
262 existing.protocol = g_strdup(c->protocol);
263 existing.username = g_strdup(c->username);
264 keyring_get(&existing);
265 if (existing.password && strcmp(c->password, existing.password)) {
266 credential_clear(&existing);
267 return EXIT_SUCCESS;
269 credential_clear(&existing);
272 attributes = make_attr_list(c);
273 secret_password_clearv_sync(&schema,
274 attributes,
275 NULL,
276 &error);
277 g_hash_table_unref(attributes);
279 if (error != NULL) {
280 g_critical("erase failed: %s", error->message);
281 g_error_free(error);
282 return EXIT_FAILURE;
285 return EXIT_SUCCESS;
289 * Table with helper operation callbacks, used by generic
290 * credential helper main function.
292 static struct credential_operation const credential_helper_ops[] = {
293 { "get", keyring_get },
294 { "store", keyring_store },
295 { "erase", keyring_erase },
296 CREDENTIAL_OP_END
299 /* ------------------ credential functions ------------------ */
301 static void credential_init(struct credential *c)
303 memset(c, 0, sizeof(*c));
306 static void credential_clear(struct credential *c)
308 g_free(c->protocol);
309 g_free(c->host);
310 g_free(c->path);
311 g_free(c->username);
312 g_free(c->password);
313 g_free(c->password_expiry_utc);
314 g_free(c->oauth_refresh_token);
316 credential_init(c);
319 static int credential_read(struct credential *c)
321 char *buf = NULL;
322 size_t alloc;
323 ssize_t line_len;
324 char *key;
325 char *value;
327 while ((line_len = getline(&buf, &alloc, stdin)) > 0) {
328 key = buf;
330 if (buf[line_len-1] == '\n')
331 buf[--line_len] = '\0';
333 if (!line_len)
334 break;
336 value = strchr(buf, '=');
337 if (!value) {
338 g_warning("invalid credential line: %s", key);
339 g_free(buf);
340 return -1;
342 *value++ = '\0';
344 if (!strcmp(key, "protocol")) {
345 g_free(c->protocol);
346 c->protocol = g_strdup(value);
347 } else if (!strcmp(key, "host")) {
348 g_free(c->host);
349 c->host = g_strdup(value);
350 value = strrchr(c->host, ':');
351 if (value) {
352 *value++ = '\0';
353 c->port = atoi(value);
355 } else if (!strcmp(key, "path")) {
356 g_free(c->path);
357 c->path = g_strdup(value);
358 } else if (!strcmp(key, "username")) {
359 g_free(c->username);
360 c->username = g_strdup(value);
361 } else if (!strcmp(key, "password_expiry_utc")) {
362 g_free(c->password_expiry_utc);
363 c->password_expiry_utc = g_strdup(value);
364 } else if (!strcmp(key, "password")) {
365 g_free(c->password);
366 c->password = g_strdup(value);
367 while (*value)
368 *value++ = '\0';
369 } else if (!strcmp(key, "oauth_refresh_token")) {
370 g_free(c->oauth_refresh_token);
371 c->oauth_refresh_token = g_strdup(value);
372 while (*value)
373 *value++ = '\0';
376 * Ignore other lines; we don't know what they mean, but
377 * this future-proofs us when later versions of git do
378 * learn new lines, and the helpers are updated to match.
382 free(buf);
384 return 0;
387 static void credential_write_item(FILE *fp, const char *key, const char *value)
389 if (!value)
390 return;
391 fprintf(fp, "%s=%s\n", key, value);
394 static void credential_write(const struct credential *c)
396 /* only write username/password, if set */
397 credential_write_item(stdout, "username", c->username);
398 credential_write_item(stdout, "password", c->password);
399 credential_write_item(stdout, "password_expiry_utc",
400 c->password_expiry_utc);
401 credential_write_item(stdout, "oauth_refresh_token",
402 c->oauth_refresh_token);
405 static void usage(const char *name)
407 struct credential_operation const *try_op = credential_helper_ops;
408 const char *basename = strrchr(name, '/');
410 basename = (basename) ? basename + 1 : name;
411 fprintf(stderr, "usage: %s <", basename);
412 while (try_op->name) {
413 fprintf(stderr, "%s", (try_op++)->name);
414 if (try_op->name)
415 fprintf(stderr, "%s", "|");
417 fprintf(stderr, "%s", ">\n");
420 int main(int argc, char *argv[])
422 int ret = EXIT_SUCCESS;
424 struct credential_operation const *try_op = credential_helper_ops;
425 struct credential cred = CREDENTIAL_INIT;
427 if (!argv[1]) {
428 usage(argv[0]);
429 exit(EXIT_FAILURE);
432 g_set_application_name("Git Credential Helper");
434 /* lookup operation callback */
435 while (try_op->name && strcmp(argv[1], try_op->name))
436 try_op++;
438 /* unsupported operation given -- ignore silently */
439 if (!try_op->name || !try_op->op)
440 goto out;
442 ret = credential_read(&cred);
443 if (ret)
444 goto out;
446 /* perform credential operation */
447 ret = (*try_op->op)(&cred);
449 credential_write(&cred);
451 out:
452 credential_clear(&cred);
453 return ret;