diff options
author | Damien Miller <djm@mindrot.org> | 2013-10-15 12:13:05 +1100 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2013-10-15 12:13:05 +1100 |
commit | 194fd904d8597a274b93e075b2047afdf5a175d4 (patch) | |
tree | e8bd17b8455a41b3dc493b2b69933b8ef0cbfff7 /readconf.c | |
parent | 71df752de2a04f423b1cd18d961a79f4fbccbcee (diff) |
- djm@cvs.openbsd.org 2013/10/14 22:22:05
[readconf.c readconf.h ssh-keysign.c ssh.c ssh_config.5]
add a "Match" keyword to ssh_config that allows matching on hostname,
user and result of arbitrary commands. "nice work" markus@
Diffstat (limited to 'readconf.c')
-rw-r--r-- | readconf.c | 227 |
1 files changed, 215 insertions, 12 deletions
diff --git a/readconf.c b/readconf.c index 7450081cd..f7b912ef6 100644 --- a/readconf.c +++ b/readconf.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* $OpenBSD: readconf.c,v 1.205 2013/08/20 00:11:37 djm Exp $ */ | 1 | /* $OpenBSD: readconf.c,v 1.206 2013/10/14 22:22:02 djm Exp $ */ |
2 | /* | 2 | /* |
3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | 3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> |
4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland | 4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland |
@@ -17,6 +17,7 @@ | |||
17 | #include <sys/types.h> | 17 | #include <sys/types.h> |
18 | #include <sys/stat.h> | 18 | #include <sys/stat.h> |
19 | #include <sys/socket.h> | 19 | #include <sys/socket.h> |
20 | #include <sys/wait.h> | ||
20 | 21 | ||
21 | #include <netinet/in.h> | 22 | #include <netinet/in.h> |
22 | #include <netinet/in_systm.h> | 23 | #include <netinet/in_systm.h> |
@@ -24,7 +25,10 @@ | |||
24 | 25 | ||
25 | #include <ctype.h> | 26 | #include <ctype.h> |
26 | #include <errno.h> | 27 | #include <errno.h> |
28 | #include <fcntl.h> | ||
27 | #include <netdb.h> | 29 | #include <netdb.h> |
30 | #include <paths.h> | ||
31 | #include <pwd.h> | ||
28 | #include <signal.h> | 32 | #include <signal.h> |
29 | #include <stdarg.h> | 33 | #include <stdarg.h> |
30 | #include <stdio.h> | 34 | #include <stdio.h> |
@@ -47,6 +51,7 @@ | |||
47 | #include "buffer.h" | 51 | #include "buffer.h" |
48 | #include "kex.h" | 52 | #include "kex.h" |
49 | #include "mac.h" | 53 | #include "mac.h" |
54 | #include "uidswap.h" | ||
50 | 55 | ||
51 | /* Format of the configuration file: | 56 | /* Format of the configuration file: |
52 | 57 | ||
@@ -115,12 +120,13 @@ | |||
115 | 120 | ||
116 | typedef enum { | 121 | typedef enum { |
117 | oBadOption, | 122 | oBadOption, |
123 | oHost, oMatch, | ||
118 | oForwardAgent, oForwardX11, oForwardX11Trusted, oForwardX11Timeout, | 124 | oForwardAgent, oForwardX11, oForwardX11Trusted, oForwardX11Timeout, |
119 | oGatewayPorts, oExitOnForwardFailure, | 125 | oGatewayPorts, oExitOnForwardFailure, |
120 | oPasswordAuthentication, oRSAAuthentication, | 126 | oPasswordAuthentication, oRSAAuthentication, |
121 | oChallengeResponseAuthentication, oXAuthLocation, | 127 | oChallengeResponseAuthentication, oXAuthLocation, |
122 | oIdentityFile, oHostName, oPort, oCipher, oRemoteForward, oLocalForward, | 128 | oIdentityFile, oHostName, oPort, oCipher, oRemoteForward, oLocalForward, |
123 | oUser, oHost, oEscapeChar, oRhostsRSAAuthentication, oProxyCommand, | 129 | oUser, oEscapeChar, oRhostsRSAAuthentication, oProxyCommand, |
124 | oGlobalKnownHostsFile, oUserKnownHostsFile, oConnectionAttempts, | 130 | oGlobalKnownHostsFile, oUserKnownHostsFile, oConnectionAttempts, |
125 | oBatchMode, oCheckHostIP, oStrictHostKeyChecking, oCompression, | 131 | oBatchMode, oCheckHostIP, oStrictHostKeyChecking, oCompression, |
126 | oCompressionLevel, oTCPKeepAlive, oNumberOfPasswordPrompts, | 132 | oCompressionLevel, oTCPKeepAlive, oNumberOfPasswordPrompts, |
@@ -194,6 +200,7 @@ static struct { | |||
194 | { "localforward", oLocalForward }, | 200 | { "localforward", oLocalForward }, |
195 | { "user", oUser }, | 201 | { "user", oUser }, |
196 | { "host", oHost }, | 202 | { "host", oHost }, |
203 | { "match", oMatch }, | ||
197 | { "escapechar", oEscapeChar }, | 204 | { "escapechar", oEscapeChar }, |
198 | { "globalknownhostsfile", oGlobalKnownHostsFile }, | 205 | { "globalknownhostsfile", oGlobalKnownHostsFile }, |
199 | { "globalknownhostsfile2", oDeprecated }, | 206 | { "globalknownhostsfile2", oDeprecated }, |
@@ -349,10 +356,188 @@ add_identity_file(Options *options, const char *dir, const char *filename, | |||
349 | options->identity_files[options->num_identity_files++] = path; | 356 | options->identity_files[options->num_identity_files++] = path; |
350 | } | 357 | } |
351 | 358 | ||
359 | int | ||
360 | default_ssh_port(void) | ||
361 | { | ||
362 | static int port; | ||
363 | struct servent *sp; | ||
364 | |||
365 | if (port == 0) { | ||
366 | sp = getservbyname(SSH_SERVICE_NAME, "tcp"); | ||
367 | port = sp ? ntohs(sp->s_port) : SSH_DEFAULT_PORT; | ||
368 | } | ||
369 | return port; | ||
370 | } | ||
371 | |||
352 | /* | 372 | /* |
353 | * Returns the number of the token pointed to by cp or oBadOption. | 373 | * Execute a command in a shell. |
374 | * Return its exit status or -1 on abnormal exit. | ||
354 | */ | 375 | */ |
376 | static int | ||
377 | execute_in_shell(const char *cmd) | ||
378 | { | ||
379 | char *shell, *command_string; | ||
380 | pid_t pid; | ||
381 | int devnull, status; | ||
382 | extern uid_t original_real_uid; | ||
355 | 383 | ||
384 | if ((shell = getenv("SHELL")) == NULL) | ||
385 | shell = _PATH_BSHELL; | ||
386 | |||
387 | /* | ||
388 | * Use "exec" to avoid "sh -c" processes on some platforms | ||
389 | * (e.g. Solaris) | ||
390 | */ | ||
391 | xasprintf(&command_string, "exec %s", cmd); | ||
392 | |||
393 | /* Need this to redirect subprocess stdin/out */ | ||
394 | if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) | ||
395 | fatal("open(/dev/null): %s", strerror(errno)); | ||
396 | |||
397 | debug("Executing command: '%.500s'", cmd); | ||
398 | |||
399 | /* Fork and execute the command. */ | ||
400 | if ((pid = fork()) == 0) { | ||
401 | char *argv[4]; | ||
402 | |||
403 | /* Child. Permanently give up superuser privileges. */ | ||
404 | permanently_drop_suid(original_real_uid); | ||
405 | |||
406 | /* Redirect child stdin and stdout. Leave stderr */ | ||
407 | if (dup2(devnull, STDIN_FILENO) == -1) | ||
408 | fatal("dup2: %s", strerror(errno)); | ||
409 | if (dup2(devnull, STDOUT_FILENO) == -1) | ||
410 | fatal("dup2: %s", strerror(errno)); | ||
411 | if (devnull > STDERR_FILENO) | ||
412 | close(devnull); | ||
413 | closefrom(STDERR_FILENO + 1); | ||
414 | |||
415 | argv[0] = shell; | ||
416 | argv[1] = "-c"; | ||
417 | argv[2] = command_string; | ||
418 | argv[3] = NULL; | ||
419 | |||
420 | execv(argv[0], argv); | ||
421 | error("Unable to execute '%.100s': %s", cmd, strerror(errno)); | ||
422 | /* Die with signal to make this error apparent to parent. */ | ||
423 | signal(SIGTERM, SIG_DFL); | ||
424 | kill(getpid(), SIGTERM); | ||
425 | _exit(1); | ||
426 | } | ||
427 | /* Parent. */ | ||
428 | if (pid < 0) | ||
429 | fatal("%s: fork: %.100s", __func__, strerror(errno)); | ||
430 | |||
431 | close(devnull); | ||
432 | free(command_string); | ||
433 | |||
434 | while (waitpid(pid, &status, 0) == -1) { | ||
435 | if (errno != EINTR && errno != EAGAIN) | ||
436 | fatal("%s: waitpid: %s", __func__, strerror(errno)); | ||
437 | } | ||
438 | if (!WIFEXITED(status)) { | ||
439 | error("command '%.100s' exited abnormally", cmd); | ||
440 | return -1; | ||
441 | } | ||
442 | debug3("command returned status %d", WEXITSTATUS(status)); | ||
443 | return WEXITSTATUS(status); | ||
444 | } | ||
445 | |||
446 | /* | ||
447 | * Parse and execute a Match directive. | ||
448 | */ | ||
449 | static int | ||
450 | match_cfg_line(Options *options, char **condition, struct passwd *pw, | ||
451 | const char *host_arg, const char *filename, int linenum) | ||
452 | { | ||
453 | char *arg, *attrib, *cmd, *cp = *condition; | ||
454 | const char *ruser, *host; | ||
455 | int r, port, result = 1; | ||
456 | size_t len; | ||
457 | char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV]; | ||
458 | |||
459 | /* | ||
460 | * Configuration is likely to be incomplete at this point so we | ||
461 | * must be prepared to use default values. | ||
462 | */ | ||
463 | port = options->port <= 0 ? default_ssh_port() : options->port; | ||
464 | ruser = options->user == NULL ? pw->pw_name : options->user; | ||
465 | host = options->hostname == NULL ? host_arg : options->hostname; | ||
466 | |||
467 | debug3("checking match for '%s' host %s", cp, host); | ||
468 | while ((attrib = strdelim(&cp)) && *attrib != '\0') { | ||
469 | if ((arg = strdelim(&cp)) == NULL || *arg == '\0') { | ||
470 | error("Missing Match criteria for %s", attrib); | ||
471 | return -1; | ||
472 | } | ||
473 | len = strlen(arg); | ||
474 | if (strcasecmp(attrib, "host") == 0) { | ||
475 | if (match_hostname(host, arg, len) != 1) | ||
476 | result = 0; | ||
477 | else | ||
478 | debug("%.200s line %d: matched 'Host %.100s' ", | ||
479 | filename, linenum, host); | ||
480 | } else if (strcasecmp(attrib, "originalhost") == 0) { | ||
481 | if (match_hostname(host_arg, arg, len) != 1) | ||
482 | result = 0; | ||
483 | else | ||
484 | debug("%.200s line %d: matched " | ||
485 | "'OriginalHost %.100s' ", | ||
486 | filename, linenum, host_arg); | ||
487 | } else if (strcasecmp(attrib, "user") == 0) { | ||
488 | if (match_pattern_list(ruser, arg, len, 0) != 1) | ||
489 | result = 0; | ||
490 | else | ||
491 | debug("%.200s line %d: matched 'User %.100s' ", | ||
492 | filename, linenum, ruser); | ||
493 | } else if (strcasecmp(attrib, "localuser") == 0) { | ||
494 | if (match_pattern_list(pw->pw_name, arg, len, 0) != 1) | ||
495 | result = 0; | ||
496 | else | ||
497 | debug("%.200s line %d: matched " | ||
498 | "'LocalUser %.100s' ", | ||
499 | filename, linenum, pw->pw_name); | ||
500 | } else if (strcasecmp(attrib, "command") == 0) { | ||
501 | if (gethostname(thishost, sizeof(thishost)) == -1) | ||
502 | fatal("gethostname: %s", strerror(errno)); | ||
503 | strlcpy(shorthost, thishost, sizeof(shorthost)); | ||
504 | shorthost[strcspn(thishost, ".")] = '\0'; | ||
505 | snprintf(portstr, sizeof(portstr), "%d", port); | ||
506 | |||
507 | cmd = percent_expand(arg, | ||
508 | "L", shorthost, | ||
509 | "d", pw->pw_dir, | ||
510 | "h", host, | ||
511 | "l", thishost, | ||
512 | "n", host_arg, | ||
513 | "p", portstr, | ||
514 | "r", ruser, | ||
515 | "u", pw->pw_name, | ||
516 | (char *)NULL); | ||
517 | r = execute_in_shell(cmd); | ||
518 | if (r == -1) { | ||
519 | fatal("%.200s line %d: match command '%.100s' " | ||
520 | "error", filename, linenum, cmd); | ||
521 | } else if (r == 0) { | ||
522 | debug("%.200s line %d: matched " | ||
523 | "'Command \"%.100s\"' ", | ||
524 | filename, linenum, cmd); | ||
525 | } else | ||
526 | result = 0; | ||
527 | free(cmd); | ||
528 | } else { | ||
529 | error("Unsupported Match attribute %s", attrib); | ||
530 | return -1; | ||
531 | } | ||
532 | } | ||
533 | debug3("match %sfound", result ? "" : "not "); | ||
534 | *condition = cp; | ||
535 | return result; | ||
536 | } | ||
537 | |||
538 | /* | ||
539 | * Returns the number of the token pointed to by cp or oBadOption. | ||
540 | */ | ||
356 | static OpCodes | 541 | static OpCodes |
357 | parse_token(const char *cp, const char *filename, int linenum, | 542 | parse_token(const char *cp, const char *filename, int linenum, |
358 | const char *ignored_unknown) | 543 | const char *ignored_unknown) |
@@ -375,21 +560,24 @@ parse_token(const char *cp, const char *filename, int linenum, | |||
375 | * only sets those values that have not already been set. | 560 | * only sets those values that have not already been set. |
376 | */ | 561 | */ |
377 | #define WHITESPACE " \t\r\n" | 562 | #define WHITESPACE " \t\r\n" |
378 | |||
379 | int | 563 | int |
380 | process_config_line(Options *options, const char *host, | 564 | process_config_line(Options *options, struct passwd *pw, const char *host, |
381 | char *line, const char *filename, int linenum, | 565 | char *line, const char *filename, int linenum, int *activep, int userconfig) |
382 | int *activep, int userconfig) | ||
383 | { | 566 | { |
384 | char *s, **charptr, *endofnumber, *keyword, *arg, *arg2; | 567 | char *s, **charptr, *endofnumber, *keyword, *arg, *arg2; |
385 | char **cpptr, fwdarg[256]; | 568 | char **cpptr, fwdarg[256]; |
386 | u_int i, *uintptr, max_entries = 0; | 569 | u_int i, *uintptr, max_entries = 0; |
387 | int negated, opcode, *intptr, value, value2; | 570 | int negated, opcode, *intptr, value, value2, cmdline = 0; |
388 | LogLevel *log_level_ptr; | 571 | LogLevel *log_level_ptr; |
389 | long long val64; | 572 | long long val64; |
390 | size_t len; | 573 | size_t len; |
391 | Forward fwd; | 574 | Forward fwd; |
392 | 575 | ||
576 | if (activep == NULL) { /* We are processing a command line directive */ | ||
577 | cmdline = 1; | ||
578 | activep = &cmdline; | ||
579 | } | ||
580 | |||
393 | /* Strip trailing whitespace */ | 581 | /* Strip trailing whitespace */ |
394 | for (len = strlen(line) - 1; len > 0; len--) { | 582 | for (len = strlen(line) - 1; len > 0; len--) { |
395 | if (strchr(WHITESPACE, line[len]) == NULL) | 583 | if (strchr(WHITESPACE, line[len]) == NULL) |
@@ -828,6 +1016,9 @@ parse_int: | |||
828 | goto parse_flag; | 1016 | goto parse_flag; |
829 | 1017 | ||
830 | case oHost: | 1018 | case oHost: |
1019 | if (cmdline) | ||
1020 | fatal("Host directive not supported as a command-line " | ||
1021 | "option"); | ||
831 | *activep = 0; | 1022 | *activep = 0; |
832 | arg2 = NULL; | 1023 | arg2 = NULL; |
833 | while ((arg = strdelim(&s)) != NULL && *arg != '\0') { | 1024 | while ((arg = strdelim(&s)) != NULL && *arg != '\0') { |
@@ -854,6 +1045,18 @@ parse_int: | |||
854 | /* Avoid garbage check below, as strdelim is done. */ | 1045 | /* Avoid garbage check below, as strdelim is done. */ |
855 | return 0; | 1046 | return 0; |
856 | 1047 | ||
1048 | case oMatch: | ||
1049 | if (cmdline) | ||
1050 | fatal("Host directive not supported as a command-line " | ||
1051 | "option"); | ||
1052 | value = match_cfg_line(options, &s, pw, host, | ||
1053 | filename, linenum); | ||
1054 | if (value < 0) | ||
1055 | fatal("%.200s line %d: Bad Match condition", filename, | ||
1056 | linenum); | ||
1057 | *activep = value; | ||
1058 | break; | ||
1059 | |||
857 | case oEscapeChar: | 1060 | case oEscapeChar: |
858 | intptr = &options->escape_char; | 1061 | intptr = &options->escape_char; |
859 | arg = strdelim(&s); | 1062 | arg = strdelim(&s); |
@@ -1107,8 +1310,8 @@ parse_int: | |||
1107 | */ | 1310 | */ |
1108 | 1311 | ||
1109 | int | 1312 | int |
1110 | read_config_file(const char *filename, const char *host, Options *options, | 1313 | read_config_file(const char *filename, struct passwd *pw, const char *host, |
1111 | int flags) | 1314 | Options *options, int flags) |
1112 | { | 1315 | { |
1113 | FILE *f; | 1316 | FILE *f; |
1114 | char line[1024]; | 1317 | char line[1024]; |
@@ -1139,8 +1342,8 @@ read_config_file(const char *filename, const char *host, Options *options, | |||
1139 | while (fgets(line, sizeof(line), f)) { | 1342 | while (fgets(line, sizeof(line), f)) { |
1140 | /* Update line number counter. */ | 1343 | /* Update line number counter. */ |
1141 | linenum++; | 1344 | linenum++; |
1142 | if (process_config_line(options, host, line, filename, linenum, | 1345 | if (process_config_line(options, pw, host, line, filename, |
1143 | &active, flags & SSHCONF_USERCONF) != 0) | 1346 | linenum, &active, flags & SSHCONF_USERCONF) != 0) |
1144 | bad_options++; | 1347 | bad_options++; |
1145 | } | 1348 | } |
1146 | fclose(f); | 1349 | fclose(f); |