Browse code

Update docker-credential-helpers to v0.5.0

Signed-off-by: Vincent Demeester <vincent@sbr.pm>

Vincent Demeester authored on 2017/03/14 04:38:14
Showing 9 changed files
... ...
@@ -97,7 +97,7 @@ github.com/googleapis/gax-go da06d194a00e19ce00d9011a13931c3f6f6887c7
97 97
 google.golang.org/genproto b3e7c2fb04031add52c4817f53f43757ccbf9c18
98 98
 
99 99
 # native credentials
100
-github.com/docker/docker-credential-helpers f72c04f1d8e71959a6d103f808c50ccbad79b9fd
100
+github.com/docker/docker-credential-helpers v0.5.0
101 101
 
102 102
 # containerd
103 103
 github.com/docker/containerd 9f68f96b8c0746e254b52bc1defcf7cc0c1a62eb
... ...
@@ -58,11 +58,12 @@ You can see examples of each function in the [client](https://godoc.org/github.c
58 58
 
59 59
 ## Development
60 60
 
61
-A credential helper can be any program that can read values from the standard input. We use the first argument in the command line to differentiate the kind of command to execute. There are three valid values:
61
+A credential helper can be any program that can read values from the standard input. We use the first argument in the command line to differentiate the kind of command to execute. There are four valid values:
62 62
 
63 63
 - `store`: Adds credentials to the keychain. The payload in the standard input is a JSON document with `ServerURL`, `Username` and `Secret`.
64 64
 - `get`: Retrieves credentials from the keychain. The payload in the standard input is the raw value for the `ServerURL`.
65 65
 - `erase`: Removes credentials from the keychain. The payload in the standard input is the raw value for the `ServerURL`.
66
+- `list`: Lists stored credentials. There is no standard input payload.
66 67
 
67 68
 This repository also includes libraries to implement new credentials programs in Go. Adding a new helper program is pretty easy. You can see how the OS X keychain helper works in the [osxkeychain](osxkeychain) directory.
68 69
 
... ...
@@ -17,6 +17,15 @@ type Credentials struct {
17 17
 	Secret    string
18 18
 }
19 19
 
20
+// Docker credentials should be labeled as such in credentials stores that allow labelling.
21
+// That label allows to filter out non-Docker credentials too at lookup/search in macOS keychain,
22
+// Windows credentials manager and Linux libsecret. Default value is "Docker Credentials"
23
+var CredsLabel = "Docker Credentials"
24
+
25
+func SetCredsLabel(label string) {
26
+	CredsLabel = label
27
+}
28
+
20 29
 // Serve initializes the credentials helper and parses the action argument.
21 30
 // This function is designed to be called from a command line interface.
22 31
 // It uses os.Args[1] as the key for the action.
... ...
@@ -1,5 +1,6 @@
1 1
 #include "osxkeychain_darwin.h"
2 2
 #include <CoreFoundation/CoreFoundation.h>
3
+#include <Foundation/NSValue.h>
3 4
 #include <stdio.h>
4 5
 #include <string.h>
5 6
 
... ...
@@ -13,7 +14,9 @@ char *get_error(OSStatus status) {
13 13
   return buf;
14 14
 }
15 15
 
16
-char *keychain_add(struct Server *server, char *username, char *secret) {
16
+char *keychain_add(struct Server *server, char *label, char *username, char *secret) {
17
+  SecKeychainItemRef item;
18
+
17 19
   OSStatus status = SecKeychainAddInternetPassword(
18 20
     NULL,
19 21
     strlen(server->host), server->host,
... ...
@@ -24,11 +27,27 @@ char *keychain_add(struct Server *server, char *username, char *secret) {
24 24
     server->proto,
25 25
     kSecAuthenticationTypeDefault,
26 26
     strlen(secret), secret,
27
-    NULL
27
+    &item
28 28
   );
29
+
29 30
   if (status) {
30 31
     return get_error(status);
31 32
   }
33
+
34
+  SecKeychainAttribute attribute;
35
+  SecKeychainAttributeList attrs;
36
+  attribute.tag = kSecLabelItemAttr;
37
+  attribute.data = label;
38
+  attribute.length = strlen(label);
39
+  attrs.count = 1;
40
+  attrs.attr = &attribute;
41
+
42
+  status = SecKeychainItemModifyContent(item, &attrs, 0, NULL);
43
+
44
+  if (status) {
45
+    return get_error(status);
46
+  }
47
+
32 48
   return NULL;
33 49
 }
34 50
 
... ...
@@ -115,44 +134,42 @@ char * CFStringToCharArr(CFStringRef aString) {
115 115
   return NULL;
116 116
 }
117 117
 
118
-char *keychain_list(char *** paths, char *** accts, unsigned int *list_l) {
118
+char *keychain_list(char *credsLabel, char *** paths, char *** accts, unsigned int *list_l) {
119
+    CFStringRef credsLabelCF = CFStringCreateWithCString(NULL, credsLabel, kCFStringEncodingUTF8);
119 120
     CFMutableDictionaryRef query = CFDictionaryCreateMutable (NULL, 1, NULL, NULL);
120 121
     CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword);
121 122
     CFDictionaryAddValue(query, kSecReturnAttributes, kCFBooleanTrue);
122 123
     CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
124
+    CFDictionaryAddValue(query, kSecAttrLabel, credsLabelCF);
123 125
     //Use this query dictionary
124 126
     CFTypeRef result= NULL;
125 127
     OSStatus status = SecItemCopyMatching(
126
-    query,
127
-    &result);
128
+                                          query,
129
+                                          &result);
130
+
131
+    CFRelease(credsLabelCF);
132
+
128 133
     //Ran a search and store the results in result
129 134
     if (status) {
130 135
         return get_error(status);
131 136
     }
132
-    int numKeys = CFArrayGetCount(result);
137
+    CFIndex numKeys = CFArrayGetCount(result);
133 138
     *paths = (char **) malloc((int)sizeof(char *)*numKeys);
134 139
     *accts = (char **) malloc((int)sizeof(char *)*numKeys);
135 140
     //result is of type CFArray
136
-    for(int i=0; i<numKeys; i++) {
141
+    for(CFIndex i=0; i<numKeys; i++) {
137 142
         CFDictionaryRef currKey = CFArrayGetValueAtIndex(result,i);
138
-        if (CFDictionaryContainsKey(currKey, CFSTR("path"))) {
139
-            //Even if a key is stored without an account, Apple defaults it to null so these arrays will be of the same length
140
-            CFStringRef pathTmp = CFDictionaryGetValue(currKey, CFSTR("path"));
141
-            CFStringRef acctTmp = CFDictionaryGetValue(currKey, CFSTR("acct"));
142
-            if (acctTmp == NULL) {
143
-                acctTmp = CFSTR("account not defined");
143
+
144
+        CFStringRef protocolTmp = CFDictionaryGetValue(currKey, CFSTR("ptcl"));
145
+        if (protocolTmp != NULL) {
146
+            CFStringRef protocolStr = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@"), protocolTmp);
147
+            if (CFStringCompare(protocolStr, CFSTR("htps"), 0) == kCFCompareEqualTo) {
148
+                protocolTmp = CFSTR("https://");
149
+            }
150
+            else {
151
+                protocolTmp = CFSTR("http://");
144 152
             }
145
-            char * path = (char *) malloc(CFStringGetLength(pathTmp)+1);
146
-            path = CFStringToCharArr(pathTmp);
147
-            path[strlen(path)] = '\0';
148
-            char * acct = (char *) malloc(CFStringGetLength(acctTmp)+1);
149
-            acct = CFStringToCharArr(acctTmp);
150
-            acct[strlen(acct)] = '\0';
151
-            //We now have all we need, username and servername. Now export this to .go
152
-            (*paths)[i] = (char *) malloc(sizeof(char)*(strlen(path)+1));
153
-            memcpy((*paths)[i], path, sizeof(char)*(strlen(path)+1));
154
-            (*accts)[i] = (char *) malloc(sizeof(char)*(strlen(acct)+1));
155
-            memcpy((*accts)[i], acct, sizeof(char)*(strlen(acct)+1));
153
+            CFRelease(protocolStr);
156 154
         }
157 155
         else {
158 156
             char * path = "0";
... ...
@@ -161,9 +178,45 @@ char *keychain_list(char *** paths, char *** accts, unsigned int *list_l) {
161 161
             memcpy((*paths)[i], path, sizeof(char)*(strlen(path)));
162 162
             (*accts)[i] = (char *) malloc(sizeof(char)*(strlen(acct)));
163 163
             memcpy((*accts)[i], acct, sizeof(char)*(strlen(acct)));
164
+            continue;
165
+        }
166
+        
167
+        CFMutableStringRef str = CFStringCreateMutableCopy(NULL, 0, protocolTmp);
168
+        CFStringRef serverTmp = CFDictionaryGetValue(currKey, CFSTR("srvr"));
169
+        if (serverTmp != NULL) {
170
+            CFStringAppend(str, serverTmp);
171
+        }
172
+        
173
+        CFStringRef pathTmp = CFDictionaryGetValue(currKey, CFSTR("path"));
174
+        if (pathTmp != NULL) {
175
+            CFStringAppend(str, pathTmp);
164 176
         }
177
+        
178
+        const NSNumber * portTmp = CFDictionaryGetValue(currKey, CFSTR("port"));
179
+        if (portTmp != NULL && portTmp.integerValue != 0) {
180
+            CFStringRef portStr = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@"), portTmp);
181
+            CFStringAppend(str, CFSTR(":"));
182
+            CFStringAppend(str, portStr);
183
+            CFRelease(portStr);
184
+        }
185
+        
186
+        CFStringRef acctTmp = CFDictionaryGetValue(currKey, CFSTR("acct"));
187
+        if (acctTmp == NULL) {
188
+            acctTmp = CFSTR("account not defined");
189
+        }
190
+
191
+        char * path = CFStringToCharArr(str);
192
+        char * acct = CFStringToCharArr(acctTmp);
193
+
194
+        //We now have all we need, username and servername. Now export this to .go
195
+        (*paths)[i] = (char *) malloc(sizeof(char)*(strlen(path)+1));
196
+        memcpy((*paths)[i], path, sizeof(char)*(strlen(path)+1));
197
+        (*accts)[i] = (char *) malloc(sizeof(char)*(strlen(acct)+1));
198
+        memcpy((*accts)[i], acct, sizeof(char)*(strlen(acct)+1));
199
+
200
+        CFRelease(str);
165 201
     }
166
-    *list_l = numKeys;
202
+    *list_l = (int)numKeys;
167 203
     return NULL;
168 204
 }
169 205
 
... ...
@@ -1,8 +1,8 @@
1 1
 package osxkeychain
2 2
 
3 3
 /*
4
-#cgo CFLAGS: -x objective-c
5
-#cgo LDFLAGS: -framework Security -framework Foundation
4
+#cgo CFLAGS: -x objective-c -mmacosx-version-min=10.10
5
+#cgo LDFLAGS: -framework Security -framework Foundation -mmacosx-version-min=10.10
6 6
 
7 7
 #include "osxkeychain_darwin.h"
8 8
 #include <stdlib.h>
... ...
@@ -10,11 +10,12 @@ package osxkeychain
10 10
 import "C"
11 11
 import (
12 12
 	"errors"
13
-	"github.com/docker/docker-credential-helpers/credentials"
14 13
 	"net/url"
15 14
 	"strconv"
16 15
 	"strings"
17 16
 	"unsafe"
17
+
18
+	"github.com/docker/docker-credential-helpers/credentials"
18 19
 )
19 20
 
20 21
 // errCredentialsNotFound is the specific error message returned by OS X
... ...
@@ -26,18 +27,22 @@ type Osxkeychain struct{}
26 26
 
27 27
 // Add adds new credentials to the keychain.
28 28
 func (h Osxkeychain) Add(creds *credentials.Credentials) error {
29
+	h.Delete(creds.ServerURL)
30
+
29 31
 	s, err := splitServer(creds.ServerURL)
30 32
 	if err != nil {
31 33
 		return err
32 34
 	}
33 35
 	defer freeServer(s)
34 36
 
37
+	label := C.CString(credentials.CredsLabel)
38
+	defer C.free(unsafe.Pointer(label))
35 39
 	username := C.CString(creds.Username)
36 40
 	defer C.free(unsafe.Pointer(username))
37 41
 	secret := C.CString(creds.Secret)
38 42
 	defer C.free(unsafe.Pointer(secret))
39 43
 
40
-	errMsg := C.keychain_add(s, username, secret)
44
+	errMsg := C.keychain_add(s, label, username, secret)
41 45
 	if errMsg != nil {
42 46
 		defer C.free(unsafe.Pointer(errMsg))
43 47
 		return errors.New(C.GoString(errMsg))
... ...
@@ -96,12 +101,15 @@ func (h Osxkeychain) Get(serverURL string) (string, string, error) {
96 96
 
97 97
 // List returns the stored URLs and corresponding usernames.
98 98
 func (h Osxkeychain) List() (map[string]string, error) {
99
+	credsLabelC := C.CString(credentials.CredsLabel)
100
+	defer C.free(unsafe.Pointer(credsLabelC))
101
+
99 102
 	var pathsC **C.char
100 103
 	defer C.free(unsafe.Pointer(pathsC))
101 104
 	var acctsC **C.char
102 105
 	defer C.free(unsafe.Pointer(acctsC))
103 106
 	var listLenC C.uint
104
-	errMsg := C.keychain_list(&pathsC, &acctsC, &listLenC)
107
+	errMsg := C.keychain_list(credsLabelC, &pathsC, &acctsC, &listLenC)
105 108
 	if errMsg != nil {
106 109
 		defer C.free(unsafe.Pointer(errMsg))
107 110
 		goMsg := C.GoString(errMsg)
... ...
@@ -7,8 +7,8 @@ struct Server {
7 7
   unsigned int port;
8 8
 };
9 9
 
10
-char *keychain_add(struct Server *server, char *username, char *secret);
10
+char *keychain_add(struct Server *server, char *label, char *username, char *secret);
11 11
 char *keychain_get(struct Server *server, unsigned int *username_l, char **username, unsigned int *secret_l, char **secret);
12 12
 char *keychain_delete(struct Server *server);
13
-char *keychain_list(char *** data, char *** accts, unsigned int *list_l);
13
+char *keychain_list(char *credsLabel, char *** data, char *** accts, unsigned int *list_l);
14 14
 void freeListData(char *** data, unsigned int length);
15 15
\ No newline at end of file
... ...
@@ -7,6 +7,7 @@ const SecretSchema *docker_get_schema(void)
7 7
 	static const SecretSchema docker_schema = {
8 8
 		"io.docker.Credentials", SECRET_SCHEMA_NONE,
9 9
 		{
10
+		    { "label", SECRET_SCHEMA_ATTRIBUTE_STRING },
10 11
 			{ "server", SECRET_SCHEMA_ATTRIBUTE_STRING },
11 12
 			{ "username", SECRET_SCHEMA_ATTRIBUTE_STRING },
12 13
 			{ "docker_cli", SECRET_SCHEMA_ATTRIBUTE_STRING },
... ...
@@ -16,11 +17,12 @@ const SecretSchema *docker_get_schema(void)
16 16
 	return &docker_schema;
17 17
 }
18 18
 
19
-GError *add(char *server, char *username, char *secret) {
19
+GError *add(char *label, char *server, char *username, char *secret) {
20 20
 	GError *err = NULL;
21 21
 
22 22
 	secret_password_store_sync (DOCKER_SCHEMA, SECRET_COLLECTION_DEFAULT,
23 23
 			server, secret, NULL, &err,
24
+			"label", label,
24 25
 			"server", server,
25 26
 			"username", username,
26 27
 			"docker_cli", "1",
... ...
@@ -40,7 +42,7 @@ GError *delete(char *server) {
40 40
 	return NULL;
41 41
 }
42 42
 
43
-char *get_username(SecretItem *item) {
43
+char *get_attribute(const char *attribute, SecretItem *item) {
44 44
 	GHashTable *attributes;
45 45
 	GHashTableIter iter;
46 46
 	gchar *value, *key;
... ...
@@ -48,7 +50,7 @@ char *get_username(SecretItem *item) {
48 48
 	attributes = secret_item_get_attributes(item);
49 49
 	g_hash_table_iter_init(&iter, attributes);
50 50
 	while (g_hash_table_iter_next(&iter, (void **)&key, (void **)&value)) {
51
-		if (strncmp(key, "username", strlen(key)) == 0)
51
+		if (strncmp(key, attribute, strlen(key)) == 0)
52 52
 			return (char *)value;
53 53
 	}
54 54
 	g_hash_table_unref(attributes);
... ...
@@ -71,7 +73,7 @@ GError *get(char *server, char **username, char **secret) {
71 71
 
72 72
 	service = secret_service_get_sync(SECRET_SERVICE_NONE, NULL, &err);
73 73
 	if (err == NULL) {
74
-		items = secret_service_search_sync(service, NULL, attributes, flags, NULL, &err);
74
+		items = secret_service_search_sync(service, DOCKER_SCHEMA, attributes, flags, NULL, &err);
75 75
 		if (err == NULL) {
76 76
 			for (l = items; l != NULL; l = g_list_next(l)) {
77 77
 				value = secret_item_get_schema_name(l->data);
... ...
@@ -85,7 +87,7 @@ GError *get(char *server, char **username, char **secret) {
85 85
 					*secret = strdup(secret_value_get(secretValue, &length));
86 86
 					secret_value_unref(secretValue);
87 87
 				}
88
-				*username = get_username(l->data);
88
+				*username = get_attribute("username", l->data);
89 89
 			}
90 90
 			g_list_free_full(items, g_object_unref);
91 91
 		}
... ...
@@ -98,22 +100,30 @@ GError *get(char *server, char **username, char **secret) {
98 98
 	return NULL;
99 99
 }
100 100
 
101
-GError *list(char *** paths, char *** accts, unsigned int *list_l) {
101
+GError *list(char *ref_label, char *** paths, char *** accts, unsigned int *list_l) {
102 102
 	GList *items;
103 103
 	GError *err = NULL;
104 104
 	SecretService *service;
105 105
 	SecretSearchFlags flags = SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK;
106
-	GHashTable *attributes;
107
-	g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
108
-	attributes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
106
+	GHashTable *attributes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
107
+
108
+	// List credentials with the right label only
109
+	g_hash_table_insert(attributes, g_strdup("label"), g_strdup(ref_label));
110
+
109 111
 	service = secret_service_get_sync(SECRET_SERVICE_NONE, NULL, &err);
112
+	if (err != NULL) {
113
+		return err;
114
+	}
115
+
110 116
 	items = secret_service_search_sync(service, NULL, attributes, flags, NULL, &err);
111 117
 	int numKeys = g_list_length(items);
112 118
 	if (err != NULL) {
113 119
 		return err;
114 120
 	}
115
-	*paths = (char **) malloc((int)sizeof(char *)*numKeys);
116
-	*accts = (char **) malloc((int)sizeof(char *)*numKeys);
121
+
122
+	char **tmp_paths = (char **) calloc(1,(int)sizeof(char *)*numKeys);
123
+	char **tmp_accts = (char **) calloc(1,(int)sizeof(char *)*numKeys);
124
+
117 125
 	// items now contains our keys from the gnome keyring
118 126
 	// we will now put it in our two lists to return it to go
119 127
 	GList *current;
... ...
@@ -121,21 +131,25 @@ GError *list(char *** paths, char *** accts, unsigned int *list_l) {
121 121
 	for(current = items; current!=NULL; current = current->next) {
122 122
 		char *pathTmp = secret_item_get_label(current->data);
123 123
 		// you cannot have a key without a label in the gnome keyring
124
-		char *acctTmp = get_username(current->data);
124
+		char *acctTmp = get_attribute("username",current->data);
125 125
 		if (acctTmp==NULL) {
126 126
 			acctTmp = "account not defined";
127 127
 		}
128
-		char *path = (char *) malloc(strlen(pathTmp));
129
-		char *acct = (char *) malloc(strlen(acctTmp));
130
-		path = pathTmp;
131
-		acct = acctTmp;
132
-		(*paths)[listNumber] = (char *) malloc(sizeof(char)*(strlen(path)));
133
-		memcpy((*paths)[listNumber], path, sizeof(char)*(strlen(path)));
134
-		(*accts)[listNumber] = (char *) malloc(sizeof(char)*(strlen(acct)));
135
-		memcpy((*accts)[listNumber], acct, sizeof(char)*(strlen(acct)));
128
+
129
+		tmp_paths[listNumber] = (char *) calloc(1, sizeof(char)*(strlen(pathTmp)+1));
130
+		tmp_accts[listNumber] = (char *) calloc(1, sizeof(char)*(strlen(acctTmp)+1));
131
+
132
+		memcpy(tmp_paths[listNumber], pathTmp, sizeof(char)*(strlen(pathTmp)+1));
133
+		memcpy(tmp_accts[listNumber], acctTmp, sizeof(char)*(strlen(acctTmp)+1));
134
+
136 135
 		listNumber = listNumber + 1;
137 136
 	}
138
-	*list_l = numKeys;
137
+
138
+	*paths = (char **) realloc(tmp_paths, (int)sizeof(char *)*listNumber);
139
+	*accts = (char **) realloc(tmp_accts, (int)sizeof(char *)*listNumber);
140
+
141
+	*list_l = listNumber;
142
+
139 143
 	return NULL;
140 144
 }
141 145
 
... ...
@@ -22,6 +22,8 @@ func (h Secretservice) Add(creds *credentials.Credentials) error {
22 22
 	if creds == nil {
23 23
 		return errors.New("missing credentials")
24 24
 	}
25
+	credsLabel := C.CString(credentials.CredsLabel)
26
+	defer C.free(unsafe.Pointer(credsLabel))
25 27
 	server := C.CString(creds.ServerURL)
26 28
 	defer C.free(unsafe.Pointer(server))
27 29
 	username := C.CString(creds.Username)
... ...
@@ -29,7 +31,7 @@ func (h Secretservice) Add(creds *credentials.Credentials) error {
29 29
 	secret := C.CString(creds.Secret)
30 30
 	defer C.free(unsafe.Pointer(secret))
31 31
 
32
-	if err := C.add(server, username, secret); err != nil {
32
+	if err := C.add(credsLabel, server, username, secret); err != nil {
33 33
 		defer C.g_error_free(err)
34 34
 		errMsg := (*C.char)(unsafe.Pointer(err.message))
35 35
 		return errors.New(C.GoString(errMsg))
... ...
@@ -79,14 +81,17 @@ func (h Secretservice) Get(serverURL string) (string, string, error) {
79 79
 	return user, pass, nil
80 80
 }
81 81
 
82
-// List returns the stored URLs and corresponding usernames.
82
+// List returns the stored URLs and corresponding usernames for a given credentials label
83 83
 func (h Secretservice) List() (map[string]string, error) {
84
+	credsLabelC := C.CString(credentials.CredsLabel)
85
+	defer C.free(unsafe.Pointer(credsLabelC))
86
+
84 87
 	var pathsC **C.char
85 88
 	defer C.free(unsafe.Pointer(pathsC))
86 89
 	var acctsC **C.char
87 90
 	defer C.free(unsafe.Pointer(acctsC))
88 91
 	var listLenC C.uint
89
-	err := C.list(&pathsC, &acctsC, &listLenC)
92
+	err := C.list(credsLabelC, &pathsC, &acctsC, &listLenC)
90 93
 	if err != nil {
91 94
 		defer C.free(unsafe.Pointer(err))
92 95
 		return nil, errors.New("Error from list function in secretservice_linux.c likely due to error in secretservice library")
... ...
@@ -94,10 +99,14 @@ func (h Secretservice) List() (map[string]string, error) {
94 94
 	defer C.freeListData(&pathsC, listLenC)
95 95
 	defer C.freeListData(&acctsC, listLenC)
96 96
 
97
+	resp := make(map[string]string)
98
+
97 99
 	listLen := int(listLenC)
100
+	if listLen == 0 {
101
+		return resp, nil
102
+	}
98 103
 	pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
99 104
 	acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
100
-	resp := make(map[string]string)
101 105
 	for i := 0; i < listLen; i++ {
102 106
 		resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i])
103 107
 	}
... ...
@@ -6,8 +6,8 @@ const SecretSchema *docker_get_schema(void) G_GNUC_CONST;
6 6
 
7 7
 #define DOCKER_SCHEMA docker_get_schema()
8 8
 
9
-GError *add(char *server, char *username, char *secret);
9
+GError *add(char *label, char *server, char *username, char *secret);
10 10
 GError *delete(char *server);
11 11
 GError *get(char *server, char **username, char **secret);
12
-GError *list(char *** paths, char *** accts, unsigned int *list_l);
12
+GError *list(char *label, char *** paths, char *** accts, unsigned int *list_l);
13 13
 void freeListData(char *** data, unsigned int length);