{"thread":{"id":"28402","subject":"[PATCH/RFC] add lame win32 credential-helper","startedAt":"2011-09-15T20:25:24Z","lastAt":"2011-09-30T14:42:15Z","messageCount":5,"participants":["Erik Faye-Lund","Jeff King","Stephen Bash"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"175602","messageId":"1316118324-6164-1-git-send-email-kusmabite@gmail.com","threadId":"28402","inReplyTo":null,"subject":"[PATCH/RFC] add lame win32 credential-helper","fromName":"Erik Faye-Lund","fromEmail":"kusmabite@gmail.com","sentAt":"2011-09-15T20:25:24Z","receivedAt":"2011-09-15T20:25:24Z","isPatch":true,"sender":{"key":"kusmabite@gmail.com","avatar":"https://avatars.githubusercontent.com/u/47073?v=4"},"body":"Signed-off-by: Erik Faye-Lund <kusmabite@gmail.com>\n---\n\nI got curious what a credential-helper that uses Windows'\nCredential Manager would look like; this is the result.\n\nSome parts of the code is heavily inspired by Jay Soffian's\nOSX-keychain work.\n\nNot that it's useful yet, since the core-git code for the\ncredential-helper support doesn't compile on Windows. So\nit's not fully tested, I've only read the interface\ndocumentation and experimented with it from the command\nline.\n\n contrib/credential-wincred/Makefile                |    8 +\n .../credential-wincred/git-credential-wincred.c    |  271 ++++++++++++++++++++\n 2 files changed, 279 insertions(+), 0 deletions(-)\n create mode 100644 contrib/credential-wincred/Makefile\n create mode 100644 contrib/credential-wincred/git-credential-wincred.c\n\ndiff --git a/contrib/credential-wincred/Makefile b/contrib/credential-wincred/Makefile\nnew file mode 100644\nindex 0000000..b4f098f\n--- /dev/null\n+++ b/contrib/credential-wincred/Makefile\n@@ -0,0 +1,8 @@\n+all: git-credential-wincred.exe\n+\n+CC = gcc\n+RM = rm -f\n+CFLAGS = -O2 -Wall\n+\n+git-credential-wincred.exe : git-credential-wincred.c\n+\t$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@\ndiff --git a/contrib/credential-wincred/git-credential-wincred.c b/contrib/credential-wincred/git-credential-wincred.c\nnew file mode 100644\nindex 0000000..5c0e2d3\n--- /dev/null\n+++ b/contrib/credential-wincred/git-credential-wincred.c\n@@ -0,0 +1,271 @@\n+#include <windows.h>\n+#include <stdio.h>\n+\n+/* MinGW doesn't have wincred.h, let's extract the stuff we need instead */\n+\n+typedef struct _CREDENTIAL_ATTRIBUTE {\n+\tLPWSTR Keyword;\n+\tDWORD  Flags;\n+\tDWORD  ValueSize;\n+\tLPBYTE Value;\n+} CREDENTIAL_ATTRIBUTE, *PCREDENTIAL_ATTRIBUTE;\n+\n+typedef struct _CREDENTIALW {\n+\tDWORD                 Flags;\n+\tDWORD                 Type;\n+\tLPWSTR                TargetName;\n+\tLPWSTR                Comment;\n+\tFILETIME              LastWritten;\n+\tDWORD                 CredentialBlobSize;\n+\tLPBYTE                CredentialBlob;\n+\tDWORD                 Persist;\n+\tDWORD                 AttributeCount;\n+\tPCREDENTIAL_ATTRIBUTE Attributes;\n+\tLPWSTR                TargetAlias;\n+\tLPWSTR                UserName;\n+} CREDENTIALW, *PCREDENTIALW;\n+\n+typedef struct _CREDUI_INFOW {\n+\tDWORD   cbSize;\n+\tHWND    hwndParent;\n+\tLPWSTR  pszMessageText;\n+\tLPWSTR  pszCaptionText;\n+\tHBITMAP hbmBanner;\n+} CREDUI_INFOW, *PCREDUI_INFOW;\n+\n+#define CRED_TYPE_GENERIC 1\n+#define CRED_PERSIST_LOCAL_MACHINE 2\n+#define CREDUIWIN_GENERIC 1\n+#define CREDUIWIN_CHECKBOX 2\n+#define CREDUIWIN_IN_CRED_ONLY 32\n+#define CRED_PACK_GENERIC_CREDENTIALS 4\n+\n+\n+typedef BOOL (WINAPI *CredWriteWT)(PCREDENTIALW, DWORD);\n+typedef BOOL (WINAPI *CredUnPackAuthenticationBufferWT)(DWORD, PVOID, DWORD,\n+    LPWSTR, DWORD *, LPWSTR, DWORD *, LPWSTR, DWORD *);\n+typedef DWORD (WINAPI *CredUIPromptForWindowsCredentialsWT)(PCREDUI_INFOW,\n+    DWORD, ULONG *, LPCVOID, ULONG, LPVOID *, ULONG *, BOOL *, DWORD);\n+typedef BOOL (WINAPI *CredEnumerateWT)(LPCWSTR, DWORD, DWORD *,\n+    PCREDENTIALW **);\n+typedef BOOL (WINAPI *CredPackAuthenticationBufferWT)(DWORD, LPWSTR, LPWSTR,\n+    PBYTE, DWORD *);\n+typedef VOID (WINAPI *CredFreeT)(PVOID);\n+typedef BOOL (WINAPI *CredDeleteWT)(LPCWSTR, DWORD, DWORD);\n+\n+static HMODULE advapi, credui;\n+static CredWriteWT CredWriteW;\n+static CredUnPackAuthenticationBufferWT CredUnPackAuthenticationBufferW;\n+static CredUIPromptForWindowsCredentialsWT CredUIPromptForWindowsCredentialsW;\n+static CredEnumerateWT CredEnumerateW;\n+static CredPackAuthenticationBufferWT CredPackAuthenticationBufferW;\n+static CredFreeT CredFree;\n+static CredDeleteWT CredDeleteW;\n+\n+static void die(const char *err, ...)\n+{\n+\tchar msg[4096];\n+\tva_list params;\n+\tva_start(params, err);\n+\tvsnprintf(msg, sizeof(msg), err, params);\n+\tfprintf(stderr, \"%s\\n\", msg);\n+\tva_end(params);\n+\texit(1);\n+}\n+\n+static void emit_user_pass(WCHAR *username, WCHAR *password)\n+{\n+\tif (username)\n+\t\twprintf(L\"username=%s\\n\", username);\n+\tif (password)\n+\t\twprintf(L\"password=%s\\n\", password);\n+}\n+\n+static int find_credentials(WCHAR *target, WCHAR *username)\n+{\n+\tWCHAR user_buf[256], pass_buf[256];\n+\tDWORD user_buf_size = sizeof(user_buf) - 1,\n+\t      pass_buf_size = sizeof(pass_buf) - 1;\n+\tCREDENTIALW **creds, *cred = NULL;\n+\tDWORD num_creds;\n+\n+\tif (!CredEnumerateW(target, 0, &num_creds, &creds))\n+\t\treturn -1;\n+\n+\tif (!username) {\n+\t\t/* no username was specified, just pick the first one */\n+\t\tcred = creds[0];\n+\t} else {\n+\t\t/* search for the first credential that matches username */\n+\t\tint i;\n+\t\tfor (i = 0; i < num_creds; ++i)\n+\t\t\tif (!wcscmp(username, creds[i]->UserName)) {\n+\t\t\t\tcred = creds[i];\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\tif (!cred)\n+\t\t\treturn -1;\n+\t}\n+\n+\tif (!CredUnPackAuthenticationBufferW(0, cred->CredentialBlob,\n+\t    cred->CredentialBlobSize, user_buf, &user_buf_size, NULL, NULL,\n+\t    pass_buf, &pass_buf_size))\n+\t\treturn -1;\n+\n+\tCredFree(creds);\n+\n+\t/* zero terminate */\n+\tuser_buf[user_buf_size] = L'\\0';\n+\tpass_buf[pass_buf_size] = L'\\0';\n+\n+\temit_user_pass(user_buf, pass_buf);\n+\treturn 0;\n+}\n+\n+/* also saves the credentials if the user tells it to */\n+static int ask_credentials(WCHAR *target, WCHAR *comment, WCHAR *username)\n+{\n+\tBOOL save = FALSE;\n+\tLPVOID auth_buf = NULL;\n+\tULONG auth_buf_size = 0;\n+\tWCHAR user_buf[256], pass_buf[256];\n+\tDWORD user_buf_size = sizeof(user_buf) - 1,\n+\t      pass_buf_size = sizeof(pass_buf) - 1;\n+\tBYTE in_buf[1024];\n+\tDWORD in_buf_size = sizeof(in_buf);\n+\tDWORD err;\n+\tULONG package = 0;\n+\tCREDUI_INFOW info = {\n+\t\tsizeof(info), NULL,\n+\t\tcomment ? comment : target, L\"Enter password\", NULL\n+\t};\n+\n+\tif (username)\n+\t\tCredPackAuthenticationBufferW(0, username, L\"\",\n+\t\t    in_buf, &in_buf_size);\n+\terr = CredUIPromptForWindowsCredentialsW(&info, 0, &package,\n+\t    in_buf, in_buf_size, &auth_buf, &auth_buf_size,\n+\t    &save, CREDUIWIN_GENERIC | CREDUIWIN_CHECKBOX);\n+\tif (err == ERROR_CANCELLED)\n+\t\treturn 0;\n+\tif (err != ERROR_SUCCESS)\n+\t\treturn -1;\n+\n+\tif (!CredUnPackAuthenticationBufferW(0, auth_buf, auth_buf_size,\n+\t    user_buf, &user_buf_size, NULL, NULL,\n+\t    pass_buf, &pass_buf_size))\n+\t\treturn -1;\n+\n+\t/* zero terminate */\n+\tuser_buf[user_buf_size] = L'\\0';\n+\tpass_buf[pass_buf_size] = L'\\0';\n+\n+\temit_user_pass(user_buf, pass_buf);\n+\n+\tif (save) {\n+\t\tCREDENTIALW cred;\n+\t\tcred.Flags = 0;\n+\t\tcred.Type = CRED_TYPE_GENERIC;\n+\t\tcred.TargetName = target;\n+\t\tcred.Comment = comment;\n+\t\tcred.CredentialBlobSize = auth_buf_size;\n+\t\tcred.CredentialBlob = auth_buf;\n+\t\tcred.Persist = CRED_PERSIST_LOCAL_MACHINE;\n+\t\tcred.AttributeCount = 0;\n+\t\tcred.Attributes = NULL;\n+\t\tcred.TargetAlias = NULL;\n+\t\tcred.UserName = user_buf;\n+\t\tif (!CredWriteW(&cred, 0))\n+\t\t\tfprintf(stderr, \"failed to write credentials\\n\");\n+\t}\n+\treturn 0;\n+}\n+\n+static void delete_credentials(WCHAR *target, WCHAR *username)\n+{\n+\tWCHAR temp[4096];\n+\n+\twcscpy(temp, target);\n+\tif (username) {\n+\t\twcscat(temp, L\"|\");\n+\t\twcscat(temp, username);\n+\t}\n+\tif (!CredDeleteW(target, CRED_TYPE_GENERIC, 0))\n+\t\tdie(\"failed to delete credentials\");\n+}\n+\n+int main(int argc, char *argv[])\n+{\n+\tconst char *usage =\n+\t    \"Usage: git credential-osxkeychain --unique=TOKEN [options]\\n\"\n+\t    \"Options:\\n\"\n+\t    \"    --description=DESCRIPTION\\n\"\n+\t    \"    --username=USERNAME\\n\"\n+\t    \"    --reject\";\n+\tWCHAR desc_buf[4096], *description = NULL,\n+\t      user_buf[256], *username = NULL,\n+\t      unique_buf[1024], *unique = NULL;\n+\tint i, reject = 0;\n+\n+\tfor (i = 1; i < argc; ++i) {\n+\t\tconst char *arg = argv[i];\n+\t\tif (!strncmp(arg, \"--description=\", 14)) {\n+\t\t\tMultiByteToWideChar(CP_UTF8, 0, arg + 14, -1,\n+\t\t\t    desc_buf, sizeof(desc_buf));\n+\t\t\tdescription = desc_buf;\n+\t\t} else if (!strncmp(arg, \"--username=\", 11)) {\n+\t\t\tMultiByteToWideChar(CP_UTF8, 0, arg + 11, -1,\n+\t\t\t    user_buf, sizeof(user_buf));\n+\t\t\tusername = user_buf;\n+\t\t} else if (!strncmp(arg, \"--unique=\", 9)) {\n+\t\t\tMultiByteToWideChar(CP_UTF8, 0, arg + 9, -1,\n+\t\t\t    unique_buf, sizeof(unique_buf));\n+\t\t\tunique = unique_buf;\n+\t\t} else if (!strcmp(arg, \"--reject\")) {\n+\t\t\treject = 1;\n+\t\t} else if (!strcmp(arg, \"--help\")) {\n+\t\t\tdie(usage);\n+\t\t} else\n+\t\t\tdie(\"Unrecognized argument `%s'; try --help\", arg);\n+\t}\n+\n+\tif (!unique)\n+\t\tdie(\"Must specify --unique=TOKEN; try --help\");\n+\n+\t/* load DLLs */\n+\tadvapi = LoadLibrary(\"advapi32.dll\");\n+\tcredui = LoadLibrary(\"credui.dll\");\n+\tif (!advapi || !credui)\n+\t\tdie(\"failed to load DLLs\");\n+\n+\t/* get function pointers */\n+\tCredWriteW = (CredWriteWT)GetProcAddress(advapi, \"CredWriteW\");\n+\tCredUnPackAuthenticationBufferW = (CredUnPackAuthenticationBufferWT)\n+\t    GetProcAddress(credui, \"CredUnPackAuthenticationBufferW\");\n+\tCredUIPromptForWindowsCredentialsW =\n+\t    (CredUIPromptForWindowsCredentialsWT)GetProcAddress(credui,\n+\t    \"CredUIPromptForWindowsCredentialsW\");\n+\tCredEnumerateW = (CredEnumerateWT)GetProcAddress(advapi,\n+\t    \"CredEnumerateW\");\n+\tCredPackAuthenticationBufferW = (CredPackAuthenticationBufferWT)\n+\t    GetProcAddress(credui, \"CredPackAuthenticationBufferW\");\n+\tCredFree = (CredFreeT)GetProcAddress(advapi, \"CredFree\");\n+\tCredDeleteW = (CredDeleteWT)GetProcAddress(advapi, \"CredDeleteW\");\n+\tif (!CredWriteW || !CredUnPackAuthenticationBufferW ||\n+\t    !CredUIPromptForWindowsCredentialsW || !CredEnumerateW ||\n+\t    !CredPackAuthenticationBufferW || !CredFree || !CredDeleteW)\n+\t\tdie(\"failed to load functions\");\n+\n+\tif (reject) {\n+\t\tdelete_credentials(unique, username);\n+\t\treturn 0;\n+\t}\n+\n+\tif (!find_credentials(unique, username))\n+\t\treturn 0;\n+\n+\tif (!ask_credentials(unique, description, username))\n+\t\treturn 0;\n+\n+\treturn -1;\n+}\n-- \n1.7.6.355.g842ba.dirty\n"},{"id":"175632","messageId":"20110915214026.GA18623@sigill.intra.peff.net","threadId":"28402","inReplyTo":"1316118324-6164-1-git-send-email-kusmabite@gmail.com","subject":"Re: [PATCH/RFC] add lame win32 credential-helper","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2011-09-15T21:40:26Z","receivedAt":"2011-09-15T21:40:26Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Sep 15, 2011 at 10:25:24PM +0200, Erik Faye-Lund wrote:\n\n> Not that it's useful yet, since the core-git code for the\n> credential-helper support doesn't compile on Windows. So\n> it's not fully tested, I've only read the interface\n> documentation and experimented with it from the command\n> line.\n\nWhich parts of the credential-helper code don't compile? I wouldn't be\nsurprised if the cache helper doesn't work (because it uses unix domain\nsockets for communication). If it's too hard to adapt it to whatever IPC\nmechanism would be appropriate on Windows, we can just leave it out on\nthat platform.\n\nBut the core code in git itself should be pretty straight forward. Let\nme know what I can do to help.\n\n-Peff\n"},{"id":"175633","messageId":"CABPQNSZjGzyxJKWRDDWxRj_SLdC1Y_9TxnAMOA+b-Pw3+X-E7w@mail.gmail.com","threadId":"28402","inReplyTo":"20110915214026.GA18623@sigill.intra.peff.net","subject":"Re: [PATCH/RFC] add lame win32 credential-helper","fromName":"Erik Faye-Lund","fromEmail":"kusmabite@gmail.com","sentAt":"2011-09-15T21:48:30Z","receivedAt":"2011-09-15T21:48:30Z","isPatch":true,"sender":{"key":"kusmabite@gmail.com","avatar":"https://avatars.githubusercontent.com/u/47073?v=4"},"body":"On Thu, Sep 15, 2011 at 11:40 PM, Jeff King <peff@peff.net> wrote:\n> On Thu, Sep 15, 2011 at 10:25:24PM +0200, Erik Faye-Lund wrote:\n>\n>> Not that it's useful yet, since the core-git code for the\n>> credential-helper support doesn't compile on Windows. So\n>> it's not fully tested, I've only read the interface\n>> documentation and experimented with it from the command\n>> line.\n>\n> Which parts of the credential-helper code don't compile? I wouldn't be\n> surprised if the cache helper doesn't work (because it uses unix domain\n> sockets for communication).\n\nThat's exactly it:\n\nunix-socket.c:12: warning: 'struct sockaddr_un' declared inside parameter list\nunix-socket.c:12: warning: its scope is only this definition or declaration, whi\nch is probably not what you want\nunix-socket.c: In function 'unix_sockaddr_init':\nunix-socket.c:15: error: dereferencing pointer to incomplete type\nunix-socket.c:17: error: dereferencing pointer to incomplete type\nunix-socket.c:18: error: dereferencing pointer to incomplete type\nunix-socket.c:19: error: dereferencing pointer to incomplete type\nunix-socket.c: In function 'unix_stream_connect':\nunix-socket.c:25: error: storage size of 'sa' isn't known\nunix-socket.c: In function 'unix_stream_listen':\nunix-socket.c:39: error: storage size of 'sa' isn't known\nmake: *** [unix-socket.o] Error 1\nmake: *** Waiting for unfinished jobs....\n\n> If it's too hard to adapt it to whatever IPC\n> mechanism would be appropriate on Windows, we can just leave it out on\n> that platform.\n>\n> But the core code in git itself should be pretty straight forward.\n\nI didn't mean that it was impossible to port, just that it didn't compile\nas-is. I haven't looked into fixing up the code so it compiles on Windows\nagain myself. And I'm not really planning to; I have little git-time these\ndays, and little knowledge of how unix-sockets works...\n"},{"id":"175643","messageId":"23805783.18971.1316177988773.JavaMail.root@mail.hq.genarts.com","threadId":"28402","inReplyTo":"CABPQNSZjGzyxJKWRDDWxRj_SLdC1Y_9TxnAMOA+b-Pw3+X-E7w@mail.gmail.com","subject":"Re: [PATCH/RFC] add lame win32 credential-helper","fromName":"Stephen Bash","fromEmail":"bash@genarts.com","sentAt":"2011-09-16T12:59:48Z","receivedAt":"2011-09-16T12:59:48Z","isPatch":true,"sender":{"key":"bash@genarts.com","avatar":null},"body":"----- Original Message -----\n> From: \"Erik Faye-Lund\" <kusmabite@gmail.com>\n> To: \"Jeff King\" <peff@peff.net>\n> Cc: git@vger.kernel.org, jaysoffian@gmail.com, gitster@pobox.com\n> Sent: Thursday, September 15, 2011 5:48:30 PM\n> Subject: Re: [PATCH/RFC] add lame win32 credential-helper\n> \n> > If it's too hard to adapt it to whatever IPC\n> > mechanism would be appropriate on Windows, we can just leave it out\n> > on that platform.\n> >\n> > But the core code in git itself should be pretty straight forward.\n> \n> I didn't mean that it was impossible to port, just that it didn't compile\n> as-is. I haven't looked into fixing up the code so it compiles on Windows\n> again myself. And I'm not really planning to; I have little git-time\n> these days, and little knowledge of how unix-sockets works...\n\nThis may be common knowledge, but from our brief experiment with them last fall, Windows Named Pipes are fairly similar to Unix Domain Sockets (not named FIFOs as one would expect...).  We didn't quite get a perfect replacement using preprocessor macros, but I think you can get pretty close (we eventually dumped the idea in favor of straight TCP sockets that behave the same on all our platforms of interest).\n\nHTH,\nStephen\n"},{"id":"176571","messageId":"CABPQNSY7ieW+0XjQWir6-Zk86j1THWW2VVLgvfrqe3XOEudemQ@mail.gmail.com","threadId":"28402","inReplyTo":"1316118324-6164-1-git-send-email-kusmabite@gmail.com","subject":"Re: [PATCH/RFC] add lame win32 credential-helper","fromName":"Erik Faye-Lund","fromEmail":"kusmabite@gmail.com","sentAt":"2011-09-30T14:42:15Z","receivedAt":"2011-09-30T14:42:15Z","isPatch":true,"sender":{"key":"kusmabite@gmail.com","avatar":"https://avatars.githubusercontent.com/u/47073?v=4"},"body":"On Thu, Sep 15, 2011 at 10:25 PM, Erik Faye-Lund <kusmabite@gmail.com> wrote:\n> Signed-off-by: Erik Faye-Lund <kusmabite@gmail.com>\n> ---\n>\n> I got curious what a credential-helper that uses Windows'\n> Credential Manager would look like; this is the result.\n>\n> Some parts of the code is heavily inspired by Jay Soffian's\n> OSX-keychain work.\n>\n> Not that it's useful yet, since the core-git code for the\n> credential-helper support doesn't compile on Windows. So\n> it's not fully tested, I've only read the interface\n> documentation and experimented with it from the command\n> line.\n>\n\n...aaand with the following patch applied on top, it pass the\ncredential-helper test that peff wrote:\n\n---8<---\nSubject: [PATCH] fix helper to pass test\n\n---\n .../credential-wincred/git-credential-wincred.c    |   87 +++++++++++---------\n 1 files changed, 47 insertions(+), 40 deletions(-)\n\ndiff --git a/contrib/credential-wincred/git-credential-wincred.c\nb/contrib/credential-wincred/git-credential-wincred.c\nindex 072c002..278dd80 100644\n--- a/contrib/credential-wincred/git-credential-wincred.c\n+++ b/contrib/credential-wincred/git-credential-wincred.c\n@@ -84,31 +84,35 @@ static void emit_user_pass(WCHAR *username, WCHAR *password)\n \t\twprintf(L\"password=%s\\n\", password);\n }\n\n-static int find_credentials(WCHAR *target, WCHAR *username)\n+static WCHAR *get_target(WCHAR *unique, WCHAR *username)\n {\n+\tstatic WCHAR target_buf[4096];\n+\t_snwprintf(target_buf, sizeof(target_buf), L\"gitcred:%s:%s\",\n+\t    unique, username);\n+\treturn target_buf;\n+}\n+\n+static int find_credentials(WCHAR *unique, WCHAR *username)\n+{\n+\tint i;\n \tWCHAR user_buf[256], pass_buf[256];\n \tDWORD user_buf_size = sizeof(user_buf) - 1,\n \t      pass_buf_size = sizeof(pass_buf) - 1;\n \tCREDENTIALW **creds, *cred = NULL;\n \tDWORD num_creds;\n\n-\tif (!CredEnumerateW(target, 0, &num_creds, &creds))\n+\tif (!CredEnumerateW(get_target(unique, username ? username : L\"*\"),\n+\t    0, &num_creds, &creds))\n \t\treturn -1;\n\n-\tif (!username) {\n-\t\t/* no username was specified, just pick the first one */\n-\t\tcred = creds[0];\n-\t} else {\n-\t\t/* search for the first credential that matches username */\n-\t\tint i;\n-\t\tfor (i = 0; i < num_creds; ++i)\n-\t\t\tif (!wcscmp(username, creds[i]->UserName)) {\n-\t\t\t\tcred = creds[i];\n-\t\t\t\tbreak;\n-\t\t\t}\n-\t\tif (!cred)\n-\t\t\treturn -1;\n-\t}\n+\t/* search for the first credential that matches username */\n+\tfor (i = 0; i < num_creds; ++i)\n+\t\tif (!username || !wcscmp(username, creds[i]->UserName)) {\n+\t\t\tcred = creds[i];\n+\t\t\tbreak;\n+\t\t}\n+\tif (!cred)\n+\t\treturn -1;\n\n \tif (!CredUnPackAuthenticationBufferW(0, cred->CredentialBlob,\n \t    cred->CredentialBlobSize, user_buf, &user_buf_size, NULL, NULL,\n@@ -126,9 +130,9 @@ static int find_credentials(WCHAR *target, WCHAR *username)\n }\n\n /* also saves the credentials if the user tells it to */\n-static int ask_credentials(WCHAR *target, WCHAR *comment, WCHAR *username)\n+static int ask_credentials(WCHAR *unique, WCHAR *comment, WCHAR *username)\n {\n-\tBOOL save = FALSE;\n+\tBOOL save = unique != NULL;\n \tLPVOID auth_buf = NULL;\n \tULONG auth_buf_size = 0;\n \tWCHAR user_buf[256], pass_buf[256];\n@@ -140,7 +144,7 @@ static int ask_credentials(WCHAR *target, WCHAR\n*comment, WCHAR *username)\n \tULONG package = 0;\n \tCREDUI_INFOW info = {\n \t\tsizeof(info), NULL,\n-\t\tcomment ? comment : target, L\"Enter password\", NULL\n+\t\tcomment ? comment : unique, L\"Enter password\", NULL\n \t};\n\n \tif (username)\n@@ -148,7 +152,7 @@ static int ask_credentials(WCHAR *target, WCHAR\n*comment, WCHAR *username)\n \t\t    in_buf, &in_buf_size);\n \terr = CredUIPromptForWindowsCredentialsW(&info, 0, &package,\n \t    in_buf, in_buf_size, &auth_buf, &auth_buf_size,\n-\t    &save, CREDUIWIN_GENERIC | CREDUIWIN_CHECKBOX);\n+\t    &save, CREDUIWIN_GENERIC | (unique ? CREDUIWIN_CHECKBOX : 0));\n \tif (err == ERROR_CANCELLED)\n \t\treturn 0;\n \tif (err != ERROR_SUCCESS)\n@@ -169,7 +173,7 @@ static int ask_credentials(WCHAR *target, WCHAR\n*comment, WCHAR *username)\n \t\tCREDENTIALW cred;\n \t\tcred.Flags = 0;\n \t\tcred.Type = CRED_TYPE_GENERIC;\n-\t\tcred.TargetName = target;\n+\t\tcred.TargetName = get_target(unique, user_buf);\n \t\tcred.Comment = comment;\n \t\tcred.CredentialBlobSize = auth_buf_size;\n \t\tcred.CredentialBlob = auth_buf;\n@@ -184,17 +188,21 @@ static int ask_credentials(WCHAR *target, WCHAR\n*comment, WCHAR *username)\n \treturn 0;\n }\n\n-static void delete_credentials(WCHAR *target, WCHAR *username)\n+static void delete_credentials(WCHAR *unique, WCHAR *username)\n {\n-\tWCHAR temp[4096];\n+\tint i;\n+\tCREDENTIALW **creds;\n+\tDWORD num_creds;\n\n-\twcscpy(temp, target);\n-\tif (username) {\n-\t\twcscat(temp, L\"|\");\n-\t\twcscat(temp, username);\n-\t}\n-\tif (!CredDeleteW(target, CRED_TYPE_GENERIC, 0))\n-\t\tdie(\"failed to delete credentials\");\n+\tif (!CredEnumerateW(get_target(unique, username ? username : L\"*\"),\n+\t    0, &num_creds, &creds))\n+\t\treturn;\n+\n+\tfor (i = 0; i < num_creds; ++i)\n+\t\tif (!CredDeleteW(get_target(unique, creds[i]->UserName),\n+\t\t    CRED_TYPE_GENERIC, 0))\n+\t\t\tdie(\"failed to delete credentials\");\n+\tCredFree(creds);\n }\n\n int main(int argc, char *argv[])\n@@ -232,9 +240,6 @@ int main(int argc, char *argv[])\n \t\t\tdie(\"Unrecognized argument `%s'; try --help\", arg);\n \t}\n\n-\tif (!unique)\n-\t\tdie(\"Must specify --unique=TOKEN; try --help\");\n-\n \t/* load DLLs */\n \tadvapi = LoadLibrary(\"advapi32.dll\");\n \tcredui = LoadLibrary(\"credui.dll\");\n@@ -256,16 +261,18 @@ int main(int argc, char *argv[])\n \tCredDeleteW = (CredDeleteWT)GetProcAddress(advapi, \"CredDeleteW\");\n \tif (!CredWriteW || !CredUnPackAuthenticationBufferW ||\n \t    !CredUIPromptForWindowsCredentialsW || !CredEnumerateW ||\n-\t    !CredPackAuthenticationBufferW || !CredFree || !CredDeleteW)\n+\t    !CredFree || !CredPackAuthenticationBufferW || !CredDeleteW)\n \t\tdie(\"failed to load functions\");\n\n-\tif (reject) {\n-\t\tdelete_credentials(unique, username);\n-\t\treturn 0;\n-\t}\n+\tif (unique) {\n+\t\tif (reject) {\n+\t\t\tdelete_credentials(unique, username);\n+\t\t\treturn 0;\n+\t\t}\n\n-\tif (!find_credentials(unique, username))\n-\t\treturn 0;\n+\t\tif (!find_credentials(unique, username))\n+\t\t\treturn 0;\n+\t}\n\n \tif (!ask_credentials(unique, description, username))\n \t\treturn 0;\n-- \n1.7.7.rc0.257.g9fefc\n---8<---\n"}]}