diff --git a/connection-specific-ssh-config b/connection-specific-ssh-config new file mode 100644 index 0000000..c4ba1fa --- /dev/null +++ b/connection-specific-ssh-config @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 + + +# +import configparser +import json +import logging +import os +import re +import subprocess +import sys +import syslog + + +# +class SSHConfiger: + + # + _action_interface = None + _action_command = None + # + _config_file_path = None + _targets = None + _config = None + + # + def __init__(self, action_interface, action_command, config_file_path): + + # Grab args + self._action_interface = action_interface + self._action_command = action_command + self._config_file_path = config_file_path + + # + def log(self, s): + + # + print ("[SSHConfiger]", s) + syslog.syslog("[SSHConfiger] " + s) + + # + def complain(self, s): + + # + syslog.syslog(syslog.LOG_ERR, "[SSHConfiger] " + s) + print("[SSHConfiger]", s, file=sys.stderr) + + # + def die(self, s): + + # + self.complain(s) + raise Exception(s) + + # + def run(self): + + # + self.log("Running for interface \""+ self._action_interface +"\": " + self._action_command) + + # Parse the config + self.parse_config() + + # Grab the specified ssh directory + if ( "ssh_dir" not in self._config.keys() ): + self.die("Config file needs key \"ssh_dir\" inside \"config\" section") + ssh_dir = self._config["ssh_dir"] + + # Determine which ssh config file we should use + ssh_config_name = self.determine_ssh_config_name() + + # Make paths + ssh_config_path_link = os.path.join(ssh_dir, "config") + ssh_config_path_target = os.path.join(ssh_dir, ssh_config_name) + self.log("Selecting source config file \"" + ssh_config_path_target + "\" for link \"" + ssh_config_path_link + "\"") + + # Don't run unless current ssh config is a symlink or not a file (for safety) + self.require_symlink_or_none(ssh_config_path_link) + + # Set the symlink + try: + os.unlink(ssh_config_path_link) + except: + pass + os.symlink(ssh_config_path_target, ssh_config_path_link) + + # + self.log("Finished") + + # + def require_symlink_or_none(self, file_path): + + # + if ( not os.path.isfile(file_path) or os.path.islink(file_path) ): + return True + + # + self.die("For safety, we cannot continue if the target link exists and is a file (" + file_path + ")") + + + # + def determine_ssh_config_name(self): + + # Only run if an interface is coming up + if ( self._action_command != "up" ): + return None + + # Check each section + found_ssh_config_name = None + for section_name in self._targets: + + # + section = self._targets[section_name] + + # Must match at least one interface + if ( self._action_interface not in section["adapters"] ): + continue + + # + interface_ssid = self.get_interface_ssid(self._action_interface) + + # Must also match at least one SSID + if ( interface_ssid not in section["ssids"] ): + continue + + # Found a match! + found_ssh_config_name = section["ssh_config_name"] + + # Didn't find anything? Go default ... + if (found_ssh_config_name == None): + if ( "default" in self._targets.keys() ): + if ( "ssh_config_name" in self._targets["default"].keys() ): + found_ssh_config_name = self._targets["default"]["ssh_config_name"] + self.log("No matches found; Defaulting to:" + found_ssh_config_name) + + # + return found_ssh_config_name + + # + def get_interface_ssid(self, interface): + + # + p = subprocess.Popen(["nmcli", "dev", "show", interface], stdout=subprocess.PIPE) + (stdout, stderr) = p.communicate() + stdout = stdout.decode() + + # + r = re.compile("""^GENERAL.CONNECTION:\s+(?P[^\s].+)$""", re.MULTILINE) + match = r.search(stdout) + + return match.group("ssid") + + # + def parse_config(self): + + # + self.log("Parsing config: " + self._config_file_path) + parser = configparser.ConfigParser() + parser.read( self._config_file_path ) + + # Attempt to grab global config + if ( "config" not in parser.sections() ): + self.die("config section not found") + config = parser["config"] + + # Targets + targets = {} + for section in parser.sections(): + + # Skip the config section + if ( section == "config" ): + continue + + # + target = { + "adapters" : [], + "ssids" : [] + } + + # Adapters + if ( parser.has_option(section, "adapters") ): + target["adapters"] = json.loads( parser[section]["adapters"] ) + if ( parser.has_option(section, "adapter") ): + target["adapters"].append( parser[section]["adapter"] ) + + # SSIDs + if ( parser.has_option(section, "ssids") ): + target["ssids"] = json.loads( parser[section]["ssids"] ) + if ( parser.has_option(section, "ssid") ): + target["ssids"].append( parser[section]["ssid"] ) + + # ssh_config_name + if ( parser.has_option(section, "ssh_config_name") ): + target["ssh_config_name"] = parser[section]["ssh_config_name"] + else: + raise Exception("ssh_config_name key missing from section: " + section) + + # + targets[section] = target + + # + self._config = config + self._targets = targets + + return True + + +# Main Entry +if (__name__ == "__main__"): + + # Script name + script_name = sys.argv[0] + + # Network Manager action info + action_interface = sys.argv[1] + action_command = sys.argv[2] + + # Config file + config_file_path = sys.argv[3] + + # + configger = SSHConfiger(action_interface, action_command, config_file_path) + configger.run() + + + + + +