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 char* object = NULL;
116
117 if (!c->path)
118 return object;
119
120 object = (char*) malloc(strlen(c->host)+strlen(c->path)+8);
121 if (!object)
122 die_errno(errno);
123
124 if (c->port)
125 sprintf(object,"%s:%hd/%s",c->host,c->port,c->path);
126 else
127 sprintf(object,"%s/%s",c->host,c->path);
128
129 return object;
130}
131
132static int keyring_get(struct credential *c)
133{
134 char* object = NULL;
135 GList *entries;
136 GnomeKeyringNetworkPasswordData *password_data;
137 GnomeKeyringResult result;
138
139 if (!c->protocol || !(c->host || c->path))
140 return EXIT_FAILURE;
141
142 object = keyring_object(c);
143
144 result = gnome_keyring_find_network_password_sync(
145 c->username,
146 NULL /* domain */,
147 c->host,
148 object,
149 c->protocol,
150 NULL /* authtype */,
151 c->port,
152 &entries);
153
154 free(object);
155
156 if (result == GNOME_KEYRING_RESULT_NO_MATCH)
157 return EXIT_SUCCESS;
158
159 if (result == GNOME_KEYRING_RESULT_CANCELLED)
160 return EXIT_SUCCESS;
161
162 if (result != GNOME_KEYRING_RESULT_OK) {
163 error("%s",gnome_keyring_result_to_message(result));
164 return EXIT_FAILURE;
165 }
166
167 /* pick the first one from the list */
168 password_data = (GnomeKeyringNetworkPasswordData *) entries->data;
169
170 free_password(c->password);
171 c->password = xstrdup(password_data->password);
172
173 if (!c->username)
174 c->username = xstrdup(password_data->user);
175
176 gnome_keyring_network_password_list_free(entries);
177
178 return EXIT_SUCCESS;
179}
180
181
182static int keyring_store(struct credential *c)
183{
184 guint32 item_id;
185 char *object = NULL;
186
187 /*
188 * Sanity check that what we are storing is actually sensible.
189 * In particular, we can't make a URL without a protocol field.
190 * Without either a host or pathname (depending on the scheme),
191 * we have no primary key. And without a username and password,
192 * we are not actually storing a credential.
193 */
194 if (!c->protocol || !(c->host || c->path) ||
195 !c->username || !c->password)
196 return EXIT_FAILURE;
197
198 object = keyring_object(c);
199
200 gnome_keyring_set_network_password_sync(
201 GNOME_KEYRING_DEFAULT,
202 c->username,
203 NULL /* domain */,
204 c->host,
205 object,
206 c->protocol,
207 NULL /* authtype */,
208 c->port,
209 c->password,
210 &item_id);
211
212 free(object);
213 return EXIT_SUCCESS;
214}
215
216static int keyring_erase(struct credential *c)
217{
218 char *object = NULL;
219 GList *entries;
220 GnomeKeyringNetworkPasswordData *password_data;
221 GnomeKeyringResult result;
222
223 /*
224 * Sanity check that we actually have something to match
225 * against. The input we get is a restrictive pattern,
226 * so technically a blank credential means "erase everything".
227 * But it is too easy to accidentally send this, since it is equivalent
228 * to empty input. So explicitly disallow it, and require that the
229 * pattern have some actual content to match.
230 */
231 if (!c->protocol && !c->host && !c->path && !c->username)
232 return EXIT_FAILURE;
233
234 object = keyring_object(c);
235
236 result = gnome_keyring_find_network_password_sync(
237 c->username,
238 NULL /* domain */,
239 c->host,
240 object,
241 c->protocol,
242 NULL /* authtype */,
243 c->port,
244 &entries);
245
246 free(object);
247
248 if (result == GNOME_KEYRING_RESULT_NO_MATCH)
249 return EXIT_SUCCESS;
250
251 if (result == GNOME_KEYRING_RESULT_CANCELLED)
252 return EXIT_SUCCESS;
253
254 if (result != GNOME_KEYRING_RESULT_OK)
255 {
256 error("%s",gnome_keyring_result_to_message(result));
257 return EXIT_FAILURE;
258 }
259
260 /* pick the first one from the list (delete all matches?) */
261 password_data = (GnomeKeyringNetworkPasswordData *) entries->data;
262
263 result = gnome_keyring_item_delete_sync(
264 password_data->keyring, password_data->item_id);
265
266 gnome_keyring_network_password_list_free(entries);
267
268 if (result != GNOME_KEYRING_RESULT_OK)
269 {
270 error("%s",gnome_keyring_result_to_message(result));
271 return EXIT_FAILURE;
272 }
273
274 return EXIT_SUCCESS;
275}
276
277/*
278 * Table with helper operation callbacks, used by generic
279 * credential helper main function.
280 */
281static struct credential_operation const credential_helper_ops[] =
282{
283 { "get", keyring_get },
284 { "store", keyring_store },
285 { "erase", keyring_erase },
286 CREDENTIAL_OP_END
287};
288
289/* ------------------ credential functions ------------------ */
290
291static void credential_init(struct credential *c)
292{
293 memset(c, 0, sizeof(*c));
294}
295
296static void credential_clear(struct credential *c)
297{
298 free(c->protocol);
299 free(c->host);
300 free(c->path);
301 free(c->username);
302 free_password(c->password);
303
304 credential_init(c);
305}
306
307static int credential_read(struct credential *c)
308{
309 char buf[1024];
310 size_t line_len;
311 char *key = buf;
312 char *value;
313
314 while (fgets(buf, sizeof(buf), stdin))
315 {
316 line_len = strlen(buf);
317
318 if (line_len && buf[line_len-1] == '\n')
319 buf[--line_len]='\0';
320
321 if (!line_len)
322 break;
323
324 value = strchr(buf,'=');
325 if (!value) {
326 warning("invalid credential line: %s", key);
327 return -1;
328 }
329 *value++ = '\0';
330
331 if (!strcmp(key, "protocol")) {
332 free(c->protocol);
333 c->protocol = xstrdup(value);
334 } else if (!strcmp(key, "host")) {
335 free(c->host);
336 c->host = xstrdup(value);
337 value = strrchr(c->host,':');
338 if (value) {
339 *value++ = '\0';
340 c->port = atoi(value);
341 }
342 } else if (!strcmp(key, "path")) {
343 free(c->path);
344 c->path = xstrdup(value);
345 } else if (!strcmp(key, "username")) {
346 free(c->username);
347 c->username = xstrdup(value);
348 } else if (!strcmp(key, "password")) {
349 free_password(c->password);
350 c->password = xstrdup(value);
351 while (*value) *value++ = '\0';
352 }
353 /*
354 * Ignore other lines; we don't know what they mean, but
355 * this future-proofs us when later versions of git do
356 * learn new lines, and the helpers are updated to match.
357 */
358 }
359 return 0;
360}
361
362static void credential_write_item(FILE *fp, const char *key, const char *value)
363{
364 if (!value)
365 return;
366 fprintf(fp, "%s=%s\n", key, value);
367}
368
369static void credential_write(const struct credential *c)
370{
371 /* only write username/password, if set */
372 credential_write_item(stdout, "username", c->username);
373 credential_write_item(stdout, "password", c->password);
374}
375
376static void usage(const char *name)
377{
378 struct credential_operation const *try_op = credential_helper_ops;
379 const char *basename = strrchr(name,'/');
380
381 basename = (basename) ? basename + 1 : name;
382 fprintf(stderr, "usage: %s <", basename);
383 while (try_op->name) {
384 fprintf(stderr,"%s",(try_op++)->name);
385 if (try_op->name)
386 fprintf(stderr,"%s","|");
387 }
388 fprintf(stderr,"%s",">\n");
389}
390
391int main(int argc, char *argv[])
392{
393 int ret = EXIT_SUCCESS;
394
395 struct credential_operation const *try_op = credential_helper_ops;
396 struct credential cred = CREDENTIAL_INIT;
397
398 if (!argv[1]) {
399 usage(argv[0]);
400 exit(EXIT_FAILURE);
401 }
402
403 g_set_application_name("Git Credential Helper");
404
405 /* lookup operation callback */
406 while (try_op->name && strcmp(argv[1], try_op->name))
407 try_op++;
408
409 /* unsupported operation given -- ignore silently */
410 if (!try_op->name || !try_op->op)
411 goto out;
412
413 ret = credential_read(&cred);
414 if (ret)
415 goto out;
416
417 /* perform credential operation */
418 ret = (*try_op->op)(&cred);
419
420 credential_write(&cred);
421
422out:
423 credential_clear(&cred);
424 return ret;
425}