33#include <stdlib.h>
44#include <Security/Security.h>
55
6- static SecProtocolType protocol ;
7- static char * host ;
8- static char * path ;
9- static char * username ;
10- static char * password ;
11- static UInt16 port ;
12-
13- __attribute__((format (printf , 1 , 2 )))
6+ #define ENCODING kCFStringEncodingUTF8
7+ static CFStringRef protocol ; /* Stores constant strings - not memory managed */
8+ static CFStringRef host ;
9+ static CFStringRef path ;
10+ static CFStringRef username ;
11+ static CFDataRef password ;
12+ static CFNumberRef port ;
13+
14+ static void clear_credential (void )
15+ {
16+ if (host ) {
17+ CFRelease (host );
18+ host = NULL ;
19+ }
20+ if (path ) {
21+ CFRelease (path );
22+ path = NULL ;
23+ }
24+ if (username ) {
25+ CFRelease (username );
26+ username = NULL ;
27+ }
28+ if (password ) {
29+ CFRelease (password );
30+ password = NULL ;
31+ }
32+ if (port ) {
33+ CFRelease (port );
34+ port = NULL ;
35+ }
36+ }
37+
38+ __attribute__((format (printf , 1 , 2 ), __noreturn__ ))
1439static void die (const char * err , ...)
1540{
1641 char msg [4096 ];
@@ -19,96 +44,178 @@ static void die(const char *err, ...)
1944 vsnprintf (msg , sizeof (msg ), err , params );
2045 fprintf (stderr , "%s\n" , msg );
2146 va_end (params );
47+ clear_credential ();
2248 exit (1 );
2349}
2450
25- static void * xstrdup ( const char * s1 )
51+ static void * xmalloc ( size_t len )
2652{
27- void * ret = strdup ( s1 );
53+ void * ret = malloc ( len );
2854 if (!ret )
2955 die ("Out of memory" );
3056 return ret ;
3157}
3258
33- #define KEYCHAIN_ITEM (x ) (x ? strlen(x) : 0), x
34- #define KEYCHAIN_ARGS \
35- NULL, /* default keychain */ \
36- KEYCHAIN_ITEM (host ), \
37- 0, NULL, /* account domain */ \
38- KEYCHAIN_ITEM (username ), \
39- KEYCHAIN_ITEM (path ), \
40- port , \
41- protocol , \
42- kSecAuthenticationTypeDefault
43-
44- static void write_item (const char * what , const char * buf , int len )
59+ static CFDictionaryRef create_dictionary (CFAllocatorRef allocator , ...)
60+ {
61+ va_list args ;
62+ const void * key ;
63+ CFMutableDictionaryRef result ;
64+
65+ result = CFDictionaryCreateMutable (allocator ,
66+ 0 ,
67+ & kCFTypeDictionaryKeyCallBacks ,
68+ & kCFTypeDictionaryValueCallBacks );
69+
70+
71+ va_start (args , allocator );
72+ while ((key = va_arg (args , const void * )) != NULL ) {
73+ const void * value ;
74+ value = va_arg (args , const void * );
75+ if (value )
76+ CFDictionarySetValue (result , key , value );
77+ }
78+ va_end (args );
79+
80+ return result ;
81+ }
82+
83+ #define CREATE_SEC_ATTRIBUTES (...) \
84+ create_dictionary(kCFAllocatorDefault, \
85+ kSecClass, kSecClassInternetPassword, \
86+ kSecAttrServer, host, \
87+ kSecAttrAccount, username, \
88+ kSecAttrPath, path, \
89+ kSecAttrPort, port, \
90+ kSecAttrProtocol, protocol, \
91+ kSecAttrAuthenticationType, \
92+ kSecAttrAuthenticationTypeDefault, \
93+ __VA_ARGS__);
94+
95+ static void write_item (const char * what , const char * buf , size_t len )
4596{
4697 printf ("%s=" , what );
4798 fwrite (buf , 1 , len , stdout );
4899 putchar ('\n' );
49100}
50101
51- static void find_username_in_item (SecKeychainItemRef item )
102+ static void find_username_in_item (CFDictionaryRef item )
52103{
53- SecKeychainAttributeList list ;
54- SecKeychainAttribute attr ;
104+ CFStringRef account_ref ;
105+ char * username_buf ;
106+ CFIndex buffer_len ;
55107
56- list .count = 1 ;
57- list .attr = & attr ;
58- attr .tag = kSecAccountItemAttr ;
108+ account_ref = CFDictionaryGetValue (item , kSecAttrAccount );
109+ if (!account_ref )
110+ {
111+ write_item ("username" , "" , 0 );
112+ return ;
113+ }
59114
60- if (SecKeychainItemCopyContent (item , NULL , & list , NULL , NULL ))
115+ username_buf = (char * )CFStringGetCStringPtr (account_ref , ENCODING );
116+ if (username_buf )
117+ {
118+ write_item ("username" , username_buf , strlen (username_buf ));
61119 return ;
120+ }
62121
63- write_item ("username" , attr .data , attr .length );
64- SecKeychainItemFreeContent (& list , NULL );
122+ /* If we can't get a CString pointer then
123+ * we need to allocate our own buffer */
124+ buffer_len = CFStringGetMaximumSizeForEncoding (
125+ CFStringGetLength (account_ref ), ENCODING ) + 1 ;
126+ username_buf = xmalloc (buffer_len );
127+ if (CFStringGetCString (account_ref ,
128+ username_buf ,
129+ buffer_len ,
130+ ENCODING )) {
131+ write_item ("username" , username_buf , buffer_len - 1 );
132+ }
133+ free (username_buf );
65134}
66135
67- static void find_internet_password (void )
136+ static OSStatus find_internet_password (void )
68137{
69- void * buf ;
70- UInt32 len ;
71- SecKeychainItemRef item ;
138+ CFDictionaryRef attrs ;
139+ CFDictionaryRef item ;
140+ CFDataRef data ;
141+ OSStatus result ;
72142
73- if (SecKeychainFindInternetPassword (KEYCHAIN_ARGS , & len , & buf , & item ))
74- return ;
143+ attrs = CREATE_SEC_ATTRIBUTES (kSecMatchLimit , kSecMatchLimitOne ,
144+ kSecReturnAttributes , kCFBooleanTrue ,
145+ kSecReturnData , kCFBooleanTrue ,
146+ NULL );
147+ result = SecItemCopyMatching (attrs , (CFTypeRef * )& item );
148+ if (result ) {
149+ goto out ;
150+ }
75151
76- write_item ("password" , buf , len );
152+ data = CFDictionaryGetValue (item , kSecValueData );
153+
154+ write_item ("password" ,
155+ (const char * )CFDataGetBytePtr (data ),
156+ CFDataGetLength (data ));
77157 if (!username )
78158 find_username_in_item (item );
79159
80- SecKeychainItemFreeContent (NULL , buf );
160+ CFRelease (item );
161+
162+ out :
163+ CFRelease (attrs );
164+
165+ /* We consider not found to not be an error */
166+ if (result == errSecItemNotFound )
167+ result = errSecSuccess ;
168+
169+ return result ;
81170}
82171
83- static void delete_internet_password (void )
172+ static OSStatus delete_internet_password (void )
84173{
85- SecKeychainItemRef item ;
174+ CFDictionaryRef attrs ;
175+ OSStatus result ;
86176
87177 /*
88178 * Require at least a protocol and host for removal, which is what git
89179 * will give us; if you want to do something more fancy, use the
90180 * Keychain manager.
91181 */
92182 if (!protocol || !host )
93- return ;
183+ return -1 ;
94184
95- if (SecKeychainFindInternetPassword (KEYCHAIN_ARGS , 0 , NULL , & item ))
96- return ;
185+ attrs = CREATE_SEC_ATTRIBUTES (NULL );
186+ result = SecItemDelete (attrs );
187+ CFRelease (attrs );
188+
189+ /* We consider not found to not be an error */
190+ if (result == errSecItemNotFound )
191+ result = errSecSuccess ;
97192
98- SecKeychainItemDelete ( item ) ;
193+ return result ;
99194}
100195
101- static void add_internet_password (void )
196+ static OSStatus add_internet_password (void )
102197{
198+ CFDictionaryRef attrs ;
199+ OSStatus result ;
200+
103201 /* Only store complete credentials */
104202 if (!protocol || !host || !username || !password )
105- return ;
203+ return -1 ;
106204
107- if (SecKeychainAddInternetPassword (
108- KEYCHAIN_ARGS ,
109- KEYCHAIN_ITEM (password ),
110- NULL ))
111- return ;
205+ attrs = CREATE_SEC_ATTRIBUTES (kSecValueData , password ,
206+ NULL );
207+
208+ result = SecItemAdd (attrs , NULL );
209+ if (result == errSecDuplicateItem ) {
210+ CFDictionaryRef query ;
211+ query = CREATE_SEC_ATTRIBUTES (NULL );
212+ result = SecItemUpdate (query , attrs );
213+ CFRelease (query );
214+ }
215+
216+ CFRelease (attrs );
217+
218+ return result ;
112219}
113220
114221static void read_credential (void )
@@ -131,36 +238,52 @@ static void read_credential(void)
131238
132239 if (!strcmp (buf , "protocol" )) {
133240 if (!strcmp (v , "imap" ))
134- protocol = kSecProtocolTypeIMAP ;
241+ protocol = kSecAttrProtocolIMAP ;
135242 else if (!strcmp (v , "imaps" ))
136- protocol = kSecProtocolTypeIMAPS ;
243+ protocol = kSecAttrProtocolIMAPS ;
137244 else if (!strcmp (v , "ftp" ))
138- protocol = kSecProtocolTypeFTP ;
245+ protocol = kSecAttrProtocolFTP ;
139246 else if (!strcmp (v , "ftps" ))
140- protocol = kSecProtocolTypeFTPS ;
247+ protocol = kSecAttrProtocolFTPS ;
141248 else if (!strcmp (v , "https" ))
142- protocol = kSecProtocolTypeHTTPS ;
249+ protocol = kSecAttrProtocolHTTPS ;
143250 else if (!strcmp (v , "http" ))
144- protocol = kSecProtocolTypeHTTP ;
251+ protocol = kSecAttrProtocolHTTP ;
145252 else if (!strcmp (v , "smtp" ))
146- protocol = kSecProtocolTypeSMTP ;
147- else /* we don't yet handle other protocols */
253+ protocol = kSecAttrProtocolSMTP ;
254+ else {
255+ /* we don't yet handle other protocols */
256+ clear_credential ();
148257 exit (0 );
258+ }
149259 }
150260 else if (!strcmp (buf , "host" )) {
151261 char * colon = strchr (v , ':' );
152262 if (colon ) {
263+ UInt16 port_i ;
153264 * colon ++ = '\0' ;
154- port = atoi (colon );
265+ port_i = atoi (colon );
266+ port = CFNumberCreate (kCFAllocatorDefault ,
267+ kCFNumberShortType ,
268+ & port_i );
155269 }
156- host = xstrdup (v );
270+ host = CFStringCreateWithCString (kCFAllocatorDefault ,
271+ v ,
272+ ENCODING );
157273 }
158274 else if (!strcmp (buf , "path" ))
159- path = xstrdup (v );
275+ path = CFStringCreateWithCString (kCFAllocatorDefault ,
276+ v ,
277+ ENCODING );
160278 else if (!strcmp (buf , "username" ))
161- username = xstrdup (v );
279+ username = CFStringCreateWithCString (
280+ kCFAllocatorDefault ,
281+ v ,
282+ ENCODING );
162283 else if (!strcmp (buf , "password" ))
163- password = xstrdup (v );
284+ password = CFDataCreate (kCFAllocatorDefault ,
285+ (UInt8 * )v ,
286+ strlen (v ));
164287 /*
165288 * Ignore other lines; we don't know what they mean, but
166289 * this future-proofs us when later versions of git do
@@ -173,6 +296,7 @@ static void read_credential(void)
173296
174297int main (int argc , const char * * argv )
175298{
299+ OSStatus result = 0 ;
176300 const char * usage =
177301 "usage: git credential-osxkeychain <get|store|erase>" ;
178302
@@ -182,12 +306,17 @@ int main(int argc, const char **argv)
182306 read_credential ();
183307
184308 if (!strcmp (argv [1 ], "get" ))
185- find_internet_password ();
309+ result = find_internet_password ();
186310 else if (!strcmp (argv [1 ], "store" ))
187- add_internet_password ();
311+ result = add_internet_password ();
188312 else if (!strcmp (argv [1 ], "erase" ))
189- delete_internet_password ();
313+ result = delete_internet_password ();
190314 /* otherwise, ignore unknown action */
191315
316+ if (result )
317+ die ("failed to %s: %d" , argv [1 ], (int )result );
318+
319+ clear_credential ();
320+
192321 return 0 ;
193322}
0 commit comments