# Version info\r
NETIDMGR_VERSION_MAJOR=1\r
NETIDMGR_VERSION_MINOR=1\r
-NETIDMGR_VERSION_PATCH=4\r
+NETIDMGR_VERSION_PATCH=6\r
NETIDMGR_VERSION_AUX=0\r
NETIDMGR_RELEASEDESC=\r
\r
# Version info\r
NETIDMGR_VERSION_MAJOR=1\r
NETIDMGR_VERSION_MINOR=1\r
-NETIDMGR_VERSION_PATCH=4\r
+NETIDMGR_VERSION_PATCH=6\r
NETIDMGR_VERSION_AUX=0\r
NETIDMGR_RELEASEDESC=\r
\r
kcdb_ident_sub = sub;\r
\r
if (kcdb_ident_sub)\r
- kmq_post_sub_msg(kcdb_ident_sub,\r
+ kmq_send_sub_msg(kcdb_ident_sub,\r
KMSG_IDENT,\r
KMSG_IDENT_INIT,\r
0,\r
\r
#include <string.h>\r
#include <time.h>\r
+#include <stdlib.h>\r
#include <assert.h>\r
#include <strsafe.h>\r
\r
} \r
#endif\r
\r
+/* we use these structures to keep track of identities that we find\r
+ while going through the API, FILE and MSLSA caches and enumerating\r
+ credentials. The only identities we want to keep track of are the\r
+ ones that have an initial ticket. We collect information for each\r
+ of the identities we find that we have initial tickets for and\r
+ then set the properties for the identities at once. */\r
+\r
+typedef struct tag_ident_data {\r
+ khm_handle ident; /* handle to the identity */\r
+ khm_int32 count; /* number of initial tickets we have\r
+ found for this identity. */\r
+ wchar_t ccname[MAX_PATH];\r
+ FILETIME ft_issue;\r
+ FILETIME ft_expire;\r
+ FILETIME ft_renewexpire;\r
+ khm_int32 krb5_flags;\r
+} ident_data;\r
+\r
+typedef struct tag_identlist {\r
+ ident_data * list;\r
+ khm_size n_list;\r
+ khm_size nc_list;\r
+} identlist;\r
+\r
+#define IDLIST_ALLOC_INCR 8\r
+\r
+static void\r
+tc_prep_idlist(identlist * idlist) {\r
+ idlist->list = NULL;\r
+ idlist->n_list = 0;\r
+ idlist->nc_list = 0;\r
+}\r
+\r
+static ident_data *\r
+tc_add_ident_to_list(identlist * idlist, khm_handle ident) {\r
+ khm_size i;\r
+ ident_data * d;\r
+\r
+ for (i=0; i < idlist->n_list; i++) {\r
+ if (kcdb_identity_is_equal(ident, idlist->list[i].ident))\r
+ break;\r
+ }\r
+\r
+ if (i < idlist->n_list) {\r
+ /* we already have this identity on our list. Increment the\r
+ count */\r
+ idlist->list[i].count++;\r
+ return &idlist->list[i];\r
+ }\r
+\r
+ /* it wasn't in our list. Add it */\r
+\r
+ if (idlist->n_list + 1 > idlist->nc_list) {\r
+ idlist->nc_list = UBOUNDSS(idlist->n_list + 1,\r
+ IDLIST_ALLOC_INCR,\r
+ IDLIST_ALLOC_INCR);\r
+#ifdef DEBUG\r
+ assert(idlist->n_list + 1 <= idlist->nc_list);\r
+#endif\r
+ idlist->list = PREALLOC(idlist->list,\r
+ sizeof(idlist->list[0]) * idlist->nc_list);\r
+#ifdef DEBUG\r
+ assert(idlist->list);\r
+#endif\r
+ ZeroMemory(&idlist->list[idlist->n_list],\r
+ sizeof(idlist->list[0]) *\r
+ (idlist->nc_list - idlist->n_list));\r
+ }\r
+\r
+ d = &idlist->list[idlist->n_list];\r
+\r
+ ZeroMemory(d, sizeof(*d));\r
+\r
+ d->ident = ident;\r
+ d->count = 1;\r
+\r
+ idlist->n_list++;\r
+\r
+ kcdb_identity_hold(ident);\r
+\r
+ return d;\r
+}\r
+\r
+static void\r
+tc_set_ident_data(identlist * idlist) {\r
+ khm_size i;\r
+\r
+ for (i=0; i < idlist->n_list; i++) {\r
+#ifdef DEBUG\r
+ assert(idlist->list[i].ident);\r
+#endif\r
+\r
+ kcdb_identity_set_attr(idlist->list[i].ident,\r
+ attr_id_krb5_ccname,\r
+ idlist->list[i].ccname,\r
+ KCDB_CBSIZE_AUTO);\r
+\r
+ kcdb_identity_set_attr(idlist->list[i].ident,\r
+ KCDB_ATTR_EXPIRE,\r
+ &idlist->list[i].ft_expire,\r
+ sizeof(idlist->list[i].ft_expire));\r
+\r
+ kcdb_identity_set_attr(idlist->list[i].ident,\r
+ KCDB_ATTR_ISSUE,\r
+ &idlist->list[i].ft_issue,\r
+ sizeof(idlist->list[i].ft_issue));\r
+\r
+ kcdb_identity_set_attr(idlist->list[i].ident,\r
+ attr_id_krb5_flags,\r
+ &idlist->list[i].krb5_flags,\r
+ sizeof(idlist->list[i].krb5_flags));\r
+\r
+ if (idlist->list[i].ft_renewexpire.dwLowDateTime == 0 &&\r
+ idlist->list[i].ft_renewexpire.dwHighDateTime == 0) {\r
+ kcdb_identity_set_attr(idlist->list[i].ident,\r
+ KCDB_ATTR_RENEW_EXPIRE,\r
+ NULL, 0);\r
+ } else {\r
+ kcdb_identity_set_attr(idlist->list[i].ident,\r
+ KCDB_ATTR_RENEW_EXPIRE,\r
+ &idlist->list[i].ft_renewexpire,\r
+ sizeof(idlist->list[i].ft_renewexpire));\r
+ }\r
+ }\r
+}\r
+\r
+static void\r
+tc_free_idlist(identlist * idlist) {\r
+ khm_size i;\r
+\r
+ for (i=0; i < idlist->n_list; i++) {\r
+ if (idlist->list[i].ident != NULL) {\r
+ kcdb_identity_release(idlist->list[i].ident);\r
+ idlist->list[i].ident = NULL;\r
+ }\r
+ }\r
+\r
+ if (idlist->list)\r
+ PFREE(idlist->list);\r
+ idlist->list = NULL;\r
+ idlist->n_list = 0;\r
+ idlist->nc_list = 0;\r
+}\r
+\r
#ifndef ENCTYPE_LOCAL_RC4_MD4\r
#define ENCTYPE_LOCAL_RC4_MD4 0xFFFFFF80\r
#endif\r
#define MAX_ADDRS 256\r
\r
static long get_tickets_from_cache(krb5_context ctx, \r
- krb5_ccache cache)\r
+ krb5_ccache cache,\r
+ identlist * idlist)\r
{\r
krb5_error_code code;\r
krb5_principal KRBv5Principal;\r
}\r
}\r
\r
+ _reportf(L"Getting tickets from cache [%s]", wcc_name);\r
+\r
if ((code = (*pkrb5_cc_set_flags)(ctx, cache, flags)))\r
{\r
if (code != KRB5_FCC_NOFILE && code != KRB5_CC_NOTFOUND)\r
goto _exit;\r
}\r
\r
+ _reportf(L"Found principal [%s]", wbuf);\r
+\r
(*pkrb5_free_principal)(ctx, KRBv5Principal);\r
\r
if ((code = (*pkrb5_cc_start_seq_get)(ctx, cache, &KRBv5Cursor))) \r
FILETIME ft_issue_new;\r
FILETIME ft_expire_old;\r
FILETIME ft_expire_new;\r
- khm_size cb;\r
+ ident_data * d;\r
\r
- /* an initial ticket! If we find one, we generally set\r
- the lifetime, and primary ccache based on this, but\r
- only if this initial cred has a greater lifetime than\r
- the current primary credential. */\r
+ /* an initial ticket! Add it to the list of identities we\r
+ have seen so far with initial tickets. */\r
+ d = tc_add_ident_to_list(idlist, ident);\r
+#ifdef DEBUG\r
+ assert(d);\r
+ assert(d->count > 0);\r
+#endif\r
\r
tt = KRBv5Credentials.times.endtime;\r
TimetToFileTime(tt, &ft_expire_new);\r
tt = KRBv5Credentials.times.starttime;\r
TimetToFileTime(tt, &ft_issue_new);\r
\r
- cb = sizeof(ft_expire_old);\r
- if(KHM_FAILED(kcdb_identity_get_attr(tident, \r
- KCDB_ATTR_EXPIRE, \r
- NULL, &ft_expire_old, \r
- &cb))\r
- || CompareFileTime(&ft_expire_new, &ft_expire_old) > 0) {\r
-\r
- kcdb_identity_set_attr(tident, attr_id_krb5_ccname, \r
- wcc_name, KCDB_CBSIZE_AUTO);\r
- kcdb_identity_set_attr(tident, KCDB_ATTR_EXPIRE, \r
- &ft_expire_new, \r
- sizeof(ft_expire_new));\r
- kcdb_identity_set_attr(tident, KCDB_ATTR_ISSUE,\r
- &ft_issue_new,\r
- sizeof(ft_issue_new));\r
+ /* so now, we have to set the properties of the identity\r
+ based on the properties of this credential under the\r
+ following circumstances:\r
+\r
+ - If this is the first time we are hitting this\r
+ identity.\r
+\r
+ - If this is not the MSLSA: cache and the expiry time\r
+ for this credential is longer than the time already\r
+ found for this identity.\r
+ */\r
+\r
+ ft_expire_old = d->ft_expire;\r
+\r
+ if(d->count == 1\r
+ || (CompareFileTime(&ft_expire_new, &ft_expire_old) > 0 &&\r
+ wcscmp(wcc_name, L"MSLSA:") != 0)) {\r
+\r
+ _reportf(L"Setting properties for identity (count=%d)", d->count);\r
+\r
+ StringCbCopy(d->ccname, sizeof(d->ccname),\r
+ wcc_name);\r
+ d->ft_expire = ft_expire_new;\r
+ d->ft_issue = ft_issue_new;\r
\r
if (KRBv5Credentials.times.renew_till > 0) {\r
tt = KRBv5Credentials.times.renew_till;\r
TimetToFileTime(tt, &ft);\r
- kcdb_identity_set_attr(tident, \r
- KCDB_ATTR_RENEW_EXPIRE, \r
- &ft, sizeof(ft));\r
+ d->ft_renewexpire = ft;\r
} else {\r
- kcdb_identity_set_attr(tident,\r
- KCDB_ATTR_RENEW_EXPIRE,\r
- NULL, 0);\r
+ ZeroMemory(&d->ft_renewexpire, sizeof(d->ft_renewexpire));\r
}\r
\r
- ti = KRBv5Credentials.ticket_flags;\r
- kcdb_identity_set_attr(tident, attr_id_krb5_flags, \r
- &ti, sizeof(ti));\r
+ d->krb5_flags = KRBv5Credentials.ticket_flags;\r
}\r
}\r
\r
khm_int32 t;\r
wchar_t * ms = NULL;\r
khm_size cb;\r
+ identlist idl;\r
\r
kcdb_credset_flush(krb5_credset);\r
+ tc_prep_idlist(&idl);\r
\r
if((*krbv5Context == 0) && (code = (*pkrb5_init_context)(krbv5Context))) {\r
goto _exit;\r
if (code)\r
continue;\r
\r
- code = get_tickets_from_cache(ctx, cache);\r
+ code = get_tickets_from_cache(ctx, cache, &idl);\r
\r
if(ctx != NULL && cache != NULL)\r
(*pkrb5_cc_close)(ctx, cache);\r
\r
_skip_cc_iter:\r
\r
- if (KHM_SUCCEEDED(khc_read_int32(csp_params, L"MsLsaList", &t)) && t) {\r
- code = (*pkrb5_cc_resolve)(ctx, "MSLSA:", &cache);\r
-\r
- if (code == 0 && cache) {\r
- code = get_tickets_from_cache(ctx, cache);\r
- }\r
-\r
- if (ctx != NULL && cache != NULL)\r
- (*pkrb5_cc_close)(ctx, cache);\r
- cache = 0;\r
- }\r
-\r
if (khc_read_multi_string(csp_params, L"FileCCList", NULL, &cb)\r
== KHM_ERROR_TOO_LONG &&\r
cb > sizeof(wchar_t) * 2) {\r
if (code)\r
continue;\r
\r
- code = get_tickets_from_cache(ctx, cache);\r
+ code = get_tickets_from_cache(ctx, cache, &idl);\r
\r
if (ctx != NULL && cache != NULL)\r
(*pkrb5_cc_close)(ctx, cache);\r
PFREE(ms);\r
}\r
\r
+ if (KHM_SUCCEEDED(khc_read_int32(csp_params, L"MsLsaList", &t)) && t) {\r
+ code = (*pkrb5_cc_resolve)(ctx, "MSLSA:", &cache);\r
+\r
+ if (code == 0 && cache) {\r
+ code = get_tickets_from_cache(ctx, cache, &idl);\r
+ }\r
+\r
+ if (ctx != NULL && cache != NULL)\r
+ (*pkrb5_cc_close)(ctx, cache);\r
+ cache = 0;\r
+ }\r
+\r
_exit:\r
if (pNCi)\r
(*pcc_free_NC_info)(cc_ctx, &pNCi);\r
(*pcc_shutdown)(&cc_ctx);\r
\r
kcdb_credset_collect(NULL, krb5_credset, NULL, credtype_id_krb5, NULL);\r
+ tc_set_ident_data(&idl);\r
+ tc_free_idlist(&idl);\r
\r
return(code);\r
}\r
krb5_principal server = NULL;\r
krb5_creds my_creds;\r
krb5_data *realm = NULL;\r
+ wchar_t idname[KCDB_IDENT_MAXCCH_NAME];\r
+ char cidname[KCDB_IDENT_MAXCCH_NAME];\r
+ khm_size cb;\r
\r
memset(&my_creds, 0, sizeof(krb5_creds));\r
\r
if ( !pkrb5_init_context )\r
goto cleanup;\r
\r
+ cb = sizeof(idname);\r
+ kcdb_identity_get_name(identity, idname, &cb);\r
+\r
+ if (khm_krb5_get_identity_flags(identity) & K5IDFLAG_IMPORTED) {\r
+ /* we are trying to renew the identity that was imported from\r
+ MSLSA: */\r
+ BOOL imported;\r
+\r
+ UnicodeStrToAnsi(cidname, sizeof(cidname), idname);\r
+\r
+ imported = khm_krb5_ms2mit(cidname, FALSE, TRUE);\r
+\r
+ if (imported)\r
+ goto cleanup;\r
+\r
+ /* if the import failed, then we try to renew the identity via\r
+ the usual procedure. */\r
+ }\r
+\r
code = khm_krb5_initialize(identity, &ctx, &cc);\r
if (code) \r
goto cleanup;\r
if (my_creds.server == server)\r
my_creds.server = NULL;\r
\r
- pkrb5_free_cred_contents(ctx, &my_creds);\r
+ if (ctx) {\r
+ pkrb5_free_cred_contents(ctx, &my_creds);\r
\r
- if (me)\r
- pkrb5_free_principal(ctx, me);\r
- if (server)\r
- pkrb5_free_principal(ctx, server);\r
- if (cc)\r
- pkrb5_cc_close(ctx, cc);\r
- if (ctx)\r
+ if (me)\r
+ pkrb5_free_principal(ctx, me);\r
+ if (server)\r
+ pkrb5_free_principal(ctx, server);\r
+ if (cc)\r
+ pkrb5_cc_close(ctx, cc);\r
pkrb5_free_context(ctx);\r
+ }\r
+\r
return(code);\r
}\r
\r
khm_handle d_cs = NULL;\r
khm_int32 rv = KHM_ERROR_SUCCESS;\r
khm_size s, cb;\r
- krb5_context ctx;\r
+ krb5_context ctx = NULL;\r
krb5_error_code code = 0;\r
int i;\r
wchar_t ccname[KRB5_MAXCCH_CCNAME];\r
if (d_cs)\r
kcdb_credset_delete(&d_cs);\r
\r
+ if (ctx != NULL)\r
+ pkrb5_free_context(ctx);\r
+\r
return rv;\r
}\r
\r
\r
\r
BOOL\r
-khm_krb5_ms2mit(BOOL save_creds)\r
+khm_krb5_ms2mit(char * match_princ, BOOL match_realm, BOOL save_creds)\r
{\r
#ifdef NO_KRB5\r
return(FALSE);\r
krb5_creds creds;\r
krb5_cc_cursor cursor=0;\r
krb5_principal princ = 0;\r
+ khm_handle ident = NULL;\r
+ wchar_t wname[KCDB_IDENT_MAXCCH_NAME];\r
+ char cname[KCDB_IDENT_MAXCCH_NAME];\r
char *cache_name = NULL;\r
char *princ_name = NULL;\r
BOOL rc = FALSE;\r
if (code = pkrb5_unparse_name(kcontext, princ, &princ_name))\r
goto cleanup;\r
\r
- kherr_reportf(L"Unparsed [%S]. Resolving target cache\n", princ_name);\r
- /* TODO: actually look up the preferred ccache name */\r
- if (code = pkrb5_cc_resolve(kcontext, princ_name, &ccache)) {\r
- kherr_reportf(L"Cannot resolve cache [%S] with code=%d. Trying default.\n", princ_name, code);\r
+ AnsiStrToUnicode(wname, sizeof(wname), princ_name);\r
+\r
+ kherr_reportf(L"Unparsed name [%s]", wname);\r
+\r
+ /* see if we have to match a specific principal */\r
+ if (match_princ != NULL) {\r
+ if (strcmp(princ_name, match_princ)) {\r
+ kherr_reportf(L"Principal mismatch. Wanted [%S], found [%S]",\r
+ match_princ, princ_name);\r
+ goto cleanup;\r
+ }\r
+ } else if (match_realm) {\r
+ wchar_t * wdefrealm;\r
+ char defrealm[256];\r
+ krb5_data * princ_realm;\r
+\r
+ wdefrealm = khm_krb5_get_default_realm();\r
+ if (wdefrealm == NULL) {\r
+ kherr_reportf(L"Can't determine default realm");\r
+ goto cleanup;\r
+ }\r
+\r
+ princ_realm = krb5_princ_realm(kcontext, princ);\r
+ UnicodeStrToAnsi(defrealm, sizeof(defrealm), wdefrealm);\r
+\r
+ if (strncmp(defrealm, princ_realm->data, princ_realm->length)) {\r
+ kherr_reportf(L"Realm mismatch. Wanted [%S], found [%*S]",\r
+ defrealm, princ_realm->length, princ_realm->data);\r
+ PFREE(wdefrealm);\r
+ goto cleanup;\r
+ }\r
+\r
+ PFREE(wdefrealm);\r
+ }\r
+\r
+ if (KHM_SUCCEEDED(kcdb_identity_create(wname,\r
+ KCDB_IDENT_FLAG_CREATE,\r
+ &ident))) {\r
+ khm_handle idconfig = NULL;\r
+ khm_handle k5config = NULL;\r
+ khm_size cb;\r
+\r
+ wname[0] = L'\0';\r
+\r
+ kcdb_identity_get_config(ident, 0, &idconfig);\r
+ if (idconfig == NULL)\r
+ goto _done_checking_config;\r
+\r
+ khc_open_space(idconfig, CSNAME_KRB5CRED, 0, &k5config);\r
+ if (k5config == NULL)\r
+ goto _done_checking_config;\r
+\r
+ cb = sizeof(wname);\r
+ khc_read_string(k5config,\r
+ L"DefaultCCName",\r
+ wname, &cb);\r
+\r
+ _done_checking_config:\r
+\r
+ if (idconfig)\r
+ khc_close_space(idconfig);\r
+ if (k5config)\r
+ khc_close_space(k5config);\r
+\r
+ if (wname[0]) {\r
+ UnicodeStrToAnsi(cname, sizeof(cname), wname);\r
+ } else {\r
+ StringCbPrintfA(cname, sizeof(cname), "API:%s", princ_name);\r
+ }\r
+\r
+ cache_name = cname;\r
+\r
+ } else {\r
+ /* the identity could not be created. we just use the\r
+ name of the principal as the ccache name. */\r
+ StringCbPrintfA(cname, sizeof(cname), "API:%s", princ_name);\r
+ cache_name = cname;\r
+ }\r
+\r
+ kherr_reportf(L"Resolving target cache [%S]\n", cache_name);\r
+\r
+ if (code = pkrb5_cc_resolve(kcontext, cache_name, &ccache)) {\r
+ kherr_reportf(L"Cannot resolve cache [%S] with code=%d. Trying default.\n", cache_name, code);\r
\r
if (code = pkrb5_cc_default(kcontext, &ccache)) {\r
kherr_reportf(L"Failed to resolve default ccache. Code=%d", code);\r
if (code = pkrb5_cc_copy_creds(kcontext, mslsa_ccache, ccache))\r
goto cleanup;\r
\r
+ /* and mark the identity as having been imported */\r
+ if (ident) {\r
+ khm_krb5_set_identity_flags(ident, K5IDFLAG_IMPORTED, K5IDFLAG_IMPORTED);\r
+ }\r
+\r
rc = TRUE;\r
} else {\r
/* Enumerate tickets from cache looking for an initial ticket */\r
\brief Get the default realm\r
\r
A string will be returned that specifies the default realm. The\r
- caller should free the string using free().\r
+ caller should free the string using PFREE().\r
\r
Returns NULL if the operation fails.\r
*/\r
else\r
return 0;\r
}\r
+\r
+void\r
+khm_krb5_set_identity_flags(khm_handle identity,\r
+ khm_int32 flag_mask,\r
+ khm_int32 flag_value) {\r
+\r
+ khm_int32 t = 0;\r
+ khm_size cb;\r
+\r
+ cb = sizeof(t);\r
+ if (KHM_FAILED(kcdb_identity_get_attr(identity,\r
+ attr_id_krb5_idflags,\r
+ NULL,\r
+ &t, &cb))) {\r
+ t = 0;\r
+ }\r
+\r
+ t &= ~flag_mask;\r
+ t |= flag_value | flag_mask;\r
+\r
+ kcdb_identity_set_attr(identity,\r
+ attr_id_krb5_idflags,\r
+ &t, sizeof(t));\r
+}\r
+\r
+khm_int32\r
+khm_krb5_get_identity_flags(khm_handle identity) {\r
+ khm_int32 t = 0;\r
+ khm_size cb;\r
+\r
+ cb = sizeof(t);\r
+ kcdb_identity_get_attr(identity,\r
+ attr_id_krb5_idflags,\r
+ NULL, &t, &cb);\r
+\r
+ return t;\r
+}\r
+\r
+long\r
+khm_krb5_get_temp_ccache(krb5_context ctx,\r
+ krb5_ccache * prcc) {\r
+ int rnd = rand();\r
+ char ccname[MAX_PATH];\r
+ long code = 0;\r
+ krb5_ccache cc = 0;\r
+\r
+ StringCbPrintfA(ccname, sizeof(ccname), "API:TempCache%8x", rnd);\r
+\r
+ code = pkrb5_cc_resolve(ctx, ccname, &cc);\r
+\r
+ if (code == 0)\r
+ *prcc = cc;\r
+\r
+ return code;\r
+}\r
// Function Prototypes.\r
\r
BOOL \r
-khm_krb5_ms2mit(BOOL);\r
+khm_krb5_ms2mit(char * match_princ,\r
+ BOOL match_realm,\r
+ BOOL save_creds);\r
\r
int\r
khm_krb5_kinit(krb5_context alt_ctx,\r
khm_int32 KHMAPI\r
khm_krb5_creds_is_equal(khm_handle vcred1, khm_handle vcred2, void * dummy);\r
\r
+void\r
+khm_krb5_set_identity_flags(khm_handle identity,\r
+ khm_int32 flag_mask,\r
+ khm_int32 flag_value);\r
+\r
+khm_int32\r
+khm_krb5_get_identity_flags(khm_handle identity);\r
+\r
+long\r
+khm_krb5_get_temp_ccache(krb5_context ctx,\r
+ krb5_ccache * cc);\r
#endif\r
khm_ui_4 uparam,\r
void * vparam) {\r
\r
- /* Logic for setting the default identity:\r
-\r
- When setting identity I as the default;\r
-\r
- - If KRB5CCNAME is set\r
- - If I["Krb5CCName"] == %KRB5CCNAME%\r
- - do nothing\r
- - Else\r
- - Copy the contents of I["Krb5CCName"] to %KRB5CCNAME\r
- - Set I["Krb5CCName"] to %KRB5CCNAME\r
- - Else\r
- - Set HKCU\Software\MIT\kerberos5,ccname to \r
- "API:".I["Krb5CCName"]\r
+ /* \r
+ Currently, setting the default identity simply sets the\r
+ "ccname" registry value at "Software\MIT\kerberos5".\r
*/\r
\r
if (uparam) {\r
/* an identity is being made default */\r
khm_handle def_ident = (khm_handle) vparam;\r
- wchar_t env_ccname[KRB5_MAXCCH_CCNAME];\r
wchar_t id_ccname[KRB5_MAXCCH_CCNAME];\r
khm_size cb;\r
DWORD dw;\r
LONG l;\r
+ HKEY hk_ccname;\r
+ DWORD dwType;\r
+ DWORD dwSize;\r
+ wchar_t reg_ccname[KRB5_MAXCCH_CCNAME];\r
+\r
+ assert(FALSE);\r
\r
#ifdef DEBUG\r
assert(def_ident != NULL);\r
#endif\r
\r
+ {\r
+ wchar_t idname[KCDB_IDENT_MAXCCH_NAME];\r
+ khm_size cb;\r
+\r
+ cb = sizeof(idname);\r
+ kcdb_identity_get_name(def_ident, idname, &cb);\r
+\r
+ _begin_task(0);\r
+ _report_cs1(KHERR_DEBUG_1, L"Setting default identity [%1!s!]", _cstr(idname));\r
+ _describe();\r
+ }\r
+\r
cb = sizeof(id_ccname);\r
if (KHM_FAILED(kcdb_identity_get_attr(def_ident,\r
attr_id_krb5_ccname,\r
NULL,\r
id_ccname,\r
- &cb)))\r
- return KHM_ERROR_UNKNOWN;\r
+ &cb))) {\r
+ _reportf(L"The specified identity does not have the Krb5CCName property");\r
+ _end_task();\r
+ return KHM_ERROR_NOT_FOUND;\r
+ }\r
\r
khm_krb5_canon_cc_name(id_ccname, sizeof(id_ccname));\r
\r
+ _reportf(L"Found Krb5CCName property : %s", id_ccname);\r
+\r
StringCbLength(id_ccname, sizeof(id_ccname), &cb);\r
cb += sizeof(wchar_t);\r
\r
- dw = GetEnvironmentVariable(L"KRB5CCNAME",\r
- env_ccname,\r
- ARRAYLENGTH(env_ccname));\r
-\r
- if (dw == 0 &&\r
- GetLastError() == ERROR_ENVVAR_NOT_FOUND) {\r
- /* KRB5CCNAME not set */\r
- HKEY hk_ccname;\r
- DWORD dwType;\r
- DWORD dwSize;\r
- wchar_t reg_ccname[KRB5_MAXCCH_CCNAME];\r
-\r
- l = RegOpenKeyEx(HKEY_CURRENT_USER,\r
- L"Software\\MIT\\kerberos5",\r
- 0,\r
- KEY_READ | KEY_WRITE,\r
- &hk_ccname);\r
-\r
- if (l != ERROR_SUCCESS)\r
- l = RegCreateKeyEx(HKEY_CURRENT_USER,\r
- L"Software\\MIT\\kerberos5",\r
- 0,\r
- NULL,\r
- REG_OPTION_NON_VOLATILE,\r
- KEY_READ | KEY_WRITE,\r
- NULL,\r
- &hk_ccname,\r
- &dw);\r
-\r
- if (l != ERROR_SUCCESS)\r
- return KHM_ERROR_UNKNOWN;\r
-\r
- dwSize = sizeof(reg_ccname);\r
+ _reportf(L"Setting default CC name in the registry");\r
+\r
+ l = RegOpenKeyEx(HKEY_CURRENT_USER,\r
+ L"Software\\MIT\\kerberos5",\r
+ 0,\r
+ KEY_READ | KEY_WRITE,\r
+ &hk_ccname);\r
+\r
+ if (l != ERROR_SUCCESS)\r
+ l = RegCreateKeyEx(HKEY_CURRENT_USER,\r
+ L"Software\\MIT\\kerberos5",\r
+ 0,\r
+ NULL,\r
+ REG_OPTION_NON_VOLATILE,\r
+ KEY_READ | KEY_WRITE,\r
+ NULL,\r
+ &hk_ccname,\r
+ &dw);\r
\r
- l = RegQueryValueEx(hk_ccname,\r
- L"ccname",\r
- NULL,\r
- &dwType,\r
- (LPBYTE) reg_ccname,\r
- &dwSize);\r
+ if (l != ERROR_SUCCESS) {\r
+ _reportf(L"Can't create registry key : %d", l);\r
+ _end_task();\r
+ return KHM_ERROR_UNKNOWN;\r
+ }\r
\r
- if (l != ERROR_SUCCESS ||\r
- dwType != REG_SZ ||\r
- khm_krb5_cc_name_cmp(reg_ccname, id_ccname)) {\r
+ dwSize = sizeof(reg_ccname);\r
\r
- /* we have to write the new value in */\r
+ l = RegQueryValueEx(hk_ccname,\r
+ L"ccname",\r
+ NULL,\r
+ &dwType,\r
+ (LPBYTE) reg_ccname,\r
+ &dwSize);\r
\r
- l = RegSetValueEx(hk_ccname,\r
- L"ccname",\r
- 0,\r
- REG_SZ,\r
- (BYTE *) id_ccname,\r
- (DWORD) cb);\r
- }\r
+ if (l != ERROR_SUCCESS ||\r
+ dwType != REG_SZ ||\r
+ khm_krb5_cc_name_cmp(reg_ccname, id_ccname)) {\r
\r
- RegCloseKey(hk_ccname);\r
+ /* we have to write the new value in */\r
+ \r
+ l = RegSetValueEx(hk_ccname,\r
+ L"ccname",\r
+ 0,\r
+ REG_SZ,\r
+ (BYTE *) id_ccname,\r
+ (DWORD) cb);\r
+ }\r
\r
- if (l == ERROR_SUCCESS) {\r
- k5_update_last_default_identity(def_ident);\r
- return KHM_ERROR_SUCCESS;\r
- } else\r
- return KHM_ERROR_UNKNOWN;\r
+ RegCloseKey(hk_ccname);\r
\r
- } else if (dw > ARRAYLENGTH(env_ccname)) {\r
- /* buffer was not enough */\r
-#ifdef DEBUG\r
- assert(FALSE);\r
-#else\r
- return KHM_ERROR_UNKNOWN;\r
-#endif\r
+ if (l == ERROR_SUCCESS) {\r
+ _reportf(L"Successfully set the default ccache");\r
+ k5_update_last_default_identity(def_ident);\r
+ _end_task();\r
+ return KHM_ERROR_SUCCESS;\r
} else {\r
- /* KRB5CCNAME is set */\r
- long code;\r
- krb5_context ctx;\r
-\r
- /* if the %KRB5CCNAME is the same as the identity\r
- ccache, then it is already the default. */\r
- if (!khm_krb5_cc_name_cmp(id_ccname, env_ccname)) {\r
- k5_update_last_default_identity(def_ident);\r
- return KHM_ERROR_SUCCESS;\r
- }\r
-\r
- /* if not, we have to copy the contents of id_ccname\r
- to env_ccname */\r
- code = pkrb5_init_context(&ctx);\r
- if (code)\r
- return KHM_ERROR_UNKNOWN;\r
-\r
- code = khm_krb5_copy_ccache_by_name(ctx, \r
- env_ccname, \r
- id_ccname);\r
-\r
- if (code == 0) {\r
- k5_update_last_default_identity(def_ident);\r
- khm_krb5_list_tickets(&ctx);\r
- }\r
-\r
- if (ctx)\r
- pkrb5_free_context(ctx);\r
-\r
- return (code == 0)?KHM_ERROR_SUCCESS:KHM_ERROR_UNKNOWN;\r
+ _reportf(L"Can't set the registry value : %d", l);\r
+ _end_task();\r
+ return KHM_ERROR_UNKNOWN;\r
}\r
+\r
} else {\r
/* the default identity is being forgotten */\r
\r
khm_int32 k5_flags;\r
};\r
\r
+/* The logic here has to reflect the logic in khm_krb5_list_tickets().\r
+ We use this to handle an identity update request because some other\r
+ plug-in or maybe NetIDMgr itself is about to do something\r
+ important(tm) with the identity and needs to make sure that the\r
+ properties of the identity are up-to-date. */\r
static khm_int32 KHMAPI\r
k5_ident_update_apply_proc(khm_handle cred,\r
void * rock) {\r
khm_ui_4 uparam,\r
void * vparam) {\r
\r
+#if 0\r
struct k5_ident_update_data d;\r
+#endif\r
khm_handle ident;\r
khm_handle tident;\r
krb5_ccache cc = NULL;\r
if (ident == NULL)\r
return KHM_ERROR_SUCCESS;\r
\r
+#if 0\r
+ /* we are going to skip doing this here since\r
+ khm_krb5_list_tickets() performs this function for us each time\r
+ we enumerate tickets. Since it also gets run each time our\r
+ list of tickets changes and since we are basing this operation\r
+ on existing tickets, we are unlikely to find anything new\r
+ here. */\r
ZeroMemory(&d, sizeof(d));\r
d.identity = ident;\r
\r
kcdb_identity_set_attr(ident, attr_id_krb5_flags, NULL, 0);\r
kcdb_identity_set_attr(ident, attr_id_krb5_ccname, NULL, 0);\r
}\r
+#endif\r
\r
if (KHM_SUCCEEDED(kcdb_identity_get_default(&tident))) {\r
kcdb_identity_release(tident);\r
krb5_principal princ = NULL;\r
char * princ_nameA = NULL;\r
wchar_t princ_nameW[KCDB_IDENT_MAXCCH_NAME];\r
+ char * ccname = NULL;\r
khm_handle ident = NULL;\r
khm_boolean found_default = FALSE;\r
\r
assert(ctx != NULL);\r
\r
+ _begin_task(0);\r
+ _report_cs0(KHERR_DEBUG_1, L"Refreshing default identity");\r
+ _describe();\r
+\r
code = pkrb5_cc_default(ctx, &cc);\r
- if (code)\r
+ if (code) {\r
+ _reportf(L"Can't open default ccache. code=%d", code);\r
goto _nc_cleanup;\r
+ }\r
\r
code = pkrb5_cc_get_principal(ctx, cc, &princ);\r
- if (code)\r
+ if (code) {\r
+ /* try to determine the identity from the ccache name */\r
+ ccname = pkrb5_cc_get_name(ctx, cc);\r
+\r
+ if (ccname) {\r
+ char * namepart = strchr(ccname, ':');\r
+\r
+ _reportf(L"CC name is [%S]", ccname);\r
+\r
+ if (namepart == NULL)\r
+ namepart = ccname;\r
+ else\r
+ namepart++;\r
+\r
+ _reportf(L"Checking if [%S] is a valid identity name", namepart);\r
+\r
+ AnsiStrToUnicode(princ_nameW, sizeof(princ_nameW), namepart);\r
+ if (kcdb_identity_is_valid_name(princ_nameW)) {\r
+ kcdb_identity_create(princ_nameW, KCDB_IDENT_FLAG_CREATE, &ident);\r
+ if (ident) {\r
+ _reportf(L"Setting [%S] as the default identity", namepart);\r
+ kcdb_identity_set_default_int(ident);\r
+ found_default = TRUE;\r
+ }\r
+ }\r
+ } else {\r
+ _reportf(L"Can't determine ccache name");\r
+ }\r
+\r
goto _nc_cleanup;\r
+ }\r
\r
code = pkrb5_unparse_name(ctx, princ, &princ_nameA);\r
if (code)\r
\r
AnsiStrToUnicode(princ_nameW, sizeof(princ_nameW), princ_nameA);\r
\r
- if (KHM_FAILED(kcdb_identity_create(princ_nameW, 0, &ident)))\r
+ _reportf(L"Found principal [%s]", princ_nameW);\r
+\r
+ if (KHM_FAILED(kcdb_identity_create(princ_nameW, KCDB_IDENT_FLAG_CREATE, &ident))) {\r
+ _reportf(L"Failed to create identity");\r
goto _nc_cleanup;\r
+ }\r
\r
+ _reportf(L"Setting default identity to [%s]", princ_nameW);\r
kcdb_identity_set_default_int(ident);\r
\r
found_default = TRUE;\r
\r
_nc_cleanup:\r
+\r
+ _end_task();\r
+\r
if (princ_nameA)\r
pkrb5_free_unparsed_name(ctx, princ_nameA);\r
\r
khm_int32 attr_id_krb5_flags = -1;\r
khm_int32 attr_id_krb5_ccname = -1;\r
khm_int32 attr_id_kvno = -1;\r
+khm_int32 attr_id_krb5_idflags = -1;\r
\r
BOOL attr_regd_key_enctype = FALSE;\r
BOOL attr_regd_tkt_enctype = FALSE;\r
BOOL attr_regd_krb5_flags = FALSE;\r
BOOL attr_regd_krb5_ccname = FALSE;\r
BOOL attr_regd_kvno = FALSE;\r
+BOOL attr_regd_krb5_idflags = FALSE;\r
\r
khm_handle csp_plugins = NULL;\r
khm_handle csp_krbcred = NULL;\r
attr_regd_kvno = TRUE;\r
}\r
\r
+ if (KHM_FAILED(kcdb_attrib_get_id(ATTRNAME_KRB5_IDFLAGS, &attr_id_krb5_idflags))) {\r
+ kcdb_attrib attrib;\r
+\r
+ ZeroMemory(&attrib, sizeof(attrib));\r
+\r
+ attrib.name = ATTRNAME_KRB5_IDFLAGS;\r
+ attrib.id = KCDB_ATTR_INVALID;\r
+ attrib.type = KCDB_TYPE_INT32;\r
+ attrib.flags = KCDB_ATTR_FLAG_PROPERTY |\r
+ KCDB_ATTR_FLAG_HIDDEN;\r
+ /* we don't bother localizing these strings since the\r
+ attribute is hidden. The user will not see these\r
+ descriptions anyway. */\r
+ attrib.short_desc = L"Krb5 ID flags";\r
+ attrib.long_desc = L"Kerberos 5 Identity Flags";\r
+\r
+ rv = kcdb_attrib_register(&attrib, &attr_id_krb5_idflags);\r
+\r
+ if (KHM_FAILED(rv))\r
+ goto _exit;\r
+\r
+ attr_regd_krb5_idflags = TRUE;\r
+ }\r
+\r
rv = kmm_get_plugins_config(0, &csp_plugins);\r
if(KHM_FAILED(rv)) goto _exit;\r
\r
kcdb_attrib_unregister(attr_id_krb5_ccname);\r
if(attr_regd_kvno)\r
kcdb_attrib_unregister(attr_id_kvno);\r
+ if(attr_regd_krb5_idflags)\r
+ kcdb_attrib_unregister(attr_id_krb5_idflags);\r
\r
if(type_regd_enctype)\r
kcdb_type_unregister(type_id_enctype);\r
#endif\r
khc_read_int32(csp_params, L"MsLsaImport", &t);\r
\r
- if (t == 1)\r
- khm_krb5_ms2mit(TRUE);\r
+ if (t != K5_LSAIMPORT_NEVER) {\r
+ krb5_context ctx = NULL;\r
+ BOOL imported;\r
+\r
+ imported = khm_krb5_ms2mit(NULL, (t == K5_LSAIMPORT_MATCH), TRUE);\r
+ if (imported) {\r
+ khm_krb5_list_tickets(&ctx);\r
+ if (ctx)\r
+ pkrb5_free_context(ctx);\r
+ }\r
+ }\r
}\r
break;\r
}\r
\r
krb5_initialized = TRUE;\r
\r
- if(ctx != NULL)\r
- pkrb5_free_context(ctx);\r
-\r
/* now convert this thread to a fiber and create a\r
separate fiber to do kinit stuff */\r
k5_main_fiber = ConvertThreadToFiber(NULL);\r
k5_register_config_panels();\r
\r
khm_krb5_list_tickets(&ctx);\r
+\r
+ if(ctx != NULL)\r
+ pkrb5_free_context(ctx);\r
}\r
}\r
break;\r
#define ATTRNAME_KRB5_FLAGS L"Krb5Flags"\r
#define ATTRNAME_KRB5_CCNAME L"Krb5CCName"\r
#define ATTRNAME_KVNO L"Kvno"\r
+#define ATTRNAME_KRB5_IDFLAGS L"Krb5IDFlags"\r
+\r
+/* Flag bits for Krb5IDFlags property */\r
+\r
+/* identity was imported from MSLSA: */\r
+#define K5IDFLAG_IMPORTED 0x00000001\r
\r
void init_krb();\r
void exit_krb();\r
extern khm_int32 attr_id_krb5_flags;\r
extern khm_int32 attr_id_krb5_ccname;\r
extern khm_int32 attr_id_kvno;\r
+extern khm_int32 attr_id_krb5_idflags;\r
\r
extern khm_ui_4 k5_commctl_version;\r
\r
o = (khui_credwnd_outline *) tbl->rows[row].data;\r
\r
if (tbl->cols[o->col].attr_id == KCDB_ATTR_ID_NAME) {\r
- if (TPARENT(o) == NULL) { /* selected an identity */\r
- khui_context_set(KHUI_SCOPE_IDENT,\r
- (khm_handle) o->data,\r
- KCDB_CREDTYPE_INVALID,\r
- NULL,\r
- NULL,\r
- 0,\r
- tbl->credset);\r
- } else {\r
+ if (TPARENT(o) != NULL) {\r
khui_credwnd_outline * op;\r
\r
op = TPARENT(o);\r
0,\r
tbl->credset);\r
} else {\r
- set_context = FALSE;\r
+ /* we can't narrow it down using the standard set\r
+ of scopes. We consider this to be an identity\r
+ selection because the user right-clicked on an\r
+ identity header. */\r
+ khui_context_set(KHUI_SCOPE_IDENT,\r
+ (khm_handle) o->data,\r
+ KCDB_CREDTYPE_INVALID,\r
+ NULL,\r
+ NULL,\r
+ 0,\r
+ tbl->credset);\r
}\r
+ } else {\r
+ /* The user clicked on an identity header. Even\r
+ though not all credentials belonging to the\r
+ identity maybe within the scope right now, we still\r
+ consider this to be an identity scope. */\r
+ khui_context_set(KHUI_SCOPE_IDENT,\r
+ (khm_handle) o->data,\r
+ KCDB_CREDTYPE_INVALID,\r
+ NULL,\r
+ NULL,\r
+ 0,\r
+ tbl->credset);\r
}\r
} else if (tbl->cols[o->col].attr_id == KCDB_ATTR_TYPE_NAME) {\r
if (TPARENT(o) == NULL) {\r
khui_credwnd_outline * op;\r
\r
op = TPARENT(o);\r
- if (tbl->cols[op->col].attr_id == KCDB_ATTR_ID_NAME &&\r
- TPARENT(op) == NULL) {\r
- /* credtype under an identity */\r
+ if (tbl->cols[op->col].attr_id == KCDB_ATTR_ID_NAME) {\r
+ /* credtype under an identity. Even though not\r
+ all the credentials of this credtype belonging\r
+ to this identity might be within the scope, we\r
+ still consider this to be a type selection\r
+ under a specific identity. */\r
khui_context_set(KHUI_SCOPE_CREDTYPE,\r
(khm_handle) op->data,\r
(khm_int32) (DWORD_PTR) o->data,\r
khm_size t = 0;\r
khm_int32 cred_type;\r
\r
+ if (ctx.identity == NULL) {\r
+ /* currently, we can't show a property sheet at this point\r
+ since most credentials providers don't provide a\r
+ property sheet that works without an identity. */\r
+\r
+ khui_context_release(&ctx);\r
+ khui_ps_destroy_sheet(ps);\r
+ return TRUE;\r
+ }\r
+\r
cred_type = ctx.cred_type;\r
\r
ps->header.hInstance = khm_hInstance;\r
ps->header.pszCaption = NULL;\r
}\r
} else {\r
+ /* we don't actually reach here since we handle this case\r
+ above */\r
kcdb_credtype_describe(cred_type, NULL, &t, KCDB_TS_LONG);\r
if(t > 0) {\r
ps->header.pszCaption = PMALLOC(t);\r
\r
cw_set_row_context(tbl, row);\r
\r
+ khm_menu_show_panel(KHUI_MENU_IDENT_CTX, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));\r
+\r
+#if 0\r
+ /* calling cw_set_row_context() should take care of enabling or\r
+ disabling actions as appropriate. We don't need to\r
+ differentiate between IDENT_CTX and TOK_CTX here. */\r
if((tbl->rows[row].flags & KHUI_CW_ROW_HEADER) &&\r
- (tbl->cols[tbl->rows[row].col].attr_id == KCDB_ATTR_ID_NAME))\r
- {\r
+ (tbl->cols[tbl->rows[row].col].attr_id == KCDB_ATTR_ID_NAME)) {\r
khm_menu_show_panel(KHUI_MENU_IDENT_CTX, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));\r
//khui_context_reset();\r
} else {\r
khm_menu_show_panel(KHUI_MENU_TOK_CTX, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));\r
//khui_context_reset();\r
}\r
+#endif\r
\r
return DefWindowProc(hwnd, uMsg, wParam, lParam);\r
}\r
meaningful value until we enter a modal loop */\r
khui_dialogs[n_khui_dialogs].active = FALSE;\r
n_khui_dialogs++;\r
- }\r
+ } else {\r
#if DEBUG\r
- else {\r
- assert(FALSE);\r
- }\r
+ assert(FALSE);\r
#endif\r
+ }\r
}\r
\r
/* should only be called from the UI thread */\r
EnableWindow(khm_hwnd_main, FALSE);\r
\r
khui_modal_dialog = hwnd;\r
+\r
+ SetForegroundWindow(hwnd);\r
}\r
}\r
\r
return 0;\r
\r
case KHUI_ACTION_PASSWD_ID:\r
+ if (khm_startup.processing)\r
+ return 0;\r
+\r
khm_cred_change_password(NULL);\r
return 0;\r
\r
case KHUI_ACTION_NEW_CRED:\r
+ if (khm_startup.processing)\r
+ return 0;\r
+\r
khm_cred_obtain_new_creds(NULL);\r
return 0;\r
\r
case KHUI_ACTION_RENEW_CRED:\r
+ if (khm_startup.processing)\r
+ return 0;\r
+\r
khm_cred_renew_creds();\r
return 0;\r
\r
case KHUI_ACTION_DESTROY_CRED:\r
+ if (khm_startup.processing)\r
+ return 0;\r
+\r
khm_cred_destroy_creds(FALSE, FALSE);\r
return 0;\r
\r
case KHUI_ACTION_SET_DEF_ID:\r
+ if (khm_startup.processing)\r
+ return 0;\r
+\r
khm_cred_set_default();\r
return 0;\r
\r
#endif\r
/* we defer the creation of the tab buttons for later */\r
\r
- /* add this to the dialog chain */\r
- khm_add_dialog(hwnd);\r
-\r
/* bring the window to the top, if necessary */\r
if (KHM_SUCCEEDED(khc_read_int32(NULL,\r
L"CredWindow\\Windows\\NewCred\\ForceToTop",\r
&t)) &&\r
- t != 0) {\r
+ t != 0 &&\r
+ !khm_is_dialog_active()) {\r
\r
/* if the main window is not visible, then the SetWindowPos()\r
call is sufficient to bring the new creds window to the\r
\r
}\r
\r
+ /* add this to the dialog chain */\r
+ khm_add_dialog(hwnd);\r
+\r
return TRUE;\r
}\r
\r
\r
nc_update_credtext(d);\r
\r
- ShowWindow(hwnd, SW_SHOW);\r
+ ShowWindow(hwnd, SW_SHOWNOACTIVATE);\r
SetFocus(hwnd);\r
\r
if (d->nc->n_identities == 0)\r
Custom_0,KC_ENDSPACE,0,\r
ByIdentity,KC_SPACE,0,The default view\r
Description,KC_STRING,View grouped by identity and credential type,\r
- ColumnList,KC_STRING,"_CWFlags,IdentityName,TypeName,Name,TimeLeft",\r
+ ColumnList,KC_STRING,"_CWFlags,IdentityName,TypeName,Location,Name,TimeLeft",\r
Columns,KC_SPACE,0,Columns\r
_CWFlags,KC_SPACE,0,\r
Width,KC_INT32,20,\r
SortIndex,KC_INT32,1\r
Flags,KC_INT32,11\r
TypeName,KC_ENDSPACE,0\r
+ Location,KC_SPACE,0,\r
+ Width,KC_INT32,50\r
+ SortIndex,KC_INT32,2\r
+ Flags,KC_INT32,11\r
+ Location,KC_ENDSPACE,0,\r
Name,KC_SPACE,0\r
Width,KC_INT32,200\r
- SortIndex,KC_INT32,2\r
+ SortIndex,KC_INT32,3\r
Flags,KC_INT32,3\r
Name,KC_ENDSPACE,0\r
TimeLeft,KC_SPACE,0\r