cdist/cdist/conf/type/__sshd_config/files/update_sshd_config.awk

294 lines
6.5 KiB
Awk
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- mode: awk; indent-tabs-mode: t -*-
function usage() {
print_err("Usage: awk -f update_sshd_config.awk -- -o set|unset [-m 'User git'] -l 'X11Forwarding no' /etc/ssh/sshd_config")
}
function print_err(s) { print s | "cat >&2" }
function alength(a, i) {
for (i = 0; (i + 1) in a; ++i);
return i
}
function join(sep, a, i, s) {
for (i = i ? i : 1; i in a; i++)
s = s sep a[i]
return substr(s, 2)
}
function getopt(opts, argv, target, files, i, c, lv, idx, nf) {
# trivial getopt(3) implementation; only basic functionality
if (argv[1] == "--") i++
for (i += 1; i in argv; i++) {
if (lv) { target[c] = argv[i]; lv = 0; continue }
if (argv[i] ~ /^-/) {
c = substr(argv[i], 2, 1)
idx = index(opts, c)
if (!idx) {
print_err(sprintf("invalid option -%c\n", c))
continue
}
if (substr(opts, idx + 1, 1) == ":") {
# option takes argument
if (length(argv[i]) > 2)
target[c] = substr(argv[i], 3)
else
lv = 1
} else {
target[c] = 1
}
} else
files[++nf] = argv[i]
}
}
# tokenise configuration line
# this function mimics the counterpart in OpenSSH (misc.c)
# but it returns two (next token SUBSEP rest) because I didnt want to have to
# simulate any pointer magic.
function strdelim_internal(s, split_equals, old) {
if (!s)
return ""
old = s
if (!match(s, WHITESPACE "|" QUOTE "" (split_equals ? "|" EQUALS : "")))
return s
s = substr(s, RSTART)
old = substr(old, 1, RSTART - 1)
if (s ~ "^" QUOTE) {
old = substr(old, 2)
# Find matching quote
if (match(s, QUOTE)) {
old = substr(old, 1, RSTART)
# s = substr()
if (match(s, "^" WHITESPACE "*"))
s = substr(s, RLENGTH)
return old
} else {
# no matching quote
return ""
}
}
if (match(s, "^" WHITESPACE "+")) {
sub("^" WHITESPACE "+", "", s)
if (split_equals)
sub(EQUALS WHITESPACE "*", "", s)
} else if (s ~ "^" EQUALS) {
s = substr(s, 2)
}
return old SUBSEP s
}
function strdelim(s) { return strdelim_internal(s, 1) }
function strdelimw(s) { return strdelim_internal(s, 0) }
function singleton_option(opt) {
return tolower(opt) !~ /^(acceptenv|allowgroups|allowusers|authenticationmethods|authorizedkeysfile|denygroups|denyusers|hostcertificate|hostkey|listenaddress|logverbose|permitlisten|permitopen|port|setenv|subsystem)$/
}
function print_update() {
if (mode) {
if (match_only) printf "\t"
printf "%s\n", line_should
updated = 1
}
}
BEGIN {
FS = "\n" # disable field splitting
WHITESPACE = "[ \t]" # servconf.c, misc.c:strdelim_internal (without line breaks, cf. bugs)
QUOTE = "[\"]" # misc.c:strdelim_internal
EQUALS = "[=]"
split("", opts)
split("", files)
getopt("ho:l:m:", ARGV, opts, files)
if (opts["h"]) { usage(); exit (e="0") }
line_should = opts["l"]
match_only = opts["m"]
num_files = alength(files)
if (num_files != 1 || !opts["o"] || !line_should) {
usage()
exit (e=126)
}
if (opts["o"] == "set") {
mode = 1
} else if (opts["o"] == "unset") {
mode = 0
} else {
print_err(sprintf("invalid mode %s\n", mode))
exit (e=1)
}
if (mode) {
# loop over sshd_config twice!
ARGV[2] = ARGV[1] = files[1]
ARGC = 3
} else {
# only loop once
ARGV[1] = files[1]
ARGC = 2
}
split(strdelim(line_should), should, SUBSEP)
option_should = tolower(should[1])
value_should = should[2]
}
{
line = $0
# Strip trailing whitespace. Allow \f (form feed) at EOL only
sub("(" WHITESPACE "|\f)*$", "", line)
# Strip leading whitespace
sub("^" WHITESPACE "*", "", line)
if (match(line, "^#" WHITESPACE "*")) {
prefix = substr(line, RSTART, RLENGTH)
line = substr(line, RSTART + RLENGTH)
} else {
prefix = ""
}
line_type = "invalid"
option_is = value_is = ""
if (line) {
split(strdelim(line), toks, SUBSEP)
if (tolower(toks[1]) == "match") {
MATCH = (prefix ~ /^#/ ? "#" : "") join(" ", toks, 2)
line_type = "match"
} else if (toks[1] ~ /^[A-Za-z][A-Za-z0-9]+$/) {
# This could be an option line
line_type = "option"
option_is = tolower(toks[1])
value_is = toks[2]
}
} else {
line_type = "empty"
}
}
# mode: unset
!mode {
# delete matching config
if (prefix !~ /^#/)
if (MATCH == match_only && option_is == option_should)
if (!value_should || value_should == value_is)
next
print
next
}
# mode: set
mode && NR == FNR {
if (line_type == "option") {
if (MATCH !~ /^#/) {
if (prefix ~ /^#/) {
# comment line
last_occ[MATCH, "#" option_is] = FNR
} else {
# option line
last_occ[MATCH, option_is] = FNR
}
last_occ[MATCH] = FNR
}
} else if (line_type == "invalid" && !prefix) {
# INVALID LINE
print_err(sprintf("%s: syntax error on line %u\n", ARGV[0], FNR))
}
next
}
# before second pass prepare hashes containing location information to be used
# in the second pass.
mode && NR > FNR && FNR == 1 {
# First we drop the locations of commented-out options if a non-commented
# option is available. If a non-commented option is available, we will
# append new config options there to have them all at one place.
for (k in last_occ) {
if (k ~ /^#/) {
# delete entries of commented out match blocks
delete last_occ[k]
continue
}
split(k, parts, SUBSEP)
if (parts[2] ~ /^#/ && ((parts[1], substr(parts[2], 2)) in last_occ))
delete last_occ[k]
}
# Reverse the option => line mapping. The line_map allows for easier lookups
# in the second pass.
# We only keep options, not top-level keywords, because we can only have
# one entry per line and there are conflicts with last lines of "sections".
for (k in last_occ) {
if (!index(k, SUBSEP)) continue
line_map[last_occ[k]] = k
}
}
# Second pass
mode && line_map[FNR] == match_only SUBSEP option_should && !updated {
split(line_map[FNR], parts, SUBSEP)
# If option allows multiple values, print current value
if (!singleton_option(parts[2])) {
if (value_should != value_is)
print
}
print_update()
next
}
mode { print }
# Is a comment option
mode && line_map[FNR] == match_only SUBSEP "#" option_should && !updated {
print_update()
}
# Last line of the should match section
mode && last_occ[match_only] == FNR && !updated {
# NOTE: Inserting empty lines is only cosmetic. It is only done if
# different options are next to each other and not in a match block
# (match blocks are usually not in the default config and thus dont
# contain commented blocks.)
if (line && option_is != option_should && !MATCH)
print ""
print_update()
}
END {
if (e) exit e
if (mode && !updated) {
if (match_only && MATCH != match_only) {
printf "\nMatch %s\n", match_only
}
print_update()
}
}