Skip to content

Commit bfe800c

Browse files
committed
Merge branch 'mm/credential-libsecret'
A new credential helper that talks via "libsecret" with implementations of XDG Secret Service API has been added to contrib/credential/. * mm/credential-libsecret: contrib: add credential helper for libsecret
2 parents 1c2b1f7 + 87d1353 commit bfe800c

File tree

2 files changed

+395
-0
lines changed

2 files changed

+395
-0
lines changed

contrib/credential/libsecret/Makefile

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
MAIN:=git-credential-libsecret
2+
all:: $(MAIN)
3+
4+
CC = gcc
5+
RM = rm -f
6+
CFLAGS = -g -O2 -Wall
7+
PKG_CONFIG = pkg-config
8+
9+
-include ../../../config.mak.autogen
10+
-include ../../../config.mak
11+
12+
INCS:=$(shell $(PKG_CONFIG) --cflags libsecret-1 glib-2.0)
13+
LIBS:=$(shell $(PKG_CONFIG) --libs libsecret-1 glib-2.0)
14+
15+
SRCS:=$(MAIN).c
16+
OBJS:=$(SRCS:.c=.o)
17+
18+
%.o: %.c
19+
$(CC) $(CFLAGS) $(CPPFLAGS) $(INCS) -o $@ -c $<
20+
21+
$(MAIN): $(OBJS)
22+
$(CC) -o $@ $(LDFLAGS) $^ $(LIBS)
23+
24+
clean:
25+
@$(RM) $(MAIN) $(OBJS)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
/*
2+
* Copyright (C) 2011 John Szakmeister <[email protected]>
3+
* 2012 Philipp A. Hartmann <[email protected]>
4+
* 2016 Mantas Mikulėnas <[email protected]>
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
17+
* along with this program; if not, write to the Free Software
18+
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19+
*/
20+
21+
/*
22+
* Credits:
23+
* - GNOME Keyring API handling originally written by John Szakmeister
24+
* - ported to credential helper API by Philipp A. Hartmann
25+
*/
26+
27+
#include <stdio.h>
28+
#include <string.h>
29+
#include <stdlib.h>
30+
#include <glib.h>
31+
#include <libsecret/secret.h>
32+
33+
/*
34+
* This credential struct and API is simplified from git's credential.{h,c}
35+
*/
36+
struct credential {
37+
char *protocol;
38+
char *host;
39+
unsigned short port;
40+
char *path;
41+
char *username;
42+
char *password;
43+
};
44+
45+
#define CREDENTIAL_INIT { NULL, NULL, 0, NULL, NULL, NULL }
46+
47+
typedef int (*credential_op_cb)(struct credential *);
48+
49+
struct credential_operation {
50+
char *name;
51+
credential_op_cb op;
52+
};
53+
54+
#define CREDENTIAL_OP_END { NULL, NULL }
55+
56+
/* ----------------- Secret Service functions ----------------- */
57+
58+
static char *make_label(struct credential *c)
59+
{
60+
if (c->port)
61+
return g_strdup_printf("Git: %s://%s:%hu/%s",
62+
c->protocol, c->host, c->port, c->path ? c->path : "");
63+
else
64+
return g_strdup_printf("Git: %s://%s/%s",
65+
c->protocol, c->host, c->path ? c->path : "");
66+
}
67+
68+
static GHashTable *make_attr_list(struct credential *c)
69+
{
70+
GHashTable *al = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
71+
72+
if (c->username)
73+
g_hash_table_insert(al, "user", g_strdup(c->username));
74+
if (c->protocol)
75+
g_hash_table_insert(al, "protocol", g_strdup(c->protocol));
76+
if (c->host)
77+
g_hash_table_insert(al, "server", g_strdup(c->host));
78+
if (c->port)
79+
g_hash_table_insert(al, "port", g_strdup_printf("%hu", c->port));
80+
if (c->path)
81+
g_hash_table_insert(al, "object", g_strdup(c->path));
82+
83+
return al;
84+
}
85+
86+
static int keyring_get(struct credential *c)
87+
{
88+
SecretService *service = NULL;
89+
GHashTable *attributes = NULL;
90+
GError *error = NULL;
91+
GList *items = NULL;
92+
93+
if (!c->protocol || !(c->host || c->path))
94+
return EXIT_FAILURE;
95+
96+
service = secret_service_get_sync(0, NULL, &error);
97+
if (error != NULL) {
98+
g_critical("could not connect to Secret Service: %s", error->message);
99+
g_error_free(error);
100+
return EXIT_FAILURE;
101+
}
102+
103+
attributes = make_attr_list(c);
104+
items = secret_service_search_sync(service,
105+
SECRET_SCHEMA_COMPAT_NETWORK,
106+
attributes,
107+
SECRET_SEARCH_LOAD_SECRETS,
108+
NULL,
109+
&error);
110+
g_hash_table_unref(attributes);
111+
if (error != NULL) {
112+
g_critical("lookup failed: %s", error->message);
113+
g_error_free(error);
114+
return EXIT_FAILURE;
115+
}
116+
117+
if (items != NULL) {
118+
SecretItem *item;
119+
SecretValue *secret;
120+
const char *s;
121+
122+
item = items->data;
123+
secret = secret_item_get_secret(item);
124+
attributes = secret_item_get_attributes(item);
125+
126+
s = g_hash_table_lookup(attributes, "user");
127+
if (s) {
128+
g_free(c->username);
129+
c->username = g_strdup(s);
130+
}
131+
132+
s = secret_value_get_text(secret);
133+
if (s) {
134+
g_free(c->password);
135+
c->password = g_strdup(s);
136+
}
137+
138+
g_hash_table_unref(attributes);
139+
secret_value_unref(secret);
140+
g_list_free_full(items, g_object_unref);
141+
}
142+
143+
return EXIT_SUCCESS;
144+
}
145+
146+
147+
static int keyring_store(struct credential *c)
148+
{
149+
char *label = NULL;
150+
GHashTable *attributes = NULL;
151+
GError *error = NULL;
152+
153+
/*
154+
* Sanity check that what we are storing is actually sensible.
155+
* In particular, we can't make a URL without a protocol field.
156+
* Without either a host or pathname (depending on the scheme),
157+
* we have no primary key. And without a username and password,
158+
* we are not actually storing a credential.
159+
*/
160+
if (!c->protocol || !(c->host || c->path) ||
161+
!c->username || !c->password)
162+
return EXIT_FAILURE;
163+
164+
label = make_label(c);
165+
attributes = make_attr_list(c);
166+
secret_password_storev_sync(SECRET_SCHEMA_COMPAT_NETWORK,
167+
attributes,
168+
NULL,
169+
label,
170+
c->password,
171+
NULL,
172+
&error);
173+
g_free(label);
174+
g_hash_table_unref(attributes);
175+
176+
if (error != NULL) {
177+
g_critical("store failed: %s", error->message);
178+
g_error_free(error);
179+
return EXIT_FAILURE;
180+
}
181+
182+
return EXIT_SUCCESS;
183+
}
184+
185+
static int keyring_erase(struct credential *c)
186+
{
187+
GHashTable *attributes = NULL;
188+
GError *error = NULL;
189+
190+
/*
191+
* Sanity check that we actually have something to match
192+
* against. The input we get is a restrictive pattern,
193+
* so technically a blank credential means "erase everything".
194+
* But it is too easy to accidentally send this, since it is equivalent
195+
* to empty input. So explicitly disallow it, and require that the
196+
* pattern have some actual content to match.
197+
*/
198+
if (!c->protocol && !c->host && !c->path && !c->username)
199+
return EXIT_FAILURE;
200+
201+
attributes = make_attr_list(c);
202+
secret_password_clearv_sync(SECRET_SCHEMA_COMPAT_NETWORK,
203+
attributes,
204+
NULL,
205+
&error);
206+
g_hash_table_unref(attributes);
207+
208+
if (error != NULL) {
209+
g_critical("erase failed: %s", error->message);
210+
g_error_free(error);
211+
return EXIT_FAILURE;
212+
}
213+
214+
return EXIT_SUCCESS;
215+
}
216+
217+
/*
218+
* Table with helper operation callbacks, used by generic
219+
* credential helper main function.
220+
*/
221+
static struct credential_operation const credential_helper_ops[] = {
222+
{ "get", keyring_get },
223+
{ "store", keyring_store },
224+
{ "erase", keyring_erase },
225+
CREDENTIAL_OP_END
226+
};
227+
228+
/* ------------------ credential functions ------------------ */
229+
230+
static void credential_init(struct credential *c)
231+
{
232+
memset(c, 0, sizeof(*c));
233+
}
234+
235+
static void credential_clear(struct credential *c)
236+
{
237+
g_free(c->protocol);
238+
g_free(c->host);
239+
g_free(c->path);
240+
g_free(c->username);
241+
g_free(c->password);
242+
243+
credential_init(c);
244+
}
245+
246+
static int credential_read(struct credential *c)
247+
{
248+
char *buf;
249+
size_t line_len;
250+
char *key;
251+
char *value;
252+
253+
key = buf = g_malloc(1024);
254+
255+
while (fgets(buf, 1024, stdin)) {
256+
line_len = strlen(buf);
257+
258+
if (line_len && buf[line_len-1] == '\n')
259+
buf[--line_len] = '\0';
260+
261+
if (!line_len)
262+
break;
263+
264+
value = strchr(buf, '=');
265+
if (!value) {
266+
g_warning("invalid credential line: %s", key);
267+
g_free(buf);
268+
return -1;
269+
}
270+
*value++ = '\0';
271+
272+
if (!strcmp(key, "protocol")) {
273+
g_free(c->protocol);
274+
c->protocol = g_strdup(value);
275+
} else if (!strcmp(key, "host")) {
276+
g_free(c->host);
277+
c->host = g_strdup(value);
278+
value = strrchr(c->host, ':');
279+
if (value) {
280+
*value++ = '\0';
281+
c->port = atoi(value);
282+
}
283+
} else if (!strcmp(key, "path")) {
284+
g_free(c->path);
285+
c->path = g_strdup(value);
286+
} else if (!strcmp(key, "username")) {
287+
g_free(c->username);
288+
c->username = g_strdup(value);
289+
} else if (!strcmp(key, "password")) {
290+
g_free(c->password);
291+
c->password = g_strdup(value);
292+
while (*value)
293+
*value++ = '\0';
294+
}
295+
/*
296+
* Ignore other lines; we don't know what they mean, but
297+
* this future-proofs us when later versions of git do
298+
* learn new lines, and the helpers are updated to match.
299+
*/
300+
}
301+
302+
g_free(buf);
303+
304+
return 0;
305+
}
306+
307+
static void credential_write_item(FILE *fp, const char *key, const char *value)
308+
{
309+
if (!value)
310+
return;
311+
fprintf(fp, "%s=%s\n", key, value);
312+
}
313+
314+
static void credential_write(const struct credential *c)
315+
{
316+
/* only write username/password, if set */
317+
credential_write_item(stdout, "username", c->username);
318+
credential_write_item(stdout, "password", c->password);
319+
}
320+
321+
static void usage(const char *name)
322+
{
323+
struct credential_operation const *try_op = credential_helper_ops;
324+
const char *basename = strrchr(name, '/');
325+
326+
basename = (basename) ? basename + 1 : name;
327+
fprintf(stderr, "usage: %s <", basename);
328+
while (try_op->name) {
329+
fprintf(stderr, "%s", (try_op++)->name);
330+
if (try_op->name)
331+
fprintf(stderr, "%s", "|");
332+
}
333+
fprintf(stderr, "%s", ">\n");
334+
}
335+
336+
int main(int argc, char *argv[])
337+
{
338+
int ret = EXIT_SUCCESS;
339+
340+
struct credential_operation const *try_op = credential_helper_ops;
341+
struct credential cred = CREDENTIAL_INIT;
342+
343+
if (!argv[1]) {
344+
usage(argv[0]);
345+
exit(EXIT_FAILURE);
346+
}
347+
348+
g_set_application_name("Git Credential Helper");
349+
350+
/* lookup operation callback */
351+
while (try_op->name && strcmp(argv[1], try_op->name))
352+
try_op++;
353+
354+
/* unsupported operation given -- ignore silently */
355+
if (!try_op->name || !try_op->op)
356+
goto out;
357+
358+
ret = credential_read(&cred);
359+
if (ret)
360+
goto out;
361+
362+
/* perform credential operation */
363+
ret = (*try_op->op)(&cred);
364+
365+
credential_write(&cred);
366+
367+
out:
368+
credential_clear(&cred);
369+
return ret;
370+
}

0 commit comments

Comments
 (0)