Annotation of src/usr.bin/ssh/auth2-pubkey.c, Revision 1.31
1.31 ! djm 1: /* $OpenBSD: auth2-pubkey.c,v 1.30 2011/09/25 05:44:47 djm Exp $ */
1.1 markus 2: /*
3: * Copyright (c) 2000 Markus Friedl. All rights reserved.
4: *
5: * Redistribution and use in source and binary forms, with or without
6: * modification, are permitted provided that the following conditions
7: * are met:
8: * 1. Redistributions of source code must retain the above copyright
9: * notice, this list of conditions and the following disclaimer.
10: * 2. Redistributions in binary form must reproduce the above copyright
11: * notice, this list of conditions and the following disclaimer in the
12: * documentation and/or other materials provided with the distribution.
13: *
14: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17: * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23: * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24: */
25:
1.10 stevesk 26:
27: #include <sys/types.h>
28: #include <sys/stat.h>
1.31 ! djm 29: #include <sys/wait.h>
1.13 stevesk 30:
1.31 ! djm 31: #include <errno.h>
1.16 djm 32: #include <fcntl.h>
1.31 ! djm 33: #include <paths.h>
1.13 stevesk 34: #include <pwd.h>
1.31 ! djm 35: #include <signal.h>
1.14 stevesk 36: #include <stdio.h>
1.15 deraadt 37: #include <stdarg.h>
1.20 djm 38: #include <string.h>
39: #include <time.h>
1.17 dtucker 40: #include <unistd.h>
1.1 markus 41:
1.15 deraadt 42: #include "xmalloc.h"
1.8 dtucker 43: #include "ssh.h"
1.1 markus 44: #include "ssh2.h"
45: #include "packet.h"
46: #include "buffer.h"
47: #include "log.h"
48: #include "servconf.h"
49: #include "compat.h"
1.15 deraadt 50: #include "key.h"
51: #include "hostfile.h"
1.1 markus 52: #include "auth.h"
53: #include "pathnames.h"
54: #include "uidswap.h"
55: #include "auth-options.h"
56: #include "canohost.h"
1.15 deraadt 57: #ifdef GSSAPI
58: #include "ssh-gss.h"
59: #endif
1.1 markus 60: #include "monitor_wrap.h"
1.9 dtucker 61: #include "misc.h"
1.21 djm 62: #include "authfile.h"
1.24 djm 63: #include "match.h"
1.1 markus 64:
65: /* import */
66: extern ServerOptions options;
67: extern u_char *session_id2;
1.4 markus 68: extern u_int session_id2_len;
1.1 markus 69:
1.2 markus 70: static int
1.1 markus 71: userauth_pubkey(Authctxt *authctxt)
72: {
73: Buffer b;
74: Key *key = NULL;
75: char *pkalg;
76: u_char *pkblob, *sig;
77: u_int alen, blen, slen;
78: int have_sig, pktype;
79: int authenticated = 0;
80:
81: if (!authctxt->valid) {
82: debug2("userauth_pubkey: disabled because of invalid user");
83: return 0;
84: }
85: have_sig = packet_get_char();
86: if (datafellows & SSH_BUG_PKAUTH) {
87: debug2("userauth_pubkey: SSH_BUG_PKAUTH");
88: /* no explicit pkalg given */
89: pkblob = packet_get_string(&blen);
90: buffer_init(&b);
91: buffer_append(&b, pkblob, blen);
92: /* so we have to extract the pkalg from the pkblob */
93: pkalg = buffer_get_string(&b, &alen);
94: buffer_free(&b);
95: } else {
96: pkalg = packet_get_string(&alen);
97: pkblob = packet_get_string(&blen);
98: }
99: pktype = key_type_from_name(pkalg);
100: if (pktype == KEY_UNSPEC) {
101: /* this is perfectly legal */
1.3 itojun 102: logit("userauth_pubkey: unsupported public key algorithm: %s",
1.1 markus 103: pkalg);
104: goto done;
105: }
106: key = key_from_blob(pkblob, blen);
107: if (key == NULL) {
108: error("userauth_pubkey: cannot decode key: %s", pkalg);
109: goto done;
110: }
111: if (key->type != pktype) {
112: error("userauth_pubkey: type mismatch for decoded key "
113: "(received %d, expected %d)", key->type, pktype);
114: goto done;
115: }
116: if (have_sig) {
117: sig = packet_get_string(&slen);
118: packet_check_eom();
119: buffer_init(&b);
120: if (datafellows & SSH_OLD_SESSIONID) {
121: buffer_append(&b, session_id2, session_id2_len);
122: } else {
123: buffer_put_string(&b, session_id2, session_id2_len);
124: }
125: /* reconstruct packet */
126: buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST);
127: buffer_put_cstring(&b, authctxt->user);
128: buffer_put_cstring(&b,
129: datafellows & SSH_BUG_PKSERVICE ?
130: "ssh-userauth" :
131: authctxt->service);
132: if (datafellows & SSH_BUG_PKAUTH) {
133: buffer_put_char(&b, have_sig);
134: } else {
135: buffer_put_cstring(&b, "publickey");
136: buffer_put_char(&b, have_sig);
137: buffer_put_cstring(&b, pkalg);
138: }
139: buffer_put_string(&b, pkblob, blen);
140: #ifdef DEBUG_PK
141: buffer_dump(&b);
142: #endif
143: /* test for correct signature */
144: authenticated = 0;
145: if (PRIVSEP(user_key_allowed(authctxt->pw, key)) &&
146: PRIVSEP(key_verify(key, sig, slen, buffer_ptr(&b),
1.6 markus 147: buffer_len(&b))) == 1)
1.1 markus 148: authenticated = 1;
1.6 markus 149: buffer_free(&b);
1.1 markus 150: xfree(sig);
151: } else {
152: debug("test whether pkalg/pkblob are acceptable");
153: packet_check_eom();
154:
155: /* XXX fake reply and always send PK_OK ? */
156: /*
157: * XXX this allows testing whether a user is allowed
158: * to login: if you happen to have a valid pubkey this
159: * message is sent. the message is NEVER sent at all
160: * if a user is not allowed to login. is this an
161: * issue? -markus
162: */
163: if (PRIVSEP(user_key_allowed(authctxt->pw, key))) {
164: packet_start(SSH2_MSG_USERAUTH_PK_OK);
165: packet_put_string(pkalg, alen);
166: packet_put_string(pkblob, blen);
167: packet_send();
168: packet_write_wait();
169: authctxt->postponed = 1;
170: }
171: }
172: if (authenticated != 1)
173: auth_clear_options();
174: done:
175: debug2("userauth_pubkey: authenticated %d pkalg %s", authenticated, pkalg);
176: if (key != NULL)
177: key_free(key);
178: xfree(pkalg);
179: xfree(pkblob);
180: return authenticated;
181: }
182:
1.24 djm 183: static int
184: match_principals_option(const char *principal_list, struct KeyCert *cert)
185: {
186: char *result;
187: u_int i;
188:
189: /* XXX percent_expand() sequences for authorized_principals? */
190:
191: for (i = 0; i < cert->nprincipals; i++) {
192: if ((result = match_list(cert->principals[i],
193: principal_list, NULL)) != NULL) {
194: debug3("matched principal from key options \"%.100s\"",
195: result);
196: xfree(result);
197: return 1;
198: }
199: }
200: return 0;
201: }
202:
203: static int
1.26 djm 204: match_principals_file(char *file, struct passwd *pw, struct KeyCert *cert)
1.24 djm 205: {
206: FILE *f;
1.26 djm 207: char line[SSH_MAX_PUBKEY_BYTES], *cp, *ep, *line_opts;
1.24 djm 208: u_long linenum = 0;
209: u_int i;
210:
211: temporarily_use_uid(pw);
212: debug("trying authorized principals file %s", file);
213: if ((f = auth_openprincipals(file, pw, options.strict_modes)) == NULL) {
214: restore_uid();
215: return 0;
216: }
217: while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) {
1.26 djm 218: /* Skip leading whitespace. */
1.24 djm 219: for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
220: ;
1.26 djm 221: /* Skip blank and comment lines. */
222: if ((ep = strchr(cp, '#')) != NULL)
223: *ep = '\0';
224: if (!*cp || *cp == '\n')
1.24 djm 225: continue;
1.26 djm 226: /* Trim trailing whitespace. */
227: ep = cp + strlen(cp) - 1;
228: while (ep > cp && (*ep == '\n' || *ep == ' ' || *ep == '\t'))
229: *ep-- = '\0';
230: /*
231: * If the line has internal whitespace then assume it has
232: * key options.
233: */
234: line_opts = NULL;
235: if ((ep = strrchr(cp, ' ')) != NULL ||
236: (ep = strrchr(cp, '\t')) != NULL) {
237: for (; *ep == ' ' || *ep == '\t'; ep++)
1.27 deraadt 238: ;
1.26 djm 239: line_opts = cp;
240: cp = ep;
241: }
1.24 djm 242: for (i = 0; i < cert->nprincipals; i++) {
243: if (strcmp(cp, cert->principals[i]) == 0) {
1.30 djm 244: debug3("matched principal \"%.100s\" "
245: "from file \"%s\" on line %lu",
1.31 ! djm 246: cert->principals[i], file, linenum);
1.26 djm 247: if (auth_parse_options(pw, line_opts,
248: file, linenum) != 1)
249: continue;
1.24 djm 250: fclose(f);
251: restore_uid();
252: return 1;
253: }
254: }
255: }
256: fclose(f);
257: restore_uid();
258: return 0;
1.31 ! djm 259: }
1.24 djm 260:
1.31 ! djm 261: /*
! 262: * Checks whether key is allowed in authorized_keys-format file,
! 263: * returns 1 if the key is allowed or 0 otherwise.
! 264: */
1.1 markus 265: static int
1.31 ! djm 266: check_authkeys_file(FILE *f, char *file, Key* key, struct passwd *pw)
1.1 markus 267: {
1.8 dtucker 268: char line[SSH_MAX_PUBKEY_BYTES];
1.20 djm 269: const char *reason;
1.18 dtucker 270: int found_key = 0;
1.1 markus 271: u_long linenum = 0;
272: Key *found;
273: char *fp;
274:
275: found_key = 0;
1.20 djm 276: found = key_new(key_is_cert(key) ? KEY_UNSPEC : key->type);
1.1 markus 277:
1.8 dtucker 278: while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) {
1.7 avsm 279: char *cp, *key_options = NULL;
1.8 dtucker 280:
1.20 djm 281: auth_clear_options();
282:
1.1 markus 283: /* Skip leading whitespace, empty and comment lines. */
284: for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
285: ;
286: if (!*cp || *cp == '\n' || *cp == '#')
287: continue;
288:
289: if (key_read(found, &cp) != 1) {
290: /* no key? check if there are options for this key */
291: int quoted = 0;
292: debug2("user_key_allowed: check options: '%s'", cp);
1.7 avsm 293: key_options = cp;
1.1 markus 294: for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) {
295: if (*cp == '\\' && cp[1] == '"')
296: cp++; /* Skip both */
297: else if (*cp == '"')
298: quoted = !quoted;
299: }
300: /* Skip remaining whitespace. */
301: for (; *cp == ' ' || *cp == '\t'; cp++)
302: ;
303: if (key_read(found, &cp) != 1) {
304: debug2("user_key_allowed: advance: '%s'", cp);
305: /* still no key? advance to next line*/
306: continue;
307: }
308: }
1.23 djm 309: if (key_is_cert(key)) {
1.25 djm 310: if (!key_equal(found, key->cert->signature_key))
311: continue;
312: if (auth_parse_options(pw, key_options, file,
313: linenum) != 1)
314: continue;
1.20 djm 315: if (!key_is_cert_authority)
316: continue;
317: fp = key_fingerprint(found, SSH_FP_MD5,
318: SSH_FP_HEX);
1.22 djm 319: debug("matching CA found: file %s, line %lu, %s %s",
320: file, linenum, key_type(found), fp);
1.24 djm 321: /*
322: * If the user has specified a list of principals as
323: * a key option, then prefer that list to matching
324: * their username in the certificate principals list.
325: */
326: if (authorized_principals != NULL &&
327: !match_principals_option(authorized_principals,
328: key->cert)) {
329: reason = "Certificate does not contain an "
330: "authorized principal";
331: fail_reason:
1.22 djm 332: xfree(fp);
1.20 djm 333: error("%s", reason);
334: auth_debug_add("%s", reason);
335: continue;
336: }
1.24 djm 337: if (key_cert_check_authority(key, 0, 0,
338: authorized_principals == NULL ? pw->pw_name : NULL,
339: &reason) != 0)
340: goto fail_reason;
1.23 djm 341: if (auth_cert_options(key, pw) != 0) {
1.22 djm 342: xfree(fp);
1.20 djm 343: continue;
1.22 djm 344: }
345: verbose("Accepted certificate ID \"%s\" "
346: "signed by %s CA %s via %s", key->cert->key_id,
347: key_type(found), fp, file);
348: xfree(fp);
1.20 djm 349: found_key = 1;
350: break;
1.25 djm 351: } else if (key_equal(found, key)) {
352: if (auth_parse_options(pw, key_options, file,
353: linenum) != 1)
354: continue;
355: if (key_is_cert_authority)
356: continue;
1.1 markus 357: found_key = 1;
358: debug("matching key found: file %s, line %lu",
359: file, linenum);
360: fp = key_fingerprint(found, SSH_FP_MD5, SSH_FP_HEX);
361: verbose("Found matching %s key: %s",
362: key_type(found), fp);
363: xfree(fp);
364: break;
365: }
366: }
367: key_free(found);
368: if (!found_key)
369: debug2("key not found");
370: return found_key;
371: }
372:
1.21 djm 373: /* Authenticate a certificate key against TrustedUserCAKeys */
374: static int
375: user_cert_trusted_ca(struct passwd *pw, Key *key)
376: {
1.24 djm 377: char *ca_fp, *principals_file = NULL;
1.21 djm 378: const char *reason;
379: int ret = 0;
380:
381: if (!key_is_cert(key) || options.trusted_user_ca_keys == NULL)
382: return 0;
383:
1.22 djm 384: ca_fp = key_fingerprint(key->cert->signature_key,
385: SSH_FP_MD5, SSH_FP_HEX);
1.21 djm 386:
387: if (key_in_file(key->cert->signature_key,
388: options.trusted_user_ca_keys, 1) != 1) {
389: debug2("%s: CA %s %s is not listed in %s", __func__,
390: key_type(key->cert->signature_key), ca_fp,
391: options.trusted_user_ca_keys);
392: goto out;
393: }
1.24 djm 394: /*
395: * If AuthorizedPrincipals is in use, then compare the certificate
396: * principals against the names in that file rather than matching
397: * against the username.
398: */
399: if ((principals_file = authorized_principals_file(pw)) != NULL) {
400: if (!match_principals_file(principals_file, pw, key->cert)) {
401: reason = "Certificate does not contain an "
402: "authorized principal";
403: fail_reason:
404: error("%s", reason);
405: auth_debug_add("%s", reason);
406: goto out;
407: }
1.21 djm 408: }
1.24 djm 409: if (key_cert_check_authority(key, 0, 1,
410: principals_file == NULL ? pw->pw_name : NULL, &reason) != 0)
411: goto fail_reason;
1.23 djm 412: if (auth_cert_options(key, pw) != 0)
1.21 djm 413: goto out;
414:
1.22 djm 415: verbose("Accepted certificate ID \"%s\" signed by %s CA %s via %s",
416: key->cert->key_id, key_type(key->cert->signature_key), ca_fp,
417: options.trusted_user_ca_keys);
1.21 djm 418: ret = 1;
419:
420: out:
1.24 djm 421: if (principals_file != NULL)
422: xfree(principals_file);
1.21 djm 423: if (ca_fp != NULL)
424: xfree(ca_fp);
425: return ret;
426: }
427:
1.31 ! djm 428: /*
! 429: * Checks whether key is allowed in file.
! 430: * returns 1 if the key is allowed or 0 otherwise.
! 431: */
! 432: static int
! 433: user_key_allowed2(struct passwd *pw, Key *key, char *file)
! 434: {
! 435: FILE *f;
! 436: int found_key = 0;
! 437:
! 438: /* Temporarily use the user's uid. */
! 439: temporarily_use_uid(pw);
! 440:
! 441: debug("trying public key file %s", file);
! 442: if ((f = auth_openkeyfile(file, pw, options.strict_modes)) != NULL) {
! 443: found_key = check_authkeys_file(f, file, key, pw);
! 444: fclose(f);
! 445: }
! 446:
! 447: restore_uid();
! 448: return found_key;
! 449: }
! 450:
! 451: /*
! 452: * Checks whether key is allowed in output of command.
! 453: * returns 1 if the key is allowed or 0 otherwise.
! 454: */
! 455: static int
! 456: user_key_command_allowed2(struct passwd *user_pw, Key *key)
! 457: {
! 458: FILE *f;
! 459: int ok, found_key = 0;
! 460: struct passwd *pw;
! 461: struct stat st;
! 462: int status, devnull, p[2], i;
! 463: pid_t pid;
! 464: char errmsg[512];
! 465:
! 466: if (options.authorized_keys_command == NULL ||
! 467: options.authorized_keys_command[0] != '/')
! 468: return 0;
! 469:
! 470: /* If no user specified to run commands the default to target user */
! 471: if (options.authorized_keys_command_user == NULL)
! 472: pw = user_pw;
! 473: else {
! 474: pw = getpwnam(options.authorized_keys_command_user);
! 475: if (pw == NULL) {
! 476: error("AuthorizedKeyCommandUser \"%s\" not found: %s",
! 477: options.authorized_keys_command, strerror(errno));
! 478: return 0;
! 479: }
! 480: }
! 481:
! 482: temporarily_use_uid(pw);
! 483:
! 484: if (stat(options.authorized_keys_command, &st) < 0) {
! 485: error("Could not stat AuthorizedKeysCommand \"%s\": %s",
! 486: options.authorized_keys_command, strerror(errno));
! 487: goto out;
! 488: }
! 489: if (auth_secure_path(options.authorized_keys_command, &st, NULL, 0,
! 490: errmsg, sizeof(errmsg)) != 0) {
! 491: error("Unsafe AuthorizedKeysCommand: %s", errmsg);
! 492: goto out;
! 493: }
! 494:
! 495: if (pipe(p) != 0) {
! 496: error("%s: pipe: %s", __func__, strerror(errno));
! 497: goto out;
! 498: }
! 499:
! 500: debug3("Running AuthorizedKeysCommand: \"%s\" as \"%s\"",
! 501: options.authorized_keys_command, pw->pw_name);
! 502:
! 503: /*
! 504: * Don't want to call this in the child, where it can fatal() and
! 505: * run cleanup_exit() code.
! 506: */
! 507: restore_uid();
! 508:
! 509: switch ((pid = fork())) {
! 510: case -1: /* error */
! 511: error("%s: fork: %s", __func__, strerror(errno));
! 512: close(p[0]);
! 513: close(p[1]);
! 514: return 0;
! 515: case 0: /* child */
! 516: for (i = 0; i < NSIG; i++)
! 517: signal(i, SIG_DFL);
! 518:
! 519: /* Don't use permanently_set_uid() here to avoid fatal() */
! 520: if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) {
! 521: error("setresgid %u: %s", (u_int)pw->pw_gid,
! 522: strerror(errno));
! 523: _exit(1);
! 524: }
! 525: if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) {
! 526: error("setresuid %u: %s", (u_int)pw->pw_uid,
! 527: strerror(errno));
! 528: _exit(1);
! 529: }
! 530:
! 531: close(p[0]);
! 532: if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) {
! 533: error("%s: open %s: %s", __func__, _PATH_DEVNULL,
! 534: strerror(errno));
! 535: _exit(1);
! 536: }
! 537: if (dup2(devnull, STDIN_FILENO) == -1 ||
! 538: dup2(p[1], STDOUT_FILENO) == -1 ||
! 539: dup2(devnull, STDERR_FILENO) == -1) {
! 540: error("%s: dup2: %s", __func__, strerror(errno));
! 541: _exit(1);
! 542: }
! 543: closefrom(STDERR_FILENO + 1);
! 544:
! 545: execl(options.authorized_keys_command,
! 546: options.authorized_keys_command, pw->pw_name, NULL);
! 547:
! 548: error("AuthorizedKeysCommand %s exec failed: %s",
! 549: options.authorized_keys_command, strerror(errno));
! 550: _exit(127);
! 551: default: /* parent */
! 552: break;
! 553: }
! 554:
! 555: temporarily_use_uid(pw);
! 556:
! 557: close(p[1]);
! 558: if ((f = fdopen(p[0], "r")) == NULL) {
! 559: error("%s: fdopen: %s", __func__, strerror(errno));
! 560: close(p[0]);
! 561: /* Don't leave zombie child */
! 562: kill(pid, SIGTERM);
! 563: while (waitpid(pid, NULL, 0) == -1 && errno == EINTR)
! 564: ;
! 565: goto out;
! 566: }
! 567: ok = check_authkeys_file(f, options.authorized_keys_command, key, pw);
! 568: fclose(f);
! 569:
! 570: while (waitpid(pid, &status, 0) == -1) {
! 571: if (errno != EINTR) {
! 572: error("%s: waitpid: %s", __func__, strerror(errno));
! 573: goto out;
! 574: }
! 575: }
! 576: if (WIFSIGNALED(status)) {
! 577: error("AuthorizedKeysCommand %s exited on signal %d",
! 578: options.authorized_keys_command, WTERMSIG(status));
! 579: goto out;
! 580: } else if (WEXITSTATUS(status) != 0) {
! 581: error("AuthorizedKeysCommand %s returned status %d",
! 582: options.authorized_keys_command, WEXITSTATUS(status));
! 583: goto out;
! 584: }
! 585: found_key = ok;
! 586: out:
! 587: restore_uid();
! 588: return found_key;
! 589: }
! 590:
! 591: /*
! 592: * Check whether key authenticates and authorises the user.
! 593: */
1.1 markus 594: int
595: user_key_allowed(struct passwd *pw, Key *key)
596: {
1.29 djm 597: u_int success, i;
1.1 markus 598: char *file;
1.21 djm 599:
600: if (auth_key_is_revoked(key))
601: return 0;
602: if (key_is_cert(key) && auth_key_is_revoked(key->cert->signature_key))
603: return 0;
604:
605: success = user_cert_trusted_ca(pw, key);
606: if (success)
607: return success;
1.1 markus 608:
1.31 ! djm 609: success = user_key_command_allowed2(pw, key);
! 610: if (success > 0)
! 611: return success;
! 612:
1.29 djm 613: for (i = 0; !success && i < options.num_authkeys_files; i++) {
1.31 ! djm 614:
! 615: if (strcasecmp(options.authorized_keys_files[i], "none") == 0)
! 616: continue;
1.29 djm 617: file = expand_authorized_keys(
618: options.authorized_keys_files[i], pw);
1.31 ! djm 619:
1.29 djm 620: success = user_key_allowed2(pw, key, file);
621: xfree(file);
622: }
1.1 markus 623:
624: return success;
625: }
1.2 markus 626:
627: Authmethod method_pubkey = {
628: "publickey",
629: userauth_pubkey,
630: &options.pubkey_authentication
631: };