d8a7038a6e938bf2338cc5d26fb7e905ef5972e1
   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[1024];
 293        size_t line_len;
 294        char   *key      = buf;
 295        char   *value;
 296
 297        while (fgets(buf, sizeof(buf), stdin))
 298        {
 299                line_len = strlen(buf);
 300
 301                if (line_len && buf[line_len-1] == '\n')
 302                        buf[--line_len]='\0';
 303
 304                if (!line_len)
 305                        break;
 306
 307                value = strchr(buf,'=');
 308                if (!value) {
 309                        warning("invalid credential line: %s", key);
 310                        return -1;
 311                }
 312                *value++ = '\0';
 313
 314                if (!strcmp(key, "protocol")) {
 315                        free(c->protocol);
 316                        c->protocol = xstrdup(value);
 317                } else if (!strcmp(key, "host")) {
 318                        free(c->host);
 319                        c->host = xstrdup(value);
 320                        value = strrchr(c->host,':');
 321                        if (value) {
 322                                *value++ = '\0';
 323                                c->port = atoi(value);
 324                        }
 325                } else if (!strcmp(key, "path")) {
 326                        free(c->path);
 327                        c->path = xstrdup(value);
 328                } else if (!strcmp(key, "username")) {
 329                        free(c->username);
 330                        c->username = xstrdup(value);
 331                } else if (!strcmp(key, "password")) {
 332                        gnome_keyring_memory_free(c->password);
 333                        c->password = gnome_keyring_memory_strdup(value);
 334                        while (*value) *value++ = '\0';
 335                }
 336                /*
 337                 * Ignore other lines; we don't know what they mean, but
 338                 * this future-proofs us when later versions of git do
 339                 * learn new lines, and the helpers are updated to match.
 340                 */
 341        }
 342        return 0;
 343}
 344
 345static void credential_write_item(FILE *fp, const char *key, const char *value)
 346{
 347        if (!value)
 348                return;
 349        fprintf(fp, "%s=%s\n", key, value);
 350}
 351
 352static void credential_write(const struct credential *c)
 353{
 354        /* only write username/password, if set */
 355        credential_write_item(stdout, "username", c->username);
 356        credential_write_item(stdout, "password", c->password);
 357}
 358
 359static void usage(const char *name)
 360{
 361        struct credential_operation const *try_op = credential_helper_ops;
 362        const char *basename = strrchr(name,'/');
 363
 364        basename = (basename) ? basename + 1 : name;
 365        fprintf(stderr, "usage: %s <", basename);
 366        while (try_op->name) {
 367                fprintf(stderr,"%s",(try_op++)->name);
 368                if (try_op->name)
 369                        fprintf(stderr,"%s","|");
 370        }
 371        fprintf(stderr,"%s",">\n");
 372}
 373
 374int main(int argc, char *argv[])
 375{
 376        int ret = EXIT_SUCCESS;
 377
 378        struct credential_operation const *try_op = credential_helper_ops;
 379        struct credential                  cred   = CREDENTIAL_INIT;
 380
 381        if (!argv[1]) {
 382                usage(argv[0]);
 383                exit(EXIT_FAILURE);
 384        }
 385
 386        g_set_application_name("Git Credential Helper");
 387
 388        /* lookup operation callback */
 389        while (try_op->name && strcmp(argv[1], try_op->name))
 390                try_op++;
 391
 392        /* unsupported operation given -- ignore silently */
 393        if (!try_op->name || !try_op->op)
 394                goto out;
 395
 396        ret = credential_read(&cred);
 397        if (ret)
 398                goto out;
 399
 400        /* perform credential operation */
 401        ret = (*try_op->op)(&cred);
 402
 403        credential_write(&cred);
 404
 405out:
 406        credential_clear(&cred);
 407        return ret;
 408}