]>
Commit | Line | Data |
---|---|---|
e23a0ce8 LP |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright 2010 Lennart Poettering | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
9 | under the terms of the GNU General Public License as published by | |
10 | the Free Software Foundation; either version 2 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU General Public License | |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
20 | ***/ | |
21 | ||
22 | #include <string.h> | |
7f4e0805 | 23 | #include <errno.h> |
b853f6e9 | 24 | #include <sys/mman.h> |
7f4e0805 LP |
25 | |
26 | #include <libcryptsetup.h> | |
e23a0ce8 LP |
27 | |
28 | #include "log.h" | |
29 | #include "util.h" | |
7f4e0805 LP |
30 | #include "ask-password-api.h" |
31 | ||
260ab287 | 32 | static const char *opt_type = NULL; /* LUKS1 or PLAIN */ |
7f4e0805 | 33 | static char *opt_cipher = NULL; |
260ab287 | 34 | static unsigned opt_key_size = 0; |
7f4e0805 | 35 | static char *opt_hash = NULL; |
260ab287 | 36 | static unsigned opt_tries = 0; |
7f4e0805 LP |
37 | static bool opt_readonly = false; |
38 | static bool opt_verify = false; | |
260ab287 | 39 | static usec_t opt_timeout = 0; |
7f4e0805 | 40 | |
1fc76335 LP |
41 | /* Options Debian's crypttab knows we don't: |
42 | ||
43 | offset= | |
44 | skip= | |
45 | precheck= | |
46 | check= | |
47 | checkargs= | |
48 | noearly= | |
49 | loud= | |
50 | keyscript= | |
51 | */ | |
52 | ||
7f4e0805 LP |
53 | static int parse_one_option(const char *option) { |
54 | assert(option); | |
55 | ||
56 | /* Handled outside of this tool */ | |
260ab287 | 57 | if (streq(option, "noauto")) |
7f4e0805 LP |
58 | return 0; |
59 | ||
60 | if (startswith(option, "cipher=")) { | |
61 | char *t; | |
62 | ||
63 | if (!(t = strdup(option+7))) | |
64 | return -ENOMEM; | |
65 | ||
66 | free(opt_cipher); | |
67 | opt_cipher = t; | |
68 | ||
69 | } else if (startswith(option, "size=")) { | |
70 | ||
260ab287 | 71 | if (safe_atou(option+5, &opt_key_size) < 0) { |
7f4e0805 LP |
72 | log_error("size= parse failure, ignoring."); |
73 | return 0; | |
74 | } | |
75 | ||
76 | } else if (startswith(option, "hash=")) { | |
77 | char *t; | |
78 | ||
79 | if (!(t = strdup(option+5))) | |
80 | return -ENOMEM; | |
81 | ||
82 | free(opt_hash); | |
83 | opt_hash = t; | |
84 | ||
85 | } else if (startswith(option, "tries=")) { | |
86 | ||
87 | if (safe_atou(option+6, &opt_tries) < 0) { | |
88 | log_error("tries= parse failure, ignoring."); | |
89 | return 0; | |
90 | } | |
91 | ||
92 | } else if (streq(option, "readonly")) | |
93 | opt_readonly = true; | |
94 | else if (streq(option, "verify")) | |
95 | opt_verify = true; | |
260ab287 LP |
96 | else if (streq(option, "luks")) |
97 | opt_type = CRYPT_LUKS1; | |
98 | else if (streq(option, "plain") || | |
99 | streq(option, "swap") || | |
100 | streq(option, "tmp")) | |
101 | opt_type = CRYPT_PLAIN; | |
7f4e0805 LP |
102 | else if (startswith(option, "timeout=")) { |
103 | ||
260ab287 | 104 | if (parse_usec(option+8, &opt_timeout) < 0) { |
7f4e0805 LP |
105 | log_error("timeout= parse failure, ignoring."); |
106 | return 0; | |
107 | } | |
108 | ||
109 | } else | |
110 | log_error("Encountered unknown /etc/crypttab option '%s', ignoring.", option); | |
111 | ||
112 | return 0; | |
113 | } | |
114 | ||
115 | static int parse_options(const char *options) { | |
116 | char *state; | |
117 | char *w; | |
118 | size_t l; | |
119 | ||
120 | assert(options); | |
121 | ||
122 | FOREACH_WORD_SEPARATOR(w, l, options, ",", state) { | |
123 | char *o; | |
124 | int r; | |
125 | ||
126 | if (!(o = strndup(w, l))) | |
127 | return -ENOMEM; | |
128 | ||
129 | r = parse_one_option(o); | |
130 | free(o); | |
131 | ||
132 | if (r < 0) | |
133 | return r; | |
134 | } | |
135 | ||
136 | return 0; | |
137 | } | |
138 | ||
139 | static void log_glue(int level, const char *msg, void *usrptr) { | |
260ab287 | 140 | log_debug("%s", msg); |
7f4e0805 | 141 | } |
e23a0ce8 LP |
142 | |
143 | int main(int argc, char *argv[]) { | |
7f4e0805 LP |
144 | int r = EXIT_FAILURE; |
145 | struct crypt_device *cd = NULL; | |
e2d480b9 | 146 | char *password = NULL, *truncated_cipher = NULL; |
260ab287 | 147 | const char *cipher = NULL, *cipher_mode = NULL, *hash = NULL; |
e23a0ce8 LP |
148 | |
149 | if (argc < 3) { | |
150 | log_error("This program requires at least two arguments."); | |
151 | return EXIT_FAILURE; | |
152 | } | |
153 | ||
bb7df0da | 154 | log_set_target(LOG_TARGET_AUTO); |
e23a0ce8 LP |
155 | log_parse_environment(); |
156 | log_open(); | |
157 | ||
260ab287 | 158 | if (streq(argv[1], "attach")) { |
7f4e0805 LP |
159 | uint32_t flags = 0; |
160 | int k; | |
260ab287 | 161 | unsigned try; |
7f4e0805 | 162 | const char *key_file = NULL; |
260ab287 | 163 | usec_t until; |
e2d480b9 | 164 | crypt_status_info status; |
7f4e0805 LP |
165 | |
166 | if (argc < 4) { | |
167 | log_error("attach requires at least two arguments."); | |
168 | goto finish; | |
169 | } | |
170 | ||
1fc76335 LP |
171 | if (argc >= 5 && |
172 | argv[4][0] && | |
173 | !streq(argv[4], "-") && | |
174 | !streq(argv[4], "none")) { | |
7f4e0805 LP |
175 | |
176 | if (!path_is_absolute(argv[4])) | |
177 | log_error("Password file path %s is not absolute. Ignoring.", argv[4]); | |
178 | else | |
179 | key_file = argv[4]; | |
180 | } | |
181 | ||
182 | if (argc >= 6 && argv[5][0] && !streq(argv[5], "-")) | |
183 | parse_options(argv[5]); | |
e23a0ce8 | 184 | |
b853f6e9 LP |
185 | /* A delicious drop of snake oil */ |
186 | mlockall(MCL_FUTURE); | |
187 | ||
7f4e0805 LP |
188 | if ((k = crypt_init(&cd, argv[3]))) { |
189 | log_error("crypt_init() failed: %s", strerror(-k)); | |
190 | goto finish; | |
191 | } | |
e23a0ce8 | 192 | |
7f4e0805 | 193 | crypt_set_log_callback(cd, log_glue, NULL); |
7f4e0805 | 194 | |
260ab287 LP |
195 | status = crypt_status(cd, argv[2]); |
196 | if (status == CRYPT_ACTIVE || status == CRYPT_BUSY) { | |
197 | log_info("Volume %s already active.", argv[2]); | |
198 | r = EXIT_SUCCESS; | |
7f4e0805 LP |
199 | goto finish; |
200 | } | |
201 | ||
202 | if (opt_readonly) | |
203 | flags |= CRYPT_ACTIVATE_READONLY; | |
204 | ||
260ab287 LP |
205 | until = now(CLOCK_MONOTONIC) + (opt_timeout > 0 ? opt_timeout : 60 * USEC_PER_SEC); |
206 | ||
207 | opt_tries = opt_tries > 0 ? opt_tries : 3; | |
208 | opt_key_size = (opt_key_size > 0 ? opt_key_size : 256); | |
260ab287 LP |
209 | hash = opt_hash ? opt_hash : "ripemd160"; |
210 | ||
e2d480b9 LP |
211 | if (opt_cipher) { |
212 | size_t l; | |
213 | ||
214 | l = strcspn(opt_cipher, "-"); | |
215 | ||
216 | if (!(truncated_cipher = strndup(opt_cipher, l))) { | |
217 | log_error("Out of memory"); | |
218 | goto finish; | |
219 | } | |
220 | ||
221 | cipher = truncated_cipher; | |
222 | cipher_mode = opt_cipher[l] ? opt_cipher+l+1 : "plain"; | |
223 | } else { | |
224 | cipher = "aes"; | |
225 | cipher_mode = "cbc-essiv:sha256"; | |
226 | } | |
227 | ||
260ab287 LP |
228 | for (try = 0; try < opt_tries; try++) { |
229 | bool pass_volume_key = false; | |
230 | ||
231 | free(password); | |
232 | password = NULL; | |
233 | ||
234 | if (!key_file) { | |
494856b5 | 235 | char *text; |
260ab287 | 236 | |
42e19823 | 237 | if (asprintf(&text, "Please enter passphrase for disk %s!", argv[2]) < 0) { |
494856b5 LP |
238 | log_error("Out of memory"); |
239 | goto finish; | |
240 | } | |
241 | ||
242 | k = ask_password_auto(text, "drive-harddisk", until, &password); | |
243 | free(text); | |
244 | ||
245 | if (k < 0) { | |
260ab287 LP |
246 | log_error("Failed to query password: %s", strerror(-k)); |
247 | goto finish; | |
248 | } | |
249 | ||
250 | if (opt_verify) { | |
251 | char *password2 = NULL; | |
252 | ||
42e19823 | 253 | if (asprintf(&text, "Please enter passphrase for disk %s! (verification)", argv[2]) < 0) { |
494856b5 LP |
254 | log_error("Out of memory"); |
255 | goto finish; | |
256 | } | |
257 | ||
258 | k = ask_password_auto(text, "drive-harddisk", until, &password2); | |
259 | free(text); | |
260 | ||
261 | if (k < 0) { | |
260ab287 LP |
262 | log_error("Failed to query verification password: %s", strerror(-k)); |
263 | goto finish; | |
264 | } | |
265 | ||
266 | if (!streq(password, password2)) { | |
267 | log_warning("Passwords did not match, retrying."); | |
268 | free(password2); | |
269 | continue; | |
270 | } | |
271 | ||
272 | free(password2); | |
273 | } | |
274 | ||
275 | if (strlen(password)+1 < opt_key_size) { | |
276 | char *c; | |
277 | ||
278 | /* Pad password if necessary */ | |
279 | ||
280 | if (!(c = new(char, opt_key_size))) { | |
281 | log_error("Out of memory."); | |
282 | goto finish; | |
283 | } | |
284 | ||
285 | strncpy(c, password, opt_key_size); | |
286 | free(password); | |
287 | password = c; | |
288 | } | |
289 | } | |
290 | ||
291 | if (!opt_type || streq(opt_type, CRYPT_LUKS1)) | |
292 | k = crypt_load(cd, CRYPT_LUKS1, NULL); | |
293 | ||
294 | if ((!opt_type && k < 0) || streq_ptr(opt_type, CRYPT_PLAIN)) { | |
295 | struct crypt_params_plain params; | |
296 | ||
297 | zero(params); | |
298 | params.hash = hash; | |
299 | ||
300 | /* In contrast to what the name | |
301 | * crypt_setup() might suggest this | |
302 | * doesn't actually format anything, | |
303 | * it just configures encryption | |
304 | * parameters when used for plain | |
305 | * mode. */ | |
306 | k = crypt_format(cd, CRYPT_PLAIN, | |
307 | cipher, | |
308 | cipher_mode, | |
309 | NULL, | |
310 | NULL, | |
311 | opt_key_size / 8, | |
312 | ¶ms); | |
313 | ||
314 | pass_volume_key = streq(hash, "plain"); | |
315 | } | |
316 | ||
317 | if (k < 0) { | |
318 | log_error("Loading of cryptographic parameters failed: %s", strerror(-k)); | |
319 | goto finish; | |
320 | } | |
321 | ||
322 | log_info("Set cipher %s, mode %s, key size %i bits for device %s.", | |
323 | crypt_get_cipher(cd), | |
324 | crypt_get_cipher_mode(cd), | |
325 | crypt_get_volume_key_size(cd)*8, | |
326 | argv[3]); | |
327 | ||
328 | if (key_file) | |
329 | k = crypt_activate_by_keyfile(cd, argv[2], CRYPT_ANY_SLOT, key_file, opt_key_size, flags); | |
330 | else if (pass_volume_key) | |
331 | k = crypt_activate_by_volume_key(cd, argv[2], password, opt_key_size, flags); | |
332 | else | |
333 | k = crypt_activate_by_passphrase(cd, argv[2], CRYPT_ANY_SLOT, password, strlen(password), flags); | |
334 | ||
335 | if (k >= 0) | |
336 | break; | |
337 | ||
338 | if (k != -EPERM) { | |
339 | log_error("Failed to activate: %s", strerror(-k)); | |
340 | goto finish; | |
341 | } | |
342 | ||
343 | log_warning("Invalid passphrase."); | |
7f4e0805 LP |
344 | } |
345 | ||
260ab287 LP |
346 | if (try >= opt_tries) { |
347 | log_error("Too many attempts."); | |
348 | r = EXIT_FAILURE; | |
7f4e0805 LP |
349 | } |
350 | ||
351 | } else if (streq(argv[1], "detach")) { | |
352 | int k; | |
353 | ||
354 | if ((k = crypt_init_by_name(&cd, argv[2]))) { | |
355 | log_error("crypt_init() failed: %s", strerror(-k)); | |
356 | goto finish; | |
357 | } | |
358 | ||
359 | crypt_set_log_callback(cd, log_glue, NULL); | |
360 | ||
361 | if ((k = crypt_deactivate(cd, argv[2])) < 0) { | |
362 | log_error("Failed to deactivate: %s", strerror(-k)); | |
363 | goto finish; | |
364 | } | |
e23a0ce8 LP |
365 | |
366 | } else { | |
367 | log_error("Unknown verb %s.", argv[1]); | |
368 | goto finish; | |
369 | } | |
370 | ||
7f4e0805 LP |
371 | r = EXIT_SUCCESS; |
372 | ||
e23a0ce8 | 373 | finish: |
7f4e0805 LP |
374 | |
375 | if (cd) | |
376 | crypt_free(cd); | |
377 | ||
260ab287 | 378 | free(opt_cipher); |
260ab287 LP |
379 | free(opt_hash); |
380 | ||
e2d480b9 LP |
381 | free(truncated_cipher); |
382 | ||
260ab287 LP |
383 | free(password); |
384 | ||
e23a0ce8 LP |
385 | return r; |
386 | } |