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