Mercurial > dropbear
comparison svr-authpubkey.c @ 1920:1489449eceb1
Check authorized_keys permissions as the user
This is necessary on NFS with squash root.
Based on work from Chris Dragan
This commit also tidies some trailing whitespace.
Fixes github pull #107
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Wed, 30 Mar 2022 12:56:09 +0800 |
parents | f8ed10efaaac |
children |
comparison
equal
deleted
inserted
replaced
1919:ff8a81386a2b | 1920:1489449eceb1 |
---|---|
1 /* | 1 /* |
2 * Dropbear - a SSH2 server | 2 * Dropbear - a SSH2 server |
3 * | 3 * |
4 * Copyright (c) 2002,2003 Matt Johnston | 4 * Copyright (c) 2002,2003 Matt Johnston |
5 * All rights reserved. | 5 * All rights reserved. |
6 * | 6 * |
7 * Permission is hereby granted, free of charge, to any person obtaining a copy | 7 * Permission is hereby granted, free of charge, to any person obtaining a copy |
8 * of this software and associated documentation files (the "Software"), to deal | 8 * of this software and associated documentation files (the "Software"), to deal |
9 * in the Software without restriction, including without limitation the rights | 9 * in the Software without restriction, including without limitation the rights |
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | 10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
11 * copies of the Software, and to permit persons to whom the Software is | 11 * copies of the Software, and to permit persons to whom the Software is |
12 * furnished to do so, subject to the following conditions: | 12 * furnished to do so, subject to the following conditions: |
13 * | 13 * |
14 * The above copyright notice and this permission notice shall be included in | 14 * The above copyright notice and this permission notice shall be included in |
15 * all copies or substantial portions of the Software. | 15 * all copies or substantial portions of the Software. |
16 * | 16 * |
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | 18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | 19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | 20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | 22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
23 * SOFTWARE. */ | 23 * SOFTWARE. */ |
24 /* | 24 /* |
25 * This file incorporates work covered by the following copyright and | 25 * This file incorporates work covered by the following copyright and |
26 * permission notice: | 26 * permission notice: |
27 * | 27 * |
28 * Copyright (c) 2000 Markus Friedl. All rights reserved. | 28 * Copyright (c) 2000 Markus Friedl. All rights reserved. |
29 * | 29 * |
30 * Redistribution and use in source and binary forms, with or without | 30 * Redistribution and use in source and binary forms, with or without |
31 * modification, are permitted provided that the following conditions | 31 * modification, are permitted provided that the following conditions |
32 * are met: | 32 * are met: |
33 * 1. Redistributions of source code must retain the above copyright | 33 * 1. Redistributions of source code must retain the above copyright |
34 * notice, this list of conditions and the following disclaimer. | 34 * notice, this list of conditions and the following disclaimer. |
35 * 2. Redistributions in binary form must reproduce the above copyright | 35 * 2. Redistributions in binary form must reproduce the above copyright |
36 * notice, this list of conditions and the following disclaimer in the | 36 * notice, this list of conditions and the following disclaimer in the |
37 * documentation and/or other materials provided with the distribution. | 37 * documentation and/or other materials provided with the distribution. |
38 * | 38 * |
39 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | 39 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
40 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | 40 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
41 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | 41 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
42 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | 42 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
43 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | 43 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
46 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 46 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
47 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | 47 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
48 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 48 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
49 * | 49 * |
50 * This copyright and permission notice applies to the code parsing public keys | 50 * This copyright and permission notice applies to the code parsing public keys |
51 * options string which can also be found in OpenSSH auth2-pubkey.c file | 51 * options string which can also be found in OpenSSH auth2-pubkey.c file |
52 * (user_key_allowed2). It has been adapted to work with buffers. | 52 * (user_key_allowed2). It has been adapted to work with buffers. |
53 * | 53 * |
54 */ | 54 */ |
55 | 55 |
56 /* Process a pubkey auth request */ | 56 /* Process a pubkey auth request */ |
106 keybloblen = buf_getint(ses.payload); | 106 keybloblen = buf_getint(ses.payload); |
107 keyblob = buf_getptr(ses.payload, keybloblen); | 107 keyblob = buf_getptr(ses.payload, keybloblen); |
108 | 108 |
109 if (!valid_user) { | 109 if (!valid_user) { |
110 /* Return failure once we have read the contents of the packet | 110 /* Return failure once we have read the contents of the packet |
111 required to validate a public key. | 111 required to validate a public key. |
112 Avoids blind user enumeration though it isn't possible to prevent | 112 Avoids blind user enumeration though it isn't possible to prevent |
113 testing for user existence if the public key is known */ | 113 testing for user existence if the public key is known */ |
114 send_msg_userauth_failure(0, 0); | 114 send_msg_userauth_failure(0, 0); |
115 goto out; | 115 goto out; |
116 } | 116 } |
128 if (svr_ses.plugin_instance != NULL) { | 128 if (svr_ses.plugin_instance != NULL) { |
129 char *options_buf; | 129 char *options_buf; |
130 if (svr_ses.plugin_instance->checkpubkey( | 130 if (svr_ses.plugin_instance->checkpubkey( |
131 svr_ses.plugin_instance, | 131 svr_ses.plugin_instance, |
132 &ses.plugin_session, | 132 &ses.plugin_session, |
133 keyalgo, | 133 keyalgo, |
134 keyalgolen, | 134 keyalgolen, |
135 keyblob, | 135 keyblob, |
136 keybloblen, | 136 keybloblen, |
137 ses.authstate.username) == DROPBEAR_SUCCESS) { | 137 ses.authstate.username) == DROPBEAR_SUCCESS) { |
138 /* Success */ | 138 /* Success */ |
139 auth_failure = 0; | 139 auth_failure = 0; |
140 | 140 |
141 /* Options provided? */ | 141 /* Options provided? */ |
142 options_buf = ses.plugin_session->get_options(ses.plugin_session); | 142 options_buf = ses.plugin_session->get_options(ses.plugin_session); |
143 if (options_buf) { | 143 if (options_buf) { |
144 struct buf temp_buf = { | 144 struct buf temp_buf = { |
145 .data = (unsigned char *)options_buf, | 145 .data = (unsigned char *)options_buf, |
146 .len = strlen(options_buf), | 146 .len = strlen(options_buf), |
147 .pos = 0, | 147 .pos = 0, |
148 .size = 0 | 148 .size = 0 |
149 }; | 149 }; |
172 send_msg_userauth_pk_ok(sigalgo, sigalgolen, keyblob, keybloblen); | 172 send_msg_userauth_pk_ok(sigalgo, sigalgolen, keyblob, keybloblen); |
173 goto out; | 173 goto out; |
174 } | 174 } |
175 | 175 |
176 /* now we can actually verify the signature */ | 176 /* now we can actually verify the signature */ |
177 | 177 |
178 /* get the key */ | 178 /* get the key */ |
179 key = new_sign_key(); | 179 key = new_sign_key(); |
180 if (buf_get_pub_key(ses.payload, key, &keytype) == DROPBEAR_FAILURE) { | 180 if (buf_get_pub_key(ses.payload, key, &keytype) == DROPBEAR_FAILURE) { |
181 send_msg_userauth_failure(0, 1); | 181 send_msg_userauth_failure(0, 1); |
182 goto out; | 182 goto out; |
189 signbuf = buf_new(ses.payload->pos + 4 + ses.session_id->len); | 189 signbuf = buf_new(ses.payload->pos + 4 + ses.session_id->len); |
190 buf_putbufstring(signbuf, ses.session_id); | 190 buf_putbufstring(signbuf, ses.session_id); |
191 | 191 |
192 /* The entire contents of the payload prior. */ | 192 /* The entire contents of the payload prior. */ |
193 buf_setpos(ses.payload, ses.payload_beginning); | 193 buf_setpos(ses.payload, ses.payload_beginning); |
194 buf_putbytes(signbuf, | 194 buf_putbytes(signbuf, |
195 buf_getptr(ses.payload, sign_payload_length), | 195 buf_getptr(ses.payload, sign_payload_length), |
196 sign_payload_length); | 196 sign_payload_length); |
197 buf_incrpos(ses.payload, sign_payload_length); | 197 buf_incrpos(ses.payload, sign_payload_length); |
198 | 198 |
199 buf_setpos(signbuf, 0); | 199 buf_setpos(signbuf, 0); |
211 if ((ses.plugin_session != NULL) && (svr_ses.plugin_instance->auth_success != NULL)) { | 211 if ((ses.plugin_session != NULL) && (svr_ses.plugin_instance->auth_success != NULL)) { |
212 /* Was authenticated through the external plugin. tell plugin that signature verification was ok */ | 212 /* Was authenticated through the external plugin. tell plugin that signature verification was ok */ |
213 svr_ses.plugin_instance->auth_success(ses.plugin_session); | 213 svr_ses.plugin_instance->auth_success(ses.plugin_session); |
214 } | 214 } |
215 #endif | 215 #endif |
216 | |
217 } else { | 216 } else { |
218 dropbear_log(LOG_WARNING, | 217 dropbear_log(LOG_WARNING, |
219 "Pubkey auth bad signature for '%s' with key %s from %s", | 218 "Pubkey auth bad signature for '%s' with key %s from %s", |
220 ses.authstate.pw_name, fp, svr_ses.addrstring); | 219 ses.authstate.pw_name, fp, svr_ses.addrstring); |
221 send_msg_userauth_failure(0, 1); | 220 send_msg_userauth_failure(0, 1); |
288 if (strncmp((const char *) buf_getptr(line, algolen), algo, algolen) != 0) { | 287 if (strncmp((const char *) buf_getptr(line, algolen), algo, algolen) != 0) { |
289 int is_comment = 0; | 288 int is_comment = 0; |
290 unsigned char *options_start = NULL; | 289 unsigned char *options_start = NULL; |
291 int options_len = 0; | 290 int options_len = 0; |
292 int escape, quoted; | 291 int escape, quoted; |
293 | 292 |
294 /* skip over any comments or leading whitespace */ | 293 /* skip over any comments or leading whitespace */ |
295 while (line->pos < line->len) { | 294 while (line->pos < line->len) { |
296 const char c = buf_getbyte(line); | 295 const char c = buf_getbyte(line); |
297 if (c == ' ' || c == '\t') { | 296 if (c == ' ' || c == '\t') { |
298 continue; | 297 continue; |
311 /* remember start of options */ | 310 /* remember start of options */ |
312 options_start = buf_getptr(line, 1); | 311 options_start = buf_getptr(line, 1); |
313 quoted = 0; | 312 quoted = 0; |
314 escape = 0; | 313 escape = 0; |
315 options_len = 0; | 314 options_len = 0; |
316 | 315 |
317 /* figure out where the options are */ | 316 /* figure out where the options are */ |
318 while (line->pos < line->len) { | 317 while (line->pos < line->len) { |
319 const char c = buf_getbyte(line); | 318 const char c = buf_getbyte(line); |
320 if (!quoted && (c == ' ' || c == '\t')) { | 319 if (!quoted && (c == ' ' || c == '\t')) { |
321 break; | 320 break; |
336 if (strncmp((const char *) buf_getptr(line, algolen), algo, algolen) != 0) { | 335 if (strncmp((const char *) buf_getptr(line, algolen), algo, algolen) != 0) { |
337 goto out; | 336 goto out; |
338 } | 337 } |
339 } | 338 } |
340 buf_incrpos(line, algolen); | 339 buf_incrpos(line, algolen); |
341 | 340 |
342 /* check for space (' ') character */ | 341 /* check for space (' ') character */ |
343 if (buf_getbyte(line) != ' ') { | 342 if (buf_getbyte(line) != ' ') { |
344 TRACE(("checkpubkey_line: space character expected, isn't there")) | 343 TRACE(("checkpubkey_line: space character expected, isn't there")) |
345 goto out; | 344 goto out; |
346 } | 345 } |
425 uid_t origuid; | 424 uid_t origuid; |
426 gid_t origgid; | 425 gid_t origgid; |
427 | 426 |
428 TRACE(("enter checkpubkey")) | 427 TRACE(("enter checkpubkey")) |
429 | 428 |
430 /* check file permissions, also whether file exists */ | |
431 if (checkpubkeyperms() == DROPBEAR_FAILURE) { | |
432 TRACE(("bad authorized_keys permissions, or file doesn't exist")) | |
433 goto out; | |
434 } | |
435 | |
436 /* we don't need to check pw and pw_dir for validity, since | |
437 * its been done in checkpubkeyperms. */ | |
438 len = strlen(ses.authstate.pw_dir); | |
439 /* allocate max required pathname storage, | |
440 * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */ | |
441 filename = m_malloc(len + 22); | |
442 snprintf(filename, len + 22, "%s/.ssh/authorized_keys", | |
443 ses.authstate.pw_dir); | |
444 | |
445 #if DROPBEAR_SVR_MULTIUSER | 429 #if DROPBEAR_SVR_MULTIUSER |
446 /* open the file as the authenticating user. */ | 430 /* access the file as the authenticating user. */ |
447 origuid = getuid(); | 431 origuid = getuid(); |
448 origgid = getgid(); | 432 origgid = getgid(); |
449 if ((setegid(ses.authstate.pw_gid)) < 0 || | 433 if ((setegid(ses.authstate.pw_gid)) < 0 || |
450 (seteuid(ses.authstate.pw_uid)) < 0) { | 434 (seteuid(ses.authstate.pw_uid)) < 0) { |
451 dropbear_exit("Failed to set euid"); | 435 dropbear_exit("Failed to set euid"); |
452 } | 436 } |
453 #endif | 437 #endif |
454 | 438 /* check file permissions, also whether file exists */ |
455 authfile = fopen(filename, "r"); | 439 if (checkpubkeyperms() == DROPBEAR_FAILURE) { |
456 | 440 TRACE(("bad authorized_keys permissions, or file doesn't exist")) |
441 } else { | |
442 /* we don't need to check pw and pw_dir for validity, since | |
443 * its been done in checkpubkeyperms. */ | |
444 len = strlen(ses.authstate.pw_dir); | |
445 /* allocate max required pathname storage, | |
446 * = path + "/.ssh/authorized_keys" + '\0' = pathlen + 22 */ | |
447 filename = m_malloc(len + 22); | |
448 snprintf(filename, len + 22, "%s/.ssh/authorized_keys", | |
449 ses.authstate.pw_dir); | |
450 | |
451 authfile = fopen(filename, "r"); | |
452 if (!authfile) { | |
453 TRACE(("checkpubkey: failed opening %s: %s", filename, strerror(errno))) | |
454 } | |
455 } | |
457 #if DROPBEAR_SVR_MULTIUSER | 456 #if DROPBEAR_SVR_MULTIUSER |
458 if ((seteuid(origuid)) < 0 || | 457 if ((seteuid(origuid)) < 0 || |
459 (setegid(origgid)) < 0) { | 458 (setegid(origgid)) < 0) { |
460 dropbear_exit("Failed to revert euid"); | 459 dropbear_exit("Failed to revert euid"); |
461 } | 460 } |
483 if (ret == DROPBEAR_SUCCESS) { | 482 if (ret == DROPBEAR_SUCCESS) { |
484 break; | 483 break; |
485 } | 484 } |
486 | 485 |
487 /* We continue to the next line otherwise */ | 486 /* We continue to the next line otherwise */ |
488 | |
489 } while (1); | 487 } while (1); |
490 | 488 |
491 out: | 489 out: |
492 if (authfile) { | 490 if (authfile) { |
493 fclose(authfile); | 491 fclose(authfile); |
506 * Checks that the user's homedir, ~/.ssh, and | 504 * Checks that the user's homedir, ~/.ssh, and |
507 * ~/.ssh/authorized_keys are all owned by either root or the user, and are | 505 * ~/.ssh/authorized_keys are all owned by either root or the user, and are |
508 * g-w, o-w */ | 506 * g-w, o-w */ |
509 static int checkpubkeyperms() { | 507 static int checkpubkeyperms() { |
510 | 508 |
511 char* filename = NULL; | 509 char* filename = NULL; |
512 int ret = DROPBEAR_FAILURE; | 510 int ret = DROPBEAR_FAILURE; |
513 unsigned int len; | 511 unsigned int len; |
514 | 512 |
515 TRACE(("enter checkpubkeyperms")) | 513 TRACE(("enter checkpubkeyperms")) |
516 | 514 |
545 goto out; | 543 goto out; |
546 } | 544 } |
547 | 545 |
548 /* file looks ok, return success */ | 546 /* file looks ok, return success */ |
549 ret = DROPBEAR_SUCCESS; | 547 ret = DROPBEAR_SUCCESS; |
550 | 548 |
551 out: | 549 out: |
552 m_free(filename); | 550 m_free(filename); |
553 | 551 |
554 TRACE(("leave checkpubkeyperms")) | 552 TRACE(("leave checkpubkeyperms")) |
555 return ret; | 553 return ret; |