{"action":"created","comment":{"url":"https://api.github.com/repos/krb5/krb5/pulls/comments/185112932","pull_request_review_id":116419680,"id":185112932,"diff_hunk":"@@ -0,0 +1,1142 @@\n+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */\n+/* plugins/kdb/lmdb/klmdb.c - KDB module using LMDB */\n+/*\n+ * Copyright (C) 2018 by the Massachusetts Institute of Technology.\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or without\n+ * modification, are permitted provided that the following conditions\n+ * are met:\n+ *\n+ * * Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * * Redistributions in binary form must reproduce the above copyright\n+ *   notice, this list of conditions and the following disclaimer in\n+ *   the documentation and/or other materials provided with the\n+ *   distribution.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n+ * \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\n+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\n+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,\n+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED\n+ * OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+\n+/*\n+ * Thread-safety note: unlike the other two in-tree KDB modules, this module\n+ * performs no mutex locking to ensure thread safety.  As the KDC and kadmind\n+ * are single-threaded, and applications are not allowed to access the same\n+ * krb5_context in multiple threads simultaneously, there is no current need\n+ * for this code to be thread-safe.  If a need arises in the future, mutex\n+ * locking should be added around the read_txn and load_txn fields of\n+ * lmdb_context to ensure that only one thread at a time accesses those\n+ * transactions.\n+ */\n+\n+/*\n+ * This KDB module stores principal and policy data using LMDB (Lightning\n+ * Memory-Mapped Database).  We use two LMDB environments, the first to hold\n+ * the majority of principal and policy data (suffix \".mdb\") in the \"principal\"\n+ * and \"policy\" databases, and the second to hold the three non-replicated\n+ * account lockout attributes (suffix \".lockout.mdb\") in the \"lockout\"\n+ * database.  The KDC only needs to write to the lockout database.\n+ *\n+ * For iteration we create a read transaction in the main environment for the\n+ * cursor.  Because the iteration callback might need to create its own\n+ * transactions for write operations (e.g. for kdb5_util\n+ * update_princ_encryption), we set the MDB_NOTLS flag on the main environment,\n+ * so that a thread can hold multiple transactions.\n+ *\n+ * To mitigate the overhead from MDB_NOTLS, we keep around a read_txn handles\n+ * in the database context for get operations, using mdb_txn_reset() and\n+ * mdb_txn_renew() between calls.\n+ *\n+ * For database loads, kdb5_util calls the create() method with the \"temporary\"\n+ * db_arg, and then promote the finished contents at the end with the\n+ * promote_db() method.  In this case we create or open the same LMDB\n+ * environments as above, open a write_txn handle for the lifetime of the\n+ * context, and empty out the principal and policy databases.  On promote_db()\n+ * we commit the transaction.  We do not empty the lockout database and write\n+ * to it non-transactionally during the load so that we don't block writes by\n+ * the KDC; this isn't ideal if the load is aborted, but it shouldn't cause any\n+ * practical issues.\n+ *\n+ * For iprop loads, kdb5_util also includes the \"merge_nra\" db_arg, signifying\n+ * that the lockout attributes from existing principal entries should be\n+ * preserved.  This attribute is noted in the LMDB context, and put_principal\n+ * operations will not write to the lockout database if an existing lockout\n+ * entry is already present for the principal.\n+ */\n+\n+#include \"k5-int.h\"\n+#include <kadm5/admin.h>\n+#include \"kdb5.h\"\n+#include \"klmdb-int.h\"\n+#include \"adb_err.h\"\n+#include <lmdb.h>\n+\n+/* The presence of any of these mask bits indicates a change to one of the\n+ * three principal lockout attributes. */\n+#define LOCKOUT_MASK (KADM5_LAST_SUCCESS | KADM5_LAST_FAILED |  \\\n+                      KADM5_FAIL_AUTH_COUNT)\n+\n+/* The default map size (for both environments) in megabytes. */\n+#define DEFAULT_MAPSIZE 128\n+\n+typedef struct {\n+    char *path;\n+    char *lockout_path;\n+    krb5_boolean temporary;     /* save changes until promote_db */\n+    krb5_boolean merge_nra;     /* preserve existing lockout attributes */\n+    krb5_boolean disable_last_success;\n+    krb5_boolean disable_lockout;\n+    krb5_boolean nosync;\n+    size_t mapsize;\n+    unsigned int maxreaders;\n+\n+    MDB_env *env;\n+    MDB_env *lockout_env;\n+    MDB_dbi princ_db;\n+    MDB_dbi policy_db;\n+    MDB_dbi lockout_db;\n+\n+    /* Used for get operations; each transaction is short-lived but we save the\n+     * handle between calls to reduce overhead from MDB_NOTLS. */\n+    MDB_txn *read_txn;\n+\n+    /* Write transaction for load operations (create() with the \"temporary\"\n+     * db_arg).  */\n+    MDB_txn *load_txn;\n+} klmdb_context;\n+\n+/* Cursor structure for ctx_iterate() */\n+typedef struct iter_curs {\n+    MDB_val key;\n+    MDB_val data;\n+    MDB_val keycopy;\n+    unsigned int startflag;\n+    unsigned int stepflag;\n+    krb5_context ctx;\n+    klmdb_context *dbc;\n+    int lockmode;\n+    krb5_boolean islocked;\n+} klmdb_iter_curs;\n+\n+static krb5_error_code\n+klerr(krb5_context context, int err, const char *msg)\n+{\n+    krb5_error_code ret;\n+    klmdb_context *dbc = context->dal_handle->db_context;\n+\n+    /* Pass through system errors; map MDB errors to a com_err code. */\n+    ret = (err > 0) ? err : KRB5_KDB_ACCESS_ERROR;\n+\n+    k5_setmsg(context, ret, _(\"%s (path: %s): %s\"), msg, dbc->path,\n+              mdb_strerror(err));\n+    return ret;\n+}\n+\n+/* Using db_args and the profile, create a DB context inside context and\n+ * initialize its configurable parameters. */\n+static krb5_error_code\n+configure_context(krb5_context context, char *conf_section, char **db_args)\n+{\n+    krb5_error_code ret;\n+    klmdb_context *dbc;\n+    char *pval = NULL;\n+    const char *path = NULL;\n+    profile_t profile = context->profile;\n+    int i, bval, ival;\n+\n+    dbc = k5alloc(sizeof(*dbc), &ret);\n+    if (dbc == NULL)\n+        return ret;\n+    context->dal_handle->db_context = dbc;\n+\n+    for (i = 0; db_args != NULL && db_args[i] != NULL; i++) {\n+        if (strcmp(db_args[i], \"temporary\") == 0) {\n+            dbc->temporary = TRUE;\n+        } else if (strcmp(db_args[i], \"merge_nra\") == 0) {\n+            dbc->merge_nra = TRUE;\n+        } else if (strncmp(db_args[i], \"dbname=\", 7) == 0) {\n+            path = db_args[i] + 8;\n+        } else {\n+            ret = EINVAL;\n+            k5_setmsg(context, ret, _(\"Unsupported argument \\\"%s\\\" for LMDB\"),\n+                      db_args[i]);\n+            goto cleanup;\n+        }\n+    }\n+\n+    if (path == NULL) {\n+        /* Check for database_name in the db_module section. */\n+        ret = profile_get_string(profile, KDB_MODULE_SECTION, conf_section,\n+                                 KRB5_CONF_DATABASE_NAME, NULL, &pval);\n+        if (!ret && pval == NULL) {\n+            /* For compatibility, check for database_name in the realm. */\n+            ret = profile_get_string(profile, KDB_REALM_SECTION,\n+                                     KRB5_DB_GET_REALM(context),\n+                                     KRB5_CONF_DATABASE_NAME, DEFAULT_KDB_FILE,\n+                                     &pval);\n+        }\n+        if (ret)\n+            goto cleanup;\n+        path = pval;\n+    }\n+\n+    if (asprintf(&dbc->path, \"%s.mdb\", path) < 0) {\n+        dbc->path = NULL;\n+        ret = ENOMEM;\n+        goto cleanup;\n+    }\n+    if (asprintf(&dbc->lockout_path, \"%s.lockout.mdb\", path) < 0) {\n+        dbc->lockout_path = NULL;\n+        ret = ENOMEM;\n+        goto cleanup;\n+    }\n+\n+    ret = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,\n+                              KRB5_CONF_DISABLE_LAST_SUCCESS, FALSE, &bval);\n+    if (ret)\n+        goto cleanup;\n+    dbc->disable_last_success = bval;\n+\n+    ret = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,\n+                              KRB5_CONF_DISABLE_LOCKOUT, FALSE, &bval);\n+    if (ret)\n+        goto cleanup;\n+    dbc->disable_lockout = bval;\n+\n+    ret = profile_get_integer(profile, KDB_MODULE_SECTION, conf_section,\n+                              KRB5_CONF_MAPSIZE, DEFAULT_MAPSIZE, &ival);\n+    if (ret)\n+        goto cleanup;\n+    dbc->mapsize = (size_t)ival * 1024 * 1024;\n+\n+    ret = profile_get_integer(profile, KDB_MODULE_SECTION, conf_section,\n+                              KRB5_CONF_MAX_READERS, 0, &ival);\n+    if (ret)\n+        goto cleanup;\n+    dbc->maxreaders = ival;\n+\n+    ret = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,\n+                              KRB5_CONF_NOSYNC, FALSE, &bval);\n+    if (ret)\n+        goto cleanup;\n+    dbc->nosync = bval;\n+\n+cleanup:\n+    profile_release_string(pval);\n+    return ret;\n+}\n+\n+static krb5_error_code\n+open_lmdb_env(krb5_context context, klmdb_context *dbc,\n+              krb5_boolean is_lockout, krb5_boolean readonly,\n+              MDB_env **env_out)\n+{\n+    krb5_error_code ret;\n+    const char *path = is_lockout ? dbc->lockout_path : dbc->path;\n+    unsigned int flags;\n+    MDB_env *env = NULL;\n+    int err;\n+\n+    *env_out = NULL;\n+\n+    err = mdb_env_create(&env);\n+    if (err)\n+        goto lmdb_error;\n+\n+    /* Use a pair of files instead of a subdirectory. */\n+    flags = MDB_NOSUBDIR;\n+\n+    /*\n+     * For the primary database, tie read transaction locktable slots to the\n+     * transaction and not the thread, so read transactions for iteration\n+     * cursors can coexist with short-lived transactions for operations invoked\n+     * by the iteration callback..\n+     */\n+    if (!is_lockout)\n+        flags |= MDB_NOTLS;\n+\n+    if (readonly)\n+        flags |= MDB_RDONLY;\n+\n+    /* Durability for lockout records is never worth the performance penalty.\n+     * For the primary environment it might be, so we make it configurable. */\n+    if (is_lockout || dbc->nosync)\n+        flags |= MDB_NOSYNC;\n+\n+    err = mdb_env_set_maxdbs(env, is_lockout ? 1 : 2);\n+    if (err)\n+        goto lmdb_error;\n+\n+    if (dbc->mapsize) {\n+        err = mdb_env_set_mapsize(env, dbc->mapsize);\n+        if (err)\n+            goto lmdb_error;\n+    }\n+\n+    if (dbc->maxreaders) {\n+        err = mdb_env_set_maxreaders(env, dbc->maxreaders);\n+        if (err)\n+            goto lmdb_error;\n+    }\n+\n+    err = mdb_env_open(env, path, flags, S_IRUSR | S_IWUSR);\n+    if (err)\n+        goto lmdb_error;\n+\n+    *env_out = env;\n+    return 0;\n+\n+lmdb_error:\n+    ret = klerr(context, err, _(\"LMDB environment open failure\"));\n+    mdb_env_close(env);\n+    return ret;\n+}\n+\n+/* Read a key from the primary environment, using a saved read transaction from\n+ * the database context.  Return KRB5_KDB_NOENTRY if the key is not found. */\n+static krb5_error_code\n+fetch(krb5_context context, MDB_dbi db, MDB_val *key, MDB_val *val_out)\n+{\n+    krb5_error_code ret = 0;\n+    klmdb_context *dbc = context->dal_handle->db_context;\n+    int err;\n+\n+    if (dbc->read_txn == NULL)\n+        err = mdb_txn_begin(dbc->env, NULL, MDB_RDONLY, &dbc->read_txn);\n+    else\n+        err = mdb_txn_renew(dbc->read_txn);\n+\n+    if (!err)\n+        err = mdb_get(dbc->read_txn, db, key, val_out);\n+\n+    if (err == MDB_NOTFOUND)\n+        ret = KRB5_KDB_NOENTRY;\n+    else if (err)\n+        ret = klerr(context, err, _(\"LMDB read failure\"));\n+\n+    mdb_txn_reset(dbc->read_txn);\n+    return ret;\n+}\n+\n+/* If we are using a lockout database, try to fetch the lockout attributes for\n+ * key and set them in entry. */\n+static void\n+fetch_lockout(krb5_context context, MDB_val *key, krb5_db_entry *entry)\n+{\n+    klmdb_context *dbc = context->dal_handle->db_context;\n+    MDB_txn *txn = NULL;\n+    MDB_val val;\n+    int err;\n+\n+    if (dbc->lockout_env == NULL)\n+        return;\n+    err = mdb_txn_begin(dbc->lockout_env, NULL, MDB_RDONLY, &txn);\n+    if (!err)\n+        err = mdb_get(txn, dbc->lockout_db, key, &val);\n+    if (!err && val.mv_size >= LOCKOUT_RECORD_LEN)\n+        klmdb_decode_princ_lockout(context, entry, val.mv_data);\n+    mdb_txn_abort(txn);\n+}\n+\n+/*\n+ * Store a value for key in the specified database within the primary\n+ * environment.  Use the saved load transaction if one is present, or a\n+ * temporary write transaction if not.  If no_overwrite is true and the key\n+ * already exists, return KRB5_KDB_INUSE.  If must_overwrite is true and the\n+ * key does not already exist, return KRB5_KDB_NOENTRY.\n+ */\n+static krb5_error_code\n+put(krb5_context context, MDB_dbi db, char *keystr, uint8_t *bytes, size_t len,\n+    krb5_boolean no_overwrite, krb5_boolean must_overwrite)\n+{\n+    klmdb_context *dbc = context->dal_handle->db_context;\n+    unsigned int putflags = no_overwrite ? MDB_NOOVERWRITE : 0;\n+    MDB_txn *temp_txn = NULL, *txn;\n+    MDB_val key = { strlen(keystr), keystr }, val = { len, bytes }, dummy;\n+    int err;\n+\n+    if (dbc->load_txn != NULL) {\n+        txn = dbc->load_txn;\n+    } else {\n+        err = mdb_txn_begin(dbc->env, NULL, 0, &temp_txn);\n+        if (err)\n+            goto error;\n+        txn = temp_txn;\n+    }\n+\n+    if (must_overwrite && mdb_get(txn, db, &key, &dummy) == MDB_NOTFOUND) {\n+        mdb_txn_abort(temp_txn);\n+        return KRB5_KDB_NOENTRY;\n+    }\n+\n+    err = mdb_put(txn, db, &key, &val, putflags);\n+    if (err)\n+        goto error;\n+\n+    if (temp_txn != NULL) {\n+        err = mdb_txn_commit(temp_txn);\n+        temp_txn = NULL;\n+        if (err)\n+            goto error;\n+    }\n+\n+    return 0;\n+\n+error:\n+    mdb_txn_abort(temp_txn);\n+    if (err == MDB_KEYEXIST)\n+        return KRB5_KDB_INUSE;\n+    else\n+        return klerr(context, err, _(\"LMDB write failure\"));\n+}\n+\n+/* Delete an entry from the specified env and database, using a temporary write\n+ * transaction.  Return KRB5_KDB_NOENTRY if the key does not exist. */\n+static krb5_error_code\n+del(krb5_context context, MDB_env *env, MDB_dbi db, char *keystr)\n+{\n+    krb5_error_code ret = 0;\n+    MDB_txn *txn;\n+    MDB_val key = { strlen(keystr), keystr };\n+    int err;\n+\n+    err = mdb_txn_begin(env, NULL, 0, &txn);\n+    if (!err)\n+        err = mdb_del(txn, db, &key, NULL);\n+    if (!err) {\n+        err = mdb_txn_commit(txn);\n+        txn = NULL;\n+    }\n+\n+    if (err == MDB_NOTFOUND)\n+        ret = KRB5_KDB_NOENTRY;\n+    else if (err)\n+        ret = klerr(context, err, _(\"LMDB delete failure\"));\n+\n+    mdb_txn_abort(txn);\n+    return ret;\n+}\n+\n+/* Zero out and unlink filename. */\n+static krb5_error_code\n+destroy_file(const char *filename)\n+{\n+    krb5_error_code ret;\n+    struct stat st;\n+    ssize_t len;\n+    off_t pos;\n+    uint8_t buf[BUFSIZ], zbuf[BUFSIZ] = { 0 };\n+    int fd;\n+\n+    fd = open(filename, O_RDWR, 0);\n+    if (fd < 0)\n+        return errno;\n+    set_cloexec_fd(fd);\n+    if (fstat(fd, &st) == -1)\n+        goto error;\n+\n+    memset(zbuf, 0, BUFSIZ);\n+    pos = 0;\n+    while (pos < st.st_size) {\n+        len = read(fd, buf, BUFSIZ);\n+        if (len < 0)\n+            goto error;\n+        /* Only rewrite the block if it's not already zeroed, in case the file\n+         * is sparse. */\n+        if (memcmp(buf, zbuf, len) != 0) {\n+            (void)lseek(fd, pos, SEEK_SET);\n+            len = write(fd, zbuf, len);\n+            if (len < 0)\n+                goto error;\n+        }\n+        pos += len;\n+    }\n+    close(fd);\n+\n+    if (unlink(filename) != 0)\n+        return errno;\n+    return 0;\n+\n+error:\n+    ret = errno;\n+    close(fd);\n+    return ret;\n+}\n+\n+static krb5_error_code\n+klmdb_lib_init()\n+{\n+    return 0;\n+}\n+\n+static krb5_error_code\n+klmdb_lib_cleanup()\n+{\n+    return 0;\n+}\n+\n+static krb5_error_code\n+klmdb_fini(krb5_context context)\n+{\n+    klmdb_context *dbc;\n+\n+    if (context->dal_handle->db_context == NULL)","path":"src/plugins/kdb/lmdb/kdb_lmdb.c","position":495,"original_position":495,"commit_id":"37342c6837eb306c4ea1e775ac832e432a54b580","original_commit_id":"306d86a15945bb75e0f1a88c4299533f11129b60","user":{"login":"frozencemetery","id":468294,"avatar_url":"https://avatars3.githubusercontent.com/u/468294?v=4","gravatar_id":"","url":"https://api.github.com/users/frozencemetery","html_url":"https://github.com/frozencemetery","followers_url":"https://api.github.com/users/frozencemetery/followers","following_url":"https://api.github.com/users/frozencemetery/following{/other_user}","gists_url":"https://api.github.com/users/frozencemetery/gists{/gist_id}","starred_url":"https://api.github.com/users/frozencemetery/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/frozencemetery/subscriptions","organizations_url":"https://api.github.com/users/frozencemetery/orgs","repos_url":"https://api.github.com/users/frozencemetery/repos","events_url":"https://api.github.com/users/frozencemetery/events{/privacy}","received_events_url":"https://api.github.com/users/frozencemetery/received_events","type":"User","site_admin":false},"body":"Can just be `if (dbc == NULL)` if the assignment is moved above this check.","created_at":"2018-04-30T21:15:56Z","updated_at":"2018-04-30T21:55:35Z","html_url":"https://github.com/krb5/krb5/pull/772#discussion_r185112932","pull_request_url":"https://api.github.com/repos/krb5/krb5/pulls/772","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/krb5/krb5/pulls/comments/185112932"},"html":{"href":"https://github.com/krb5/krb5/pull/772#discussion_r185112932"},"pull_request":{"href":"https://api.github.com/repos/krb5/krb5/pulls/772"}}},"pull_request":{"url":"https://api.github.com/repos/krb5/krb5/pulls/772","id":184750951,"html_url":"https://github.com/krb5/krb5/pull/772","diff_url":"https://github.com/krb5/krb5/pull/772.diff","patch_url":"https://github.com/krb5/krb5/pull/772.patch","issue_url":"https://api.github.com/repos/krb5/krb5/issues/772","number":772,"state":"open","locked":false,"title":"LMDB KDB module","user":{"login":"greghudson","id":1131421,"avatar_url":"https://avatars2.githubusercontent.com/u/1131421?v=4","gravatar_id":"","url":"https://api.github.com/users/greghudson","html_url":"https://github.com/greghudson","followers_url":"https://api.github.com/users/greghudson/followers","following_url":"https://api.github.com/users/greghudson/following{/other_user}","gists_url":"https://api.github.com/users/greghudson/gists{/gist_id}","starred_url":"https://api.github.com/users/greghudson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/greghudson/subscriptions","organizations_url":"https://api.github.com/users/greghudson/orgs","repos_url":"https://api.github.com/users/greghudson/repos","events_url":"https://api.github.com/users/greghudson/events{/privacy}","received_events_url":"https://api.github.com/users/greghudson/received_events","type":"User","site_admin":false},"body":"The testing commit has a lot of churn, particularly in t_iprop.py where a lot of existing code needed to be indented one level.  Reviewing it with git diff -w might be easiest.  I chose not to make the kadmin/gssrpc tests honor the K5TEST_LMDB environment variable because the necessary shell code wouldn't have been as simple as I would have liked, and because I think they don't add much to DB code coverage.\r\n\r\nI wasn't sure whether to add a new admin doc section for KDB types and adding a subsection to database.rst.  I went with a new section for now, partly because I don't like the current contents of database.rst (it copies a bunch of reference material with ``.. include`` directives) and therefore couldn't figure out where the new material would best fit.\r\n\r\nSome notes on places where it might be possible to factor out similar code but I decided against it:\r\n\r\n* The lockout code is duplicated among the three KDB modules.  I filed issue 8667 about this; we could make small libkdb5 helper functions but we can't factor out the majority of the code.\r\n\r\n* destroy_file() is duplicated with DB2 (although the version here is simplified a bit).  We could put it in kdb5.c but it would feel out of scope there.  Putting it in libkrb5support might be interesting (we could use it for kdestroy, for instance), but then we have to worry about whether it builds on Windows.\r\n\r\n* klmdb_iterate() and klmdb_iter_policy() have some repeated structure, but they have different callbacks and principal iteration has to fetch the lockout information and to support reverse iteration, so I didn't think a common function would work very naturally.\r\n\r\n* klmdb_open() and klmdb_create() wind up in similar states as far as open environments and DBs, but get there in sufficiently different ways that it didn't look feasible to make a common function.\r\n","created_at":"2018-04-28T00:00:30Z","updated_at":"2018-04-30T21:55:35Z","closed_at":null,"merged_at":null,"merge_commit_sha":"336b9e4caa0847a63bc445ffdc3328eae4d9d532","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/krb5/krb5/pulls/772/commits","review_comments_url":"https://api.github.com/repos/krb5/krb5/pulls/772/comments","review_comment_url":"https://api.github.com/repos/krb5/krb5/pulls/comments{/number}","comments_url":"https://api.github.com/repos/krb5/krb5/issues/772/comments","statuses_url":"https://api.github.com/repos/krb5/krb5/statuses/37342c6837eb306c4ea1e775ac832e432a54b580","head":{"label":"greghudson:lmdb","ref":"lmdb","sha":"37342c6837eb306c4ea1e775ac832e432a54b580","user":{"login":"greghudson","id":1131421,"avatar_url":"https://avatars2.githubusercontent.com/u/1131421?v=4","gravatar_id":"","url":"https://api.github.com/users/greghudson","html_url":"https://github.com/greghudson","followers_url":"https://api.github.com/users/greghudson/followers","following_url":"https://api.github.com/users/greghudson/following{/other_user}","gists_url":"https://api.github.com/users/greghudson/gists{/gist_id}","starred_url":"https://api.github.com/users/greghudson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/greghudson/subscriptions","organizations_url":"https://api.github.com/users/greghudson/orgs","repos_url":"https://api.github.com/users/greghudson/repos","events_url":"https://api.github.com/users/greghudson/events{/privacy}","received_events_url":"https://api.github.com/users/greghudson/received_events","type":"User","site_admin":false},"repo":{"id":4423904,"name":"krb5","full_name":"greghudson/krb5","owner":{"login":"greghudson","id":1131421,"avatar_url":"https://avatars2.githubusercontent.com/u/1131421?v=4","gravatar_id":"","url":"https://api.github.com/users/greghudson","html_url":"https://github.com/greghudson","followers_url":"https://api.github.com/users/greghudson/followers","following_url":"https://api.github.com/users/greghudson/following{/other_user}","gists_url":"https://api.github.com/users/greghudson/gists{/gist_id}","starred_url":"https://api.github.com/users/greghudson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/greghudson/subscriptions","organizations_url":"https://api.github.com/users/greghudson/orgs","repos_url":"https://api.github.com/users/greghudson/repos","events_url":"https://api.github.com/users/greghudson/events{/privacy}","received_events_url":"https://api.github.com/users/greghudson/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/greghudson/krb5","description":"mirror of MIT krb5 repository","fork":true,"url":"https://api.github.com/repos/greghudson/krb5","forks_url":"https://api.github.com/repos/greghudson/krb5/forks","keys_url":"https://api.github.com/repos/greghudson/krb5/keys{/key_id}","collaborators_url":"https://api.github.com/repos/greghudson/krb5/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/greghudson/krb5/teams","hooks_url":"https://api.github.com/repos/greghudson/krb5/hooks","issue_events_url":"https://api.github.com/repos/greghudson/krb5/issues/events{/number}","events_url":"https://api.github.com/repos/greghudson/krb5/events","assignees_url":"https://api.github.com/repos/greghudson/krb5/assignees{/user}","branches_url":"https://api.github.com/repos/greghudson/krb5/branches{/branch}","tags_url":"https://api.github.com/repos/greghudson/krb5/tags","blobs_url":"https://api.github.com/repos/greghudson/krb5/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/greghudson/krb5/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/greghudson/krb5/git/refs{/sha}","trees_url":"https://api.github.com/repos/greghudson/krb5/git/trees{/sha}","statuses_url":"https://api.github.com/repos/greghudson/krb5/statuses/{sha}","languages_url":"https://api.github.com/repos/greghudson/krb5/languages","stargazers_url":"https://api.github.com/repos/greghudson/krb5/stargazers","contributors_url":"https://api.github.com/repos/greghudson/krb5/contributors","subscribers_url":"https://api.github.com/repos/greghudson/krb5/subscribers","subscription_url":"https://api.github.com/repos/greghudson/krb5/subscription","commits_url":"https://api.github.com/repos/greghudson/krb5/commits{/sha}","git_commits_url":"https://api.github.com/repos/greghudson/krb5/git/commits{/sha}","comments_url":"https://api.github.com/repos/greghudson/krb5/comments{/number}","issue_comment_url":"https://api.github.com/repos/greghudson/krb5/issues/comments{/number}","contents_url":"https://api.github.com/repos/greghudson/krb5/contents/{+path}","compare_url":"https://api.github.com/repos/greghudson/krb5/compare/{base}...{head}","merges_url":"https://api.github.com/repos/greghudson/krb5/merges","archive_url":"https://api.github.com/repos/greghudson/krb5/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/greghudson/krb5/downloads","issues_url":"https://api.github.com/repos/greghudson/krb5/issues{/number}","pulls_url":"https://api.github.com/repos/greghudson/krb5/pulls{/number}","milestones_url":"https://api.github.com/repos/greghudson/krb5/milestones{/number}","notifications_url":"https://api.github.com/repos/greghudson/krb5/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/greghudson/krb5/labels{/name}","releases_url":"https://api.github.com/repos/greghudson/krb5/releases{/id}","deployments_url":"https://api.github.com/repos/greghudson/krb5/deployments","created_at":"2012-05-23T18:43:39Z","updated_at":"2015-01-06T02:06:38Z","pushed_at":"2018-04-29T04:50:19Z","git_url":"git://github.com/greghudson/krb5.git","ssh_url":"git@github.com:greghudson/krb5.git","clone_url":"https://github.com/greghudson/krb5.git","svn_url":"https://github.com/greghudson/krb5","homepage":null,"size":63194,"stargazers_count":3,"watchers_count":3,"language":"C","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"archived":false,"open_issues_count":0,"license":null,"forks":2,"open_issues":0,"watchers":3,"default_branch":"master"}},"base":{"label":"krb5:master","ref":"master","sha":"e1b5b824f5d7388a67d0854b56d3906c4fbdd778","user":{"login":"krb5","id":523797,"avatar_url":"https://avatars0.githubusercontent.com/u/523797?v=4","gravatar_id":"","url":"https://api.github.com/users/krb5","html_url":"https://github.com/krb5","followers_url":"https://api.github.com/users/krb5/followers","following_url":"https://api.github.com/users/krb5/following{/other_user}","gists_url":"https://api.github.com/users/krb5/gists{/gist_id}","starred_url":"https://api.github.com/users/krb5/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/krb5/subscriptions","organizations_url":"https://api.github.com/users/krb5/orgs","repos_url":"https://api.github.com/users/krb5/repos","events_url":"https://api.github.com/users/krb5/events{/privacy}","received_events_url":"https://api.github.com/users/krb5/received_events","type":"Organization","site_admin":false},"repo":{"id":4307702,"name":"krb5","full_name":"krb5/krb5","owner":{"login":"krb5","id":523797,"avatar_url":"https://avatars0.githubusercontent.com/u/523797?v=4","gravatar_id":"","url":"https://api.github.com/users/krb5","html_url":"https://github.com/krb5","followers_url":"https://api.github.com/users/krb5/followers","following_url":"https://api.github.com/users/krb5/following{/other_user}","gists_url":"https://api.github.com/users/krb5/gists{/gist_id}","starred_url":"https://api.github.com/users/krb5/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/krb5/subscriptions","organizations_url":"https://api.github.com/users/krb5/orgs","repos_url":"https://api.github.com/users/krb5/repos","events_url":"https://api.github.com/users/krb5/events{/privacy}","received_events_url":"https://api.github.com/users/krb5/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/krb5/krb5","description":"mirror of MIT krb5 repository","fork":false,"url":"https://api.github.com/repos/krb5/krb5","forks_url":"https://api.github.com/repos/krb5/krb5/forks","keys_url":"https://api.github.com/repos/krb5/krb5/keys{/key_id}","collaborators_url":"https://api.github.com/repos/krb5/krb5/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/krb5/krb5/teams","hooks_url":"https://api.github.com/repos/krb5/krb5/hooks","issue_events_url":"https://api.github.com/repos/krb5/krb5/issues/events{/number}","events_url":"https://api.github.com/repos/krb5/krb5/events","assignees_url":"https://api.github.com/repos/krb5/krb5/assignees{/user}","branches_url":"https://api.github.com/repos/krb5/krb5/branches{/branch}","tags_url":"https://api.github.com/repos/krb5/krb5/tags","blobs_url":"https://api.github.com/repos/krb5/krb5/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/krb5/krb5/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/krb5/krb5/git/refs{/sha}","trees_url":"https://api.github.com/repos/krb5/krb5/git/trees{/sha}","statuses_url":"https://api.github.com/repos/krb5/krb5/statuses/{sha}","languages_url":"https://api.github.com/repos/krb5/krb5/languages","stargazers_url":"https://api.github.com/repos/krb5/krb5/stargazers","contributors_url":"https://api.github.com/repos/krb5/krb5/contributors","subscribers_url":"https://api.github.com/repos/krb5/krb5/subscribers","subscription_url":"https://api.github.com/repos/krb5/krb5/subscription","commits_url":"https://api.github.com/repos/krb5/krb5/commits{/sha}","git_commits_url":"https://api.github.com/repos/krb5/krb5/git/commits{/sha}","comments_url":"https://api.github.com/repos/krb5/krb5/comments{/number}","issue_comment_url":"https://api.github.com/repos/krb5/krb5/issues/comments{/number}","contents_url":"https://api.github.com/repos/krb5/krb5/contents/{+path}","compare_url":"https://api.github.com/repos/krb5/krb5/compare/{base}...{head}","merges_url":"https://api.github.com/repos/krb5/krb5/merges","archive_url":"https://api.github.com/repos/krb5/krb5/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/krb5/krb5/downloads","issues_url":"https://api.github.com/repos/krb5/krb5/issues{/number}","pulls_url":"https://api.github.com/repos/krb5/krb5/pulls{/number}","milestones_url":"https://api.github.com/repos/krb5/krb5/milestones{/number}","notifications_url":"https://api.github.com/repos/krb5/krb5/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/krb5/krb5/labels{/name}","releases_url":"https://api.github.com/repos/krb5/krb5/releases{/id}","deployments_url":"https://api.github.com/repos/krb5/krb5/deployments","created_at":"2012-05-12T16:01:32Z","updated_at":"2018-04-29T05:40:15Z","pushed_at":"2018-04-29T04:34:36Z","git_url":"git://github.com/krb5/krb5.git","ssh_url":"git@github.com:krb5/krb5.git","clone_url":"https://github.com/krb5/krb5.git","svn_url":"https://github.com/krb5/krb5","homepage":null,"size":69591,"stargazers_count":169,"watchers_count":169,"language":"C","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":163,"mirror_url":null,"archived":false,"open_issues_count":15,"license":null,"forks":163,"open_issues":15,"watchers":169,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/krb5/krb5/pulls/772"},"html":{"href":"https://github.com/krb5/krb5/pull/772"},"issue":{"href":"https://api.github.com/repos/krb5/krb5/issues/772"},"comments":{"href":"https://api.github.com/repos/krb5/krb5/issues/772/comments"},"review_comments":{"href":"https://api.github.com/repos/krb5/krb5/pulls/772/comments"},"review_comment":{"href":"https://api.github.com/repos/krb5/krb5/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/krb5/krb5/pulls/772/commits"},"statuses":{"href":"https://api.github.com/repos/krb5/krb5/statuses/37342c6837eb306c4ea1e775ac832e432a54b580"}},"author_association":"OWNER"}}
{"actor":{"display_login":"frozencemetery"}}