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