summaryrefslogtreecommitdiff
path: root/readconf.c
diff options
context:
space:
mode:
authorDamien Miller <djm@mindrot.org>2013-10-15 12:13:05 +1100
committerDamien Miller <djm@mindrot.org>2013-10-15 12:13:05 +1100
commit194fd904d8597a274b93e075b2047afdf5a175d4 (patch)
treee8bd17b8455a41b3dc493b2b69933b8ef0cbfff7 /readconf.c
parent71df752de2a04f423b1cd18d961a79f4fbccbcee (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.c227
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
116typedef enum { 121typedef 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
359int
360default_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 */
376static int
377execute_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 */
449static int
450match_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 */
356static OpCodes 541static OpCodes
357parse_token(const char *cp, const char *filename, int linenum, 542parse_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
379int 563int
380process_config_line(Options *options, const char *host, 564process_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
1109int 1312int
1110read_config_file(const char *filename, const char *host, Options *options, 1313read_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);