contrib / credential / gnome-keyring / git-credential-gnome-keyring.con commit contrib/git-credential-gnome-keyring.c: support ancient gnome-keyring (5a3db11)
   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#endif
  90
  91/*
  92 * This credential struct and API is simplified from git's credential.{h,c}
  93 */
  94struct credential
  95{
  96        char          *protocol;
  97        char          *host;
  98        unsigned short port;
  99        char          *path;
 100        char          *username;
 101        char          *password;
 102};
 103
 104#define CREDENTIAL_INIT \
 105  { NULL,NULL,0,NULL,NULL,NULL }
 106
 107typedef int (*credential_op_cb)(struct credential*);
 108
 109struct credential_operation
 110{
 111        char             *name;
 112        credential_op_cb op;
 113};
 114
 115#define CREDENTIAL_OP_END \
 116  { NULL,NULL }
 117
 118/* ----------------- GNOME Keyring functions ----------------- */
 119
 120/* create a special keyring option string, if path is given */
 121static char* keyring_object(struct credential *c)
 122{
 123        if (!c->path)
 124                return NULL;
 125
 126        if (c->port)
 127                return g_strdup_printf("%s:%hd/%s", c->host, c->port, c->path);
 128
 129        return g_strdup_printf("%s/%s", c->host, c->path);
 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        g_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                g_critical("%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        gnome_keyring_memory_free(c->password);
 171        c->password = gnome_keyring_memory_strdup(password_data->password);
 172
 173        if (!c->username)
 174                c->username = g_strdup(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        GnomeKeyringResult result;
 187
 188        /*
 189         * Sanity check that what we are storing is actually sensible.
 190         * In particular, we can't make a URL without a protocol field.
 191         * Without either a host or pathname (depending on the scheme),
 192         * we have no primary key. And without a username and password,
 193         * we are not actually storing a credential.
 194         */
 195        if (!c->protocol || !(c->host || c->path) ||
 196            !c->username || !c->password)
 197                return EXIT_FAILURE;
 198
 199        object = keyring_object(c);
 200
 201        result = gnome_keyring_set_network_password_sync(
 202                                GNOME_KEYRING_DEFAULT,
 203                                c->username,
 204                                NULL /* domain */,
 205                                c->host,
 206                                object,
 207                                c->protocol,
 208                                NULL /* authtype */,
 209                                c->port,
 210                                c->password,
 211                                &item_id);
 212
 213        g_free(object);
 214
 215        if (result != GNOME_KEYRING_RESULT_OK &&
 216            result != GNOME_KEYRING_RESULT_CANCELLED) {
 217                g_critical("%s", gnome_keyring_result_to_message(result));
 218                return EXIT_FAILURE;
 219        }
 220
 221        return EXIT_SUCCESS;
 222}
 223
 224static int keyring_erase(struct credential *c)
 225{
 226        char  *object = NULL;
 227        GList *entries;
 228        GnomeKeyringNetworkPasswordData *password_data;
 229        GnomeKeyringResult result;
 230
 231        /*
 232         * Sanity check that we actually have something to match
 233         * against. The input we get is a restrictive pattern,
 234         * so technically a blank credential means "erase everything".
 235         * But it is too easy to accidentally send this, since it is equivalent
 236         * to empty input. So explicitly disallow it, and require that the
 237         * pattern have some actual content to match.
 238         */
 239        if (!c->protocol && !c->host && !c->path && !c->username)
 240                return EXIT_FAILURE;
 241
 242        object = keyring_object(c);
 243
 244        result = gnome_keyring_find_network_password_sync(
 245                                c->username,
 246                                NULL /* domain */,
 247                                c->host,
 248                                object,
 249                                c->protocol,
 250                                NULL /* authtype */,
 251                                c->port,
 252                                &entries);
 253
 254        g_free(object);
 255
 256        if (result == GNOME_KEYRING_RESULT_NO_MATCH)
 257                return EXIT_SUCCESS;
 258
 259        if (result == GNOME_KEYRING_RESULT_CANCELLED)
 260                return EXIT_SUCCESS;
 261
 262        if (result != GNOME_KEYRING_RESULT_OK)
 263        {
 264                g_critical("%s", gnome_keyring_result_to_message(result));
 265                return EXIT_FAILURE;
 266        }
 267
 268        /* pick the first one from the list (delete all matches?) */
 269        password_data = (GnomeKeyringNetworkPasswordData *) entries->data;
 270
 271        result = gnome_keyring_item_delete_sync(
 272                password_data->keyring, password_data->item_id);
 273
 274        gnome_keyring_network_password_list_free(entries);
 275
 276        if (result != GNOME_KEYRING_RESULT_OK)
 277        {
 278                g_critical("%s", gnome_keyring_result_to_message(result));
 279                return EXIT_FAILURE;
 280        }
 281
 282        return EXIT_SUCCESS;
 283}
 284
 285/*
 286 * Table with helper operation callbacks, used by generic
 287 * credential helper main function.
 288 */
 289static struct credential_operation const credential_helper_ops[] =
 290{
 291        { "get",   keyring_get   },
 292        { "store", keyring_store },
 293        { "erase", keyring_erase },
 294        CREDENTIAL_OP_END
 295};
 296
 297/* ------------------ credential functions ------------------ */
 298
 299static void credential_init(struct credential *c)
 300{
 301        memset(c, 0, sizeof(*c));
 302}
 303
 304static void credential_clear(struct credential *c)
 305{
 306        g_free(c->protocol);
 307        g_free(c->host);
 308        g_free(c->path);
 309        g_free(c->username);
 310        gnome_keyring_memory_free(c->password);
 311
 312        credential_init(c);
 313}
 314
 315static int credential_read(struct credential *c)
 316{
 317        char    *buf;
 318        size_t line_len;
 319        char   *key;
 320        char   *value;
 321
 322        key = buf = gnome_keyring_memory_alloc(1024);
 323
 324        while (fgets(buf, 1024, stdin))
 325        {
 326                line_len = strlen(buf);
 327
 328                if (line_len && buf[line_len-1] == '\n')
 329                        buf[--line_len]='\0';
 330
 331                if (!line_len)
 332                        break;
 333
 334                value = strchr(buf,'=');
 335                if (!value) {
 336                        g_warning("invalid credential line: %s", key);
 337                        gnome_keyring_memory_free(buf);
 338                        return -1;
 339                }
 340                *value++ = '\0';
 341
 342                if (!strcmp(key, "protocol")) {
 343                        g_free(c->protocol);
 344                        c->protocol = g_strdup(value);
 345                } else if (!strcmp(key, "host")) {
 346                        g_free(c->host);
 347                        c->host = g_strdup(value);
 348                        value = strrchr(c->host,':');
 349                        if (value) {
 350                                *value++ = '\0';
 351                                c->port = atoi(value);
 352                        }
 353                } else if (!strcmp(key, "path")) {
 354                        g_free(c->path);
 355                        c->path = g_strdup(value);
 356                } else if (!strcmp(key, "username")) {
 357                        g_free(c->username);
 358                        c->username = g_strdup(value);
 359                } else if (!strcmp(key, "password")) {
 360                        gnome_keyring_memory_free(c->password);
 361                        c->password = gnome_keyring_memory_strdup(value);
 362                        while (*value) *value++ = '\0';
 363                }
 364                /*
 365                 * Ignore other lines; we don't know what they mean, but
 366                 * this future-proofs us when later versions of git do
 367                 * learn new lines, and the helpers are updated to match.
 368                 */
 369        }
 370
 371        gnome_keyring_memory_free(buf);
 372
 373        return 0;
 374}
 375
 376static void credential_write_item(FILE *fp, const char *key, const char *value)
 377{
 378        if (!value)
 379                return;
 380        fprintf(fp, "%s=%s\n", key, value);
 381}
 382
 383static void credential_write(const struct credential *c)
 384{
 385        /* only write username/password, if set */
 386        credential_write_item(stdout, "username", c->username);
 387        credential_write_item(stdout, "password", c->password);
 388}
 389
 390static void usage(const char *name)
 391{
 392        struct credential_operation const *try_op = credential_helper_ops;
 393        const char *basename = strrchr(name,'/');
 394
 395        basename = (basename) ? basename + 1 : name;
 396        fprintf(stderr, "usage: %s <", basename);
 397        while (try_op->name) {
 398                fprintf(stderr,"%s",(try_op++)->name);
 399                if (try_op->name)
 400                        fprintf(stderr,"%s","|");
 401        }
 402        fprintf(stderr,"%s",">\n");
 403}
 404
 405int main(int argc, char *argv[])
 406{
 407        int ret = EXIT_SUCCESS;
 408
 409        struct credential_operation const *try_op = credential_helper_ops;
 410        struct credential                  cred   = CREDENTIAL_INIT;
 411
 412        if (!argv[1]) {
 413                usage(argv[0]);
 414                exit(EXIT_FAILURE);
 415        }
 416
 417        g_set_application_name("Git Credential Helper");
 418
 419        /* lookup operation callback */
 420        while (try_op->name && strcmp(argv[1], try_op->name))
 421                try_op++;
 422
 423        /* unsupported operation given -- ignore silently */
 424        if (!try_op->name || !try_op->op)
 425                goto out;
 426
 427        ret = credential_read(&cred);
 428        if (ret)
 429                goto out;
 430
 431        /* perform credential operation */
 432        ret = (*try_op->op)(&cred);
 433
 434        credential_write(&cred);
 435
 436out:
 437        credential_clear(&cred);
 438        return ret;
 439}