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