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/>.
22 * - GNOME Keyring API handling originally written by John Szakmeister
23 * - ported to credential helper API by Philipp A. Hartmann
30 #include <libsecret/secret.h>
33 * This credential struct and API is simplified from git's credential.{h,c}
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
{
55 #define CREDENTIAL_OP_END { NULL, NULL }
57 static void credential_clear(struct credential
*c
);
59 /* ----------------- Secret Service functions ----------------- */
61 static const SecretSchema schema
= {
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
},
80 static char *make_label(struct credential
*c
)
83 return g_strdup_printf("Git: %s://%s:%hu/%s",
84 c
->protocol
, c
->host
, c
->port
, c
->path
? c
->path
: "");
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
);
95 g_hash_table_insert(al
, "user", g_strdup(c
->username
));
97 g_hash_table_insert(al
, "protocol", g_strdup(c
->protocol
));
99 g_hash_table_insert(al
, "server", g_strdup(c
->host
));
101 g_hash_table_insert(al
, "port", g_strdup_printf("%hu", c
->port
));
103 g_hash_table_insert(al
, "object", g_strdup(c
->path
));
108 static int keyring_get(struct credential
*c
)
110 SecretService
*service
= NULL
;
111 GHashTable
*attributes
= NULL
;
112 GError
*error
= NULL
;
115 if (!c
->protocol
|| !(c
->host
|| c
->path
))
118 service
= secret_service_get_sync(0, NULL
, &error
);
120 g_critical("could not connect to Secret Service: %s", error
->message
);
125 attributes
= make_attr_list(c
);
126 items
= secret_service_search_sync(service
,
129 SECRET_SEARCH_LOAD_SECRETS
| SECRET_SEARCH_UNLOCK
,
132 g_hash_table_unref(attributes
);
134 g_critical("lookup failed: %s", error
->message
);
146 secret
= secret_item_get_secret(item
);
147 attributes
= secret_item_get_attributes(item
);
149 s
= g_hash_table_lookup(attributes
, "user");
152 c
->username
= g_strdup(s
);
155 s
= secret_value_get_text(secret
);
158 * Passwords and other attributes encoded in following format:
160 * password_expiry_utc=1684189401
161 * oauth_refresh_token=xyzzy
163 parts
= g_strsplit(s
, "\n", 0);
164 if (g_strv_length(parts
) >= 1) {
166 c
->password
= g_strdup(parts
[0]);
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]);
183 g_hash_table_unref(attributes
);
184 secret_value_unref(secret
);
185 g_list_free_full(items
, g_object_unref
);
192 static int keyring_store(struct credential
*c
)
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
)
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
,
228 g_string_free(secret
, TRUE
);
230 g_hash_table_unref(attributes
);
233 g_critical("store failed: %s", error
->message
);
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
)
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
);
269 credential_clear(&existing
);
272 attributes
= make_attr_list(c
);
273 secret_password_clearv_sync(&schema
,
277 g_hash_table_unref(attributes
);
280 g_critical("erase failed: %s", error
->message
);
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
},
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
)
313 g_free(c
->password_expiry_utc
);
314 g_free(c
->oauth_refresh_token
);
319 static int credential_read(struct credential
*c
)
327 while ((line_len
= getline(&buf
, &alloc
, stdin
)) > 0) {
330 if (buf
[line_len
-1] == '\n')
331 buf
[--line_len
] = '\0';
336 value
= strchr(buf
, '=');
338 g_warning("invalid credential line: %s", key
);
344 if (!strcmp(key
, "protocol")) {
346 c
->protocol
= g_strdup(value
);
347 } else if (!strcmp(key
, "host")) {
349 c
->host
= g_strdup(value
);
350 value
= strrchr(c
->host
, ':');
353 c
->port
= atoi(value
);
355 } else if (!strcmp(key
, "path")) {
357 c
->path
= g_strdup(value
);
358 } else if (!strcmp(key
, "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")) {
366 c
->password
= g_strdup(value
);
369 } else if (!strcmp(key
, "oauth_refresh_token")) {
370 g_free(c
->oauth_refresh_token
);
371 c
->oauth_refresh_token
= g_strdup(value
);
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.
387 static void credential_write_item(FILE *fp
, const char *key
, const char *value
)
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
);
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
;
432 g_set_application_name("Git Credential Helper");
434 /* lookup operation callback */
435 while (try_op
->name
&& strcmp(argv
[1], try_op
->name
))
438 /* unsupported operation given -- ignore silently */
439 if (!try_op
->name
|| !try_op
->op
)
442 ret
= credential_read(&cred
);
446 /* perform credential operation */
447 ret
= (*try_op
->op
)(&cred
);
449 credential_write(&cred
);
452 credential_clear(&cred
);