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
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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 <stdarg.h>
29#include <stdlib.h>
30#include <errno.h>
31#include <glib.h>
32#include <gnome-keyring.h>
33
34/*
35 * This credential struct and API is simplified from git's credential.{h,c}
36 */
37struct credential
38{
39 char *protocol;
40 char *host;
41 unsigned short port;
42 char *path;
43 char *username;
44 char *password;
45};
46
47#define CREDENTIAL_INIT \
48 { NULL,NULL,0,NULL,NULL,NULL }
49
50typedef int (*credential_op_cb)(struct credential*);
51
52struct credential_operation
53{
54 char *name;
55 credential_op_cb op;
56};
57
58#define CREDENTIAL_OP_END \
59 { NULL,NULL }
60
61/* ---------------- common helper functions ----------------- */
62
63static inline void free_password(char *password)
64{
65 char *c = password;
66 if (!password)
67 return;
68
69 while (*c) *c++ = '\0';
70 free(password);
71}
72
73static inline void warning(const char *fmt, ...)
74{
75 va_list ap;
76
77 va_start(ap, fmt);
78 fprintf(stderr, "warning: ");
79 vfprintf(stderr, fmt, ap);
80 fprintf(stderr, "\n" );
81 va_end(ap);
82}
83
84static inline void error(const char *fmt, ...)
85{
86 va_list ap;
87
88 va_start(ap, fmt);
89 fprintf(stderr, "error: ");
90 vfprintf(stderr, fmt, ap);
91 fprintf(stderr, "\n" );
92 va_end(ap);
93}
94
95static inline void die_errno(int err)
96{
97 error("%s", strerror(err));
98 exit(EXIT_FAILURE);
99}
100
101static inline char *xstrdup(const char *str)
102{
103 char *ret = strdup(str);
104 if (!ret)
105 die_errno(errno);
106
107 return ret;
108}
109
110/* ----------------- GNOME Keyring functions ----------------- */
111
112/* create a special keyring option string, if path is given */
113static char* keyring_object(struct credential *c)
114{
115 if (!c->path)
116 return NULL;
117
118 if (c->port)
119 return g_strdup_printf("%s:%hd/%s", c->host, c->port, c->path);
120
121 return g_strdup_printf("%s/%s", c->host, c->path);
122}
123
124static int keyring_get(struct credential *c)
125{
126 char* object = NULL;
127 GList *entries;
128 GnomeKeyringNetworkPasswordData *password_data;
129 GnomeKeyringResult result;
130
131 if (!c->protocol || !(c->host || c->path))
132 return EXIT_FAILURE;
133
134 object = keyring_object(c);
135
136 result = gnome_keyring_find_network_password_sync(
137 c->username,
138 NULL /* domain */,
139 c->host,
140 object,
141 c->protocol,
142 NULL /* authtype */,
143 c->port,
144 &entries);
145
146 free(object);
147
148 if (result == GNOME_KEYRING_RESULT_NO_MATCH)
149 return EXIT_SUCCESS;
150
151 if (result == GNOME_KEYRING_RESULT_CANCELLED)
152 return EXIT_SUCCESS;
153
154 if (result != GNOME_KEYRING_RESULT_OK) {
155 error("%s",gnome_keyring_result_to_message(result));
156 return EXIT_FAILURE;
157 }
158
159 /* pick the first one from the list */
160 password_data = (GnomeKeyringNetworkPasswordData *) entries->data;
161
162 free_password(c->password);
163 c->password = xstrdup(password_data->password);
164
165 if (!c->username)
166 c->username = xstrdup(password_data->user);
167
168 gnome_keyring_network_password_list_free(entries);
169
170 return EXIT_SUCCESS;
171}
172
173
174static int keyring_store(struct credential *c)
175{
176 guint32 item_id;
177 char *object = NULL;
178
179 /*
180 * Sanity check that what we are storing is actually sensible.
181 * In particular, we can't make a URL without a protocol field.
182 * Without either a host or pathname (depending on the scheme),
183 * we have no primary key. And without a username and password,
184 * we are not actually storing a credential.
185 */
186 if (!c->protocol || !(c->host || c->path) ||
187 !c->username || !c->password)
188 return EXIT_FAILURE;
189
190 object = keyring_object(c);
191
192 gnome_keyring_set_network_password_sync(
193 GNOME_KEYRING_DEFAULT,
194 c->username,
195 NULL /* domain */,
196 c->host,
197 object,
198 c->protocol,
199 NULL /* authtype */,
200 c->port,
201 c->password,
202 &item_id);
203
204 free(object);
205 return EXIT_SUCCESS;
206}
207
208static int keyring_erase(struct credential *c)
209{
210 char *object = NULL;
211 GList *entries;
212 GnomeKeyringNetworkPasswordData *password_data;
213 GnomeKeyringResult result;
214
215 /*
216 * Sanity check that we actually have something to match
217 * against. The input we get is a restrictive pattern,
218 * so technically a blank credential means "erase everything".
219 * But it is too easy to accidentally send this, since it is equivalent
220 * to empty input. So explicitly disallow it, and require that the
221 * pattern have some actual content to match.
222 */
223 if (!c->protocol && !c->host && !c->path && !c->username)
224 return EXIT_FAILURE;
225
226 object = keyring_object(c);
227
228 result = gnome_keyring_find_network_password_sync(
229 c->username,
230 NULL /* domain */,
231 c->host,
232 object,
233 c->protocol,
234 NULL /* authtype */,
235 c->port,
236 &entries);
237
238 free(object);
239
240 if (result == GNOME_KEYRING_RESULT_NO_MATCH)
241 return EXIT_SUCCESS;
242
243 if (result == GNOME_KEYRING_RESULT_CANCELLED)
244 return EXIT_SUCCESS;
245
246 if (result != GNOME_KEYRING_RESULT_OK)
247 {
248 error("%s",gnome_keyring_result_to_message(result));
249 return EXIT_FAILURE;
250 }
251
252 /* pick the first one from the list (delete all matches?) */
253 password_data = (GnomeKeyringNetworkPasswordData *) entries->data;
254
255 result = gnome_keyring_item_delete_sync(
256 password_data->keyring, password_data->item_id);
257
258 gnome_keyring_network_password_list_free(entries);
259
260 if (result != GNOME_KEYRING_RESULT_OK)
261 {
262 error("%s",gnome_keyring_result_to_message(result));
263 return EXIT_FAILURE;
264 }
265
266 return EXIT_SUCCESS;
267}
268
269/*
270 * Table with helper operation callbacks, used by generic
271 * credential helper main function.
272 */
273static struct credential_operation const credential_helper_ops[] =
274{
275 { "get", keyring_get },
276 { "store", keyring_store },
277 { "erase", keyring_erase },
278 CREDENTIAL_OP_END
279};
280
281/* ------------------ credential functions ------------------ */
282
283static void credential_init(struct credential *c)
284{
285 memset(c, 0, sizeof(*c));
286}
287
288static void credential_clear(struct credential *c)
289{
290 free(c->protocol);
291 free(c->host);
292 free(c->path);
293 free(c->username);
294 free_password(c->password);
295
296 credential_init(c);
297}
298
299static int credential_read(struct credential *c)
300{
301 char buf[1024];
302 size_t line_len;
303 char *key = buf;
304 char *value;
305
306 while (fgets(buf, sizeof(buf), stdin))
307 {
308 line_len = strlen(buf);
309
310 if (line_len && buf[line_len-1] == '\n')
311 buf[--line_len]='\0';
312
313 if (!line_len)
314 break;
315
316 value = strchr(buf,'=');
317 if (!value) {
318 warning("invalid credential line: %s", key);
319 return -1;
320 }
321 *value++ = '\0';
322
323 if (!strcmp(key, "protocol")) {
324 free(c->protocol);
325 c->protocol = xstrdup(value);
326 } else if (!strcmp(key, "host")) {
327 free(c->host);
328 c->host = xstrdup(value);
329 value = strrchr(c->host,':');
330 if (value) {
331 *value++ = '\0';
332 c->port = atoi(value);
333 }
334 } else if (!strcmp(key, "path")) {
335 free(c->path);
336 c->path = xstrdup(value);
337 } else if (!strcmp(key, "username")) {
338 free(c->username);
339 c->username = xstrdup(value);
340 } else if (!strcmp(key, "password")) {
341 free_password(c->password);
342 c->password = xstrdup(value);
343 while (*value) *value++ = '\0';
344 }
345 /*
346 * Ignore other lines; we don't know what they mean, but
347 * this future-proofs us when later versions of git do
348 * learn new lines, and the helpers are updated to match.
349 */
350 }
351 return 0;
352}
353
354static void credential_write_item(FILE *fp, const char *key, const char *value)
355{
356 if (!value)
357 return;
358 fprintf(fp, "%s=%s\n", key, value);
359}
360
361static void credential_write(const struct credential *c)
362{
363 /* only write username/password, if set */
364 credential_write_item(stdout, "username", c->username);
365 credential_write_item(stdout, "password", c->password);
366}
367
368static void usage(const char *name)
369{
370 struct credential_operation const *try_op = credential_helper_ops;
371 const char *basename = strrchr(name,'/');
372
373 basename = (basename) ? basename + 1 : name;
374 fprintf(stderr, "usage: %s <", basename);
375 while (try_op->name) {
376 fprintf(stderr,"%s",(try_op++)->name);
377 if (try_op->name)
378 fprintf(stderr,"%s","|");
379 }
380 fprintf(stderr,"%s",">\n");
381}
382
383int main(int argc, char *argv[])
384{
385 int ret = EXIT_SUCCESS;
386
387 struct credential_operation const *try_op = credential_helper_ops;
388 struct credential cred = CREDENTIAL_INIT;
389
390 if (!argv[1]) {
391 usage(argv[0]);
392 exit(EXIT_FAILURE);
393 }
394
395 g_set_application_name("Git Credential Helper");
396
397 /* lookup operation callback */
398 while (try_op->name && strcmp(argv[1], try_op->name))
399 try_op++;
400
401 /* unsupported operation given -- ignore silently */
402 if (!try_op->name || !try_op->op)
403 goto out;
404
405 ret = credential_read(&cred);
406 if (ret)
407 goto out;
408
409 /* perform credential operation */
410 ret = (*try_op->op)(&cred);
411
412 credential_write(&cred);
413
414out:
415 credential_clear(&cred);
416 return ret;
417}