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