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