/src/git/builtin/credential-store.c
Line | Count | Source (jump to first uncovered line) |
1 | | #include "builtin.h" |
2 | | #include "config.h" |
3 | | #include "gettext.h" |
4 | | #include "lockfile.h" |
5 | | #include "credential.h" |
6 | | #include "path.h" |
7 | | #include "string-list.h" |
8 | | #include "parse-options.h" |
9 | | #include "write-or-die.h" |
10 | | |
11 | | static struct lock_file credential_lock; |
12 | | |
13 | | static int parse_credential_file(const char *fn, |
14 | | struct credential *c, |
15 | | void (*match_cb)(struct credential *), |
16 | | void (*other_cb)(struct strbuf *), |
17 | | int match_password) |
18 | 0 | { |
19 | 0 | FILE *fh; |
20 | 0 | struct strbuf line = STRBUF_INIT; |
21 | 0 | struct credential entry = CREDENTIAL_INIT; |
22 | 0 | int found_credential = 0; |
23 | |
|
24 | 0 | fh = fopen(fn, "r"); |
25 | 0 | if (!fh) { |
26 | 0 | if (errno != ENOENT && errno != EACCES) |
27 | 0 | die_errno("unable to open %s", fn); |
28 | 0 | return found_credential; |
29 | 0 | } |
30 | | |
31 | 0 | while (strbuf_getline_lf(&line, fh) != EOF) { |
32 | 0 | if (!credential_from_url_gently(&entry, line.buf, 1) && |
33 | 0 | entry.username && entry.password && |
34 | 0 | credential_match(c, &entry, match_password)) { |
35 | 0 | found_credential = 1; |
36 | 0 | if (match_cb) { |
37 | 0 | match_cb(&entry); |
38 | 0 | break; |
39 | 0 | } |
40 | 0 | } |
41 | 0 | else if (other_cb) |
42 | 0 | other_cb(&line); |
43 | 0 | } |
44 | |
|
45 | 0 | credential_clear(&entry); |
46 | 0 | strbuf_release(&line); |
47 | 0 | fclose(fh); |
48 | 0 | return found_credential; |
49 | 0 | } |
50 | | |
51 | | static void print_entry(struct credential *c) |
52 | 0 | { |
53 | 0 | printf("username=%s\n", c->username); |
54 | 0 | printf("password=%s\n", c->password); |
55 | 0 | } |
56 | | |
57 | | static void print_line(struct strbuf *buf) |
58 | 0 | { |
59 | 0 | strbuf_addch(buf, '\n'); |
60 | 0 | write_or_die(get_lock_file_fd(&credential_lock), buf->buf, buf->len); |
61 | 0 | } |
62 | | |
63 | | static void rewrite_credential_file(const char *fn, struct credential *c, |
64 | | struct strbuf *extra, int match_password) |
65 | 0 | { |
66 | 0 | int timeout_ms = 1000; |
67 | |
|
68 | 0 | git_config_get_int("credentialstore.locktimeoutms", &timeout_ms); |
69 | 0 | if (hold_lock_file_for_update_timeout(&credential_lock, fn, 0, timeout_ms) < 0) |
70 | 0 | die_errno(_("unable to get credential storage lock in %d ms"), timeout_ms); |
71 | 0 | if (extra) |
72 | 0 | print_line(extra); |
73 | 0 | parse_credential_file(fn, c, NULL, print_line, match_password); |
74 | 0 | if (commit_lock_file(&credential_lock) < 0) |
75 | 0 | die_errno("unable to write credential store"); |
76 | 0 | } |
77 | | |
78 | | static int is_rfc3986_unreserved(char ch) |
79 | 0 | { |
80 | 0 | return isalnum(ch) || |
81 | 0 | ch == '-' || ch == '_' || ch == '.' || ch == '~'; |
82 | 0 | } |
83 | | |
84 | | static int is_rfc3986_reserved_or_unreserved(char ch) |
85 | 0 | { |
86 | 0 | if (is_rfc3986_unreserved(ch)) |
87 | 0 | return 1; |
88 | 0 | switch (ch) { |
89 | 0 | case '!': case '*': case '\'': case '(': case ')': case ';': |
90 | 0 | case ':': case '@': case '&': case '=': case '+': case '$': |
91 | 0 | case ',': case '/': case '?': case '#': case '[': case ']': |
92 | 0 | return 1; |
93 | 0 | } |
94 | 0 | return 0; |
95 | 0 | } |
96 | | |
97 | | static void store_credential_file(const char *fn, struct credential *c) |
98 | 0 | { |
99 | 0 | struct strbuf buf = STRBUF_INIT; |
100 | |
|
101 | 0 | strbuf_addf(&buf, "%s://", c->protocol); |
102 | 0 | strbuf_addstr_urlencode(&buf, c->username, is_rfc3986_unreserved); |
103 | 0 | strbuf_addch(&buf, ':'); |
104 | 0 | strbuf_addstr_urlencode(&buf, c->password, is_rfc3986_unreserved); |
105 | 0 | strbuf_addch(&buf, '@'); |
106 | 0 | if (c->host) |
107 | 0 | strbuf_addstr_urlencode(&buf, c->host, is_rfc3986_unreserved); |
108 | 0 | if (c->path) { |
109 | 0 | strbuf_addch(&buf, '/'); |
110 | 0 | strbuf_addstr_urlencode(&buf, c->path, |
111 | 0 | is_rfc3986_reserved_or_unreserved); |
112 | 0 | } |
113 | |
|
114 | 0 | rewrite_credential_file(fn, c, &buf, 0); |
115 | 0 | strbuf_release(&buf); |
116 | 0 | } |
117 | | |
118 | | static void store_credential(const struct string_list *fns, struct credential *c) |
119 | 0 | { |
120 | 0 | struct string_list_item *fn; |
121 | | |
122 | | /* |
123 | | * Sanity check that what we are storing is actually sensible. |
124 | | * In particular, we can't make a URL without a protocol field. |
125 | | * Without either a host or pathname (depending on the scheme), |
126 | | * we have no primary key. And without a username and password, |
127 | | * we are not actually storing a credential. |
128 | | */ |
129 | 0 | if (!c->protocol || !(c->host || c->path) || !c->username || !c->password) |
130 | 0 | return; |
131 | | |
132 | 0 | for_each_string_list_item(fn, fns) |
133 | 0 | if (!access(fn->string, F_OK)) { |
134 | 0 | store_credential_file(fn->string, c); |
135 | 0 | return; |
136 | 0 | } |
137 | | /* |
138 | | * Write credential to the filename specified by fns->items[0], thus |
139 | | * creating it |
140 | | */ |
141 | 0 | if (fns->nr) |
142 | 0 | store_credential_file(fns->items[0].string, c); |
143 | 0 | } |
144 | | |
145 | | static void remove_credential(const struct string_list *fns, struct credential *c) |
146 | 0 | { |
147 | 0 | struct string_list_item *fn; |
148 | | |
149 | | /* |
150 | | * Sanity check that we actually have something to match |
151 | | * against. The input we get is a restrictive pattern, |
152 | | * so technically a blank credential means "erase everything". |
153 | | * But it is too easy to accidentally send this, since it is equivalent |
154 | | * to empty input. So explicitly disallow it, and require that the |
155 | | * pattern have some actual content to match. |
156 | | */ |
157 | 0 | if (!c->protocol && !c->host && !c->path && !c->username) |
158 | 0 | return; |
159 | 0 | for_each_string_list_item(fn, fns) |
160 | 0 | if (!access(fn->string, F_OK)) |
161 | 0 | rewrite_credential_file(fn->string, c, NULL, 1); |
162 | 0 | } |
163 | | |
164 | | static void lookup_credential(const struct string_list *fns, struct credential *c) |
165 | 0 | { |
166 | 0 | struct string_list_item *fn; |
167 | |
|
168 | 0 | for_each_string_list_item(fn, fns) |
169 | 0 | if (parse_credential_file(fn->string, c, print_entry, NULL, 0)) |
170 | 0 | return; /* Found credential */ |
171 | 0 | } |
172 | | |
173 | | int cmd_credential_store(int argc, const char **argv, const char *prefix) |
174 | 0 | { |
175 | 0 | const char * const usage[] = { |
176 | 0 | "git credential-store [<options>] <action>", |
177 | 0 | NULL |
178 | 0 | }; |
179 | 0 | const char *op; |
180 | 0 | struct credential c = CREDENTIAL_INIT; |
181 | 0 | struct string_list fns = STRING_LIST_INIT_DUP; |
182 | 0 | char *file = NULL; |
183 | 0 | struct option options[] = { |
184 | 0 | OPT_STRING(0, "file", &file, "path", |
185 | 0 | "fetch and store credentials in <path>"), |
186 | 0 | OPT_END() |
187 | 0 | }; |
188 | |
|
189 | 0 | umask(077); |
190 | |
|
191 | 0 | argc = parse_options(argc, (const char **)argv, prefix, options, usage, 0); |
192 | 0 | if (argc != 1) |
193 | 0 | usage_with_options(usage, options); |
194 | 0 | op = argv[0]; |
195 | |
|
196 | 0 | if (file) { |
197 | 0 | string_list_append(&fns, file); |
198 | 0 | } else { |
199 | 0 | if ((file = interpolate_path("~/.git-credentials", 0))) |
200 | 0 | string_list_append_nodup(&fns, file); |
201 | 0 | file = xdg_config_home("credentials"); |
202 | 0 | if (file) |
203 | 0 | string_list_append_nodup(&fns, file); |
204 | 0 | } |
205 | 0 | if (!fns.nr) |
206 | 0 | die("unable to set up default path; use --file"); |
207 | | |
208 | 0 | if (credential_read(&c, stdin, CREDENTIAL_OP_HELPER) < 0) |
209 | 0 | die("unable to read credential"); |
210 | | |
211 | 0 | if (!strcmp(op, "get")) |
212 | 0 | lookup_credential(&fns, &c); |
213 | 0 | else if (!strcmp(op, "erase")) |
214 | 0 | remove_credential(&fns, &c); |
215 | 0 | else if (!strcmp(op, "store")) |
216 | 0 | store_credential(&fns, &c); |
217 | 0 | else |
218 | 0 | ; /* Ignore unknown operation. */ |
219 | |
|
220 | 0 | string_list_clear(&fns, 0); |
221 | 0 | credential_clear(&c); |
222 | 0 | return 0; |
223 | 0 | } |