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