contrib / credential / gnome-keyring / git-credential-gnome-keyring.con commit Merge branch 'nd/gc-lock-against-each-other' (414b703)
   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 <stdlib.h>
  29#include <glib.h>
  30#include <gnome-keyring.h>
  31
  32#ifdef GNOME_KEYRING_DEFAULT
  33
  34   /* Modern gnome-keyring */
  35
  36#include <gnome-keyring-memory.h>
  37
  38#else
  39
  40   /*
  41    * Support ancient gnome-keyring, circ. RHEL 5.X.
  42    * GNOME_KEYRING_DEFAULT seems to have been introduced with Gnome 2.22,
  43    * and the other features roughly around Gnome 2.20, 6 months before.
  44    * Ubuntu 8.04 used Gnome 2.22 (I think).  Not sure any distro used 2.20.
  45    * So the existence/non-existence of GNOME_KEYRING_DEFAULT seems like
  46    * a decent thing to use as an indicator.
  47    */
  48
  49#define GNOME_KEYRING_DEFAULT NULL
  50
  51/*
  52 * ancient gnome-keyring returns DENIED when an entry is not found.
  53 * Setting NO_MATCH to DENIED will prevent us from reporting DENIED
  54 * errors during get and erase operations, but we will still report
  55 * DENIED errors during a store.
  56 */
  57#define GNOME_KEYRING_RESULT_NO_MATCH GNOME_KEYRING_RESULT_DENIED
  58
  59#define gnome_keyring_memory_alloc g_malloc
  60#define gnome_keyring_memory_free gnome_keyring_free_password
  61#define gnome_keyring_memory_strdup g_strdup
  62
  63static const char* gnome_keyring_result_to_message(GnomeKeyringResult result)
  64{
  65        switch (result) {
  66        case GNOME_KEYRING_RESULT_OK:
  67                return "OK";
  68        case GNOME_KEYRING_RESULT_DENIED:
  69                return "Denied";
  70        case GNOME_KEYRING_RESULT_NO_KEYRING_DAEMON:
  71                return "No Keyring Daemon";
  72        case GNOME_KEYRING_RESULT_ALREADY_UNLOCKED:
  73                return "Already UnLocked";
  74        case GNOME_KEYRING_RESULT_NO_SUCH_KEYRING:
  75                return "No Such Keyring";
  76        case GNOME_KEYRING_RESULT_BAD_ARGUMENTS:
  77                return "Bad Arguments";
  78        case GNOME_KEYRING_RESULT_IO_ERROR:
  79                return "IO Error";
  80        case GNOME_KEYRING_RESULT_CANCELLED:
  81                return "Cancelled";
  82        case GNOME_KEYRING_RESULT_ALREADY_EXISTS:
  83                return "Already Exists";
  84        default:
  85                return "Unknown Error";
  86        }
  87}
  88
  89/*
  90 * Support really ancient gnome-keyring, circ. RHEL 4.X.
  91 * Just a guess for the Glib version.  Glib 2.8 was roughly Gnome 2.12 ?
  92 * Which was released with gnome-keyring 0.4.3 ??
  93 */
  94#if GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 8
  95
  96static void gnome_keyring_done_cb(GnomeKeyringResult result, gpointer user_data)
  97{
  98        gpointer *data = (gpointer*) user_data;
  99        int *done = (int*) data[0];
 100        GnomeKeyringResult *r = (GnomeKeyringResult*) data[1];
 101
 102        *r = result;
 103        *done = 1;
 104}
 105
 106static void wait_for_request_completion(int *done)
 107{
 108        GMainContext *mc = g_main_context_default();
 109        while (!*done)
 110                g_main_context_iteration(mc, TRUE);
 111}
 112
 113static GnomeKeyringResult gnome_keyring_item_delete_sync(const char *keyring, guint32 id)
 114{
 115        int done = 0;
 116        GnomeKeyringResult result;
 117        gpointer data[] = { &done, &result };
 118
 119        gnome_keyring_item_delete(keyring, id, gnome_keyring_done_cb, data,
 120                NULL);
 121
 122        wait_for_request_completion(&done);
 123
 124        return result;
 125}
 126
 127#endif
 128#endif
 129
 130/*
 131 * This credential struct and API is simplified from git's credential.{h,c}
 132 */
 133struct credential
 134{
 135        char          *protocol;
 136        char          *host;
 137        unsigned short port;
 138        char          *path;
 139        char          *username;
 140        char          *password;
 141};
 142
 143#define CREDENTIAL_INIT \
 144  { NULL,NULL,0,NULL,NULL,NULL }
 145
 146typedef int (*credential_op_cb)(struct credential*);
 147
 148struct credential_operation
 149{
 150        char             *name;
 151        credential_op_cb op;
 152};
 153
 154#define CREDENTIAL_OP_END \
 155  { NULL,NULL }
 156
 157/* ----------------- GNOME Keyring functions ----------------- */
 158
 159/* create a special keyring option string, if path is given */
 160static char* keyring_object(struct credential *c)
 161{
 162        if (!c->path)
 163                return NULL;
 164
 165        if (c->port)
 166                return g_strdup_printf("%s:%hd/%s", c->host, c->port, c->path);
 167
 168        return g_strdup_printf("%s/%s", c->host, c->path);
 169}
 170
 171static int keyring_get(struct credential *c)
 172{
 173        char* object = NULL;
 174        GList *entries;
 175        GnomeKeyringNetworkPasswordData *password_data;
 176        GnomeKeyringResult result;
 177
 178        if (!c->protocol || !(c->host || c->path))
 179                return EXIT_FAILURE;
 180
 181        object = keyring_object(c);
 182
 183        result = gnome_keyring_find_network_password_sync(
 184                                c->username,
 185                                NULL /* domain */,
 186                                c->host,
 187                                object,
 188                                c->protocol,
 189                                NULL /* authtype */,
 190                                c->port,
 191                                &entries);
 192
 193        g_free(object);
 194
 195        if (result == GNOME_KEYRING_RESULT_NO_MATCH)
 196                return EXIT_SUCCESS;
 197
 198        if (result == GNOME_KEYRING_RESULT_CANCELLED)
 199                return EXIT_SUCCESS;
 200
 201        if (result != GNOME_KEYRING_RESULT_OK) {
 202                g_critical("%s", gnome_keyring_result_to_message(result));
 203                return EXIT_FAILURE;
 204        }
 205
 206        /* pick the first one from the list */
 207        password_data = (GnomeKeyringNetworkPasswordData *) entries->data;
 208
 209        gnome_keyring_memory_free(c->password);
 210        c->password = gnome_keyring_memory_strdup(password_data->password);
 211
 212        if (!c->username)
 213                c->username = g_strdup(password_data->user);
 214
 215        gnome_keyring_network_password_list_free(entries);
 216
 217        return EXIT_SUCCESS;
 218}
 219
 220
 221static int keyring_store(struct credential *c)
 222{
 223        guint32 item_id;
 224        char  *object = NULL;
 225        GnomeKeyringResult result;
 226
 227        /*
 228         * Sanity check that what we are storing is actually sensible.
 229         * In particular, we can't make a URL without a protocol field.
 230         * Without either a host or pathname (depending on the scheme),
 231         * we have no primary key. And without a username and password,
 232         * we are not actually storing a credential.
 233         */
 234        if (!c->protocol || !(c->host || c->path) ||
 235            !c->username || !c->password)
 236                return EXIT_FAILURE;
 237
 238        object = keyring_object(c);
 239
 240        result = gnome_keyring_set_network_password_sync(
 241                                GNOME_KEYRING_DEFAULT,
 242                                c->username,
 243                                NULL /* domain */,
 244                                c->host,
 245                                object,
 246                                c->protocol,
 247                                NULL /* authtype */,
 248                                c->port,
 249                                c->password,
 250                                &item_id);
 251
 252        g_free(object);
 253
 254        if (result != GNOME_KEYRING_RESULT_OK &&
 255            result != GNOME_KEYRING_RESULT_CANCELLED) {
 256                g_critical("%s", gnome_keyring_result_to_message(result));
 257                return EXIT_FAILURE;
 258        }
 259
 260        return EXIT_SUCCESS;
 261}
 262
 263static int keyring_erase(struct credential *c)
 264{
 265        char  *object = NULL;
 266        GList *entries;
 267        GnomeKeyringNetworkPasswordData *password_data;
 268        GnomeKeyringResult result;
 269
 270        /*
 271         * Sanity check that we actually have something to match
 272         * against. The input we get is a restrictive pattern,
 273         * so technically a blank credential means "erase everything".
 274         * But it is too easy to accidentally send this, since it is equivalent
 275         * to empty input. So explicitly disallow it, and require that the
 276         * pattern have some actual content to match.
 277         */
 278        if (!c->protocol && !c->host && !c->path && !c->username)
 279                return EXIT_FAILURE;
 280
 281        object = keyring_object(c);
 282
 283        result = gnome_keyring_find_network_password_sync(
 284                                c->username,
 285                                NULL /* domain */,
 286                                c->host,
 287                                object,
 288                                c->protocol,
 289                                NULL /* authtype */,
 290                                c->port,
 291                                &entries);
 292
 293        g_free(object);
 294
 295        if (result == GNOME_KEYRING_RESULT_NO_MATCH)
 296                return EXIT_SUCCESS;
 297
 298        if (result == GNOME_KEYRING_RESULT_CANCELLED)
 299                return EXIT_SUCCESS;
 300
 301        if (result != GNOME_KEYRING_RESULT_OK)
 302        {
 303                g_critical("%s", gnome_keyring_result_to_message(result));
 304                return EXIT_FAILURE;
 305        }
 306
 307        /* pick the first one from the list (delete all matches?) */
 308        password_data = (GnomeKeyringNetworkPasswordData *) entries->data;
 309
 310        result = gnome_keyring_item_delete_sync(
 311                password_data->keyring, password_data->item_id);
 312
 313        gnome_keyring_network_password_list_free(entries);
 314
 315        if (result != GNOME_KEYRING_RESULT_OK)
 316        {
 317                g_critical("%s", gnome_keyring_result_to_message(result));
 318                return EXIT_FAILURE;
 319        }
 320
 321        return EXIT_SUCCESS;
 322}
 323
 324/*
 325 * Table with helper operation callbacks, used by generic
 326 * credential helper main function.
 327 */
 328static struct credential_operation const credential_helper_ops[] =
 329{
 330        { "get",   keyring_get   },
 331        { "store", keyring_store },
 332        { "erase", keyring_erase },
 333        CREDENTIAL_OP_END
 334};
 335
 336/* ------------------ credential functions ------------------ */
 337
 338static void credential_init(struct credential *c)
 339{
 340        memset(c, 0, sizeof(*c));
 341}
 342
 343static void credential_clear(struct credential *c)
 344{
 345        g_free(c->protocol);
 346        g_free(c->host);
 347        g_free(c->path);
 348        g_free(c->username);
 349        gnome_keyring_memory_free(c->password);
 350
 351        credential_init(c);
 352}
 353
 354static int credential_read(struct credential *c)
 355{
 356        char    *buf;
 357        size_t line_len;
 358        char   *key;
 359        char   *value;
 360
 361        key = buf = gnome_keyring_memory_alloc(1024);
 362
 363        while (fgets(buf, 1024, stdin))
 364        {
 365                line_len = strlen(buf);
 366
 367                if (line_len && buf[line_len-1] == '\n')
 368                        buf[--line_len]='\0';
 369
 370                if (!line_len)
 371                        break;
 372
 373                value = strchr(buf,'=');
 374                if (!value) {
 375                        g_warning("invalid credential line: %s", key);
 376                        gnome_keyring_memory_free(buf);
 377                        return -1;
 378                }
 379                *value++ = '\0';
 380
 381                if (!strcmp(key, "protocol")) {
 382                        g_free(c->protocol);
 383                        c->protocol = g_strdup(value);
 384                } else if (!strcmp(key, "host")) {
 385                        g_free(c->host);
 386                        c->host = g_strdup(value);
 387                        value = strrchr(c->host,':');
 388                        if (value) {
 389                                *value++ = '\0';
 390                                c->port = atoi(value);
 391                        }
 392                } else if (!strcmp(key, "path")) {
 393                        g_free(c->path);
 394                        c->path = g_strdup(value);
 395                } else if (!strcmp(key, "username")) {
 396                        g_free(c->username);
 397                        c->username = g_strdup(value);
 398                } else if (!strcmp(key, "password")) {
 399                        gnome_keyring_memory_free(c->password);
 400                        c->password = gnome_keyring_memory_strdup(value);
 401                        while (*value) *value++ = '\0';
 402                }
 403                /*
 404                 * Ignore other lines; we don't know what they mean, but
 405                 * this future-proofs us when later versions of git do
 406                 * learn new lines, and the helpers are updated to match.
 407                 */
 408        }
 409
 410        gnome_keyring_memory_free(buf);
 411
 412        return 0;
 413}
 414
 415static void credential_write_item(FILE *fp, const char *key, const char *value)
 416{
 417        if (!value)
 418                return;
 419        fprintf(fp, "%s=%s\n", key, value);
 420}
 421
 422static void credential_write(const struct credential *c)
 423{
 424        /* only write username/password, if set */
 425        credential_write_item(stdout, "username", c->username);
 426        credential_write_item(stdout, "password", c->password);
 427}
 428
 429static void usage(const char *name)
 430{
 431        struct credential_operation const *try_op = credential_helper_ops;
 432        const char *basename = strrchr(name,'/');
 433
 434        basename = (basename) ? basename + 1 : name;
 435        fprintf(stderr, "usage: %s <", basename);
 436        while (try_op->name) {
 437                fprintf(stderr,"%s",(try_op++)->name);
 438                if (try_op->name)
 439                        fprintf(stderr,"%s","|");
 440        }
 441        fprintf(stderr,"%s",">\n");
 442}
 443
 444int main(int argc, char *argv[])
 445{
 446        int ret = EXIT_SUCCESS;
 447
 448        struct credential_operation const *try_op = credential_helper_ops;
 449        struct credential                  cred   = CREDENTIAL_INIT;
 450
 451        if (!argv[1]) {
 452                usage(argv[0]);
 453                exit(EXIT_FAILURE);
 454        }
 455
 456        g_set_application_name("Git Credential Helper");
 457
 458        /* lookup operation callback */
 459        while (try_op->name && strcmp(argv[1], try_op->name))
 460                try_op++;
 461
 462        /* unsupported operation given -- ignore silently */
 463        if (!try_op->name || !try_op->op)
 464                goto out;
 465
 466        ret = credential_read(&cred);
 467        if (ret)
 468                goto out;
 469
 470        /* perform credential operation */
 471        ret = (*try_op->op)(&cred);
 472
 473        credential_write(&cred);
 474
 475out:
 476        credential_clear(&cred);
 477        return ret;
 478}