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