src/Pure/General/ssh.scala
changeset 64130 e17c211a0bb6
parent 64129 fce8b7c746b4
child 64131 f01fca58e0a5
equal deleted inserted replaced
64129:fce8b7c746b4 64130:e17c211a0bb6
     6 
     6 
     7 package isabelle
     7 package isabelle
     8 
     8 
     9 
     9 
    10 import com.jcraft.jsch.{JSch, Logger => JSch_Logger, Session => JSch_Session,
    10 import com.jcraft.jsch.{JSch, Logger => JSch_Logger, Session => JSch_Session,
    11   OpenSSHConfig, UserInfo, ChannelExec, ChannelSftp}
    11   OpenSSHConfig, UserInfo, Channel => JSch_Channel, ChannelExec, ChannelSftp}
    12 
    12 
    13 
    13 
    14 object SSH
    14 object SSH
    15 {
    15 {
    16   /* init */
    16   /* init */
    17 
    17 
    18   def init(config_dir: Path = Path.explode("~/.ssh"),
    18   def init(options: Options): SSH =
    19     config_file: Path = Path.explode("~/.ssh/config"),
       
    20     identity_files: List[Path] =
       
    21       List("~/.ssh/id_dsa", "~/.ssh/id_ecdsa", "~/.ssh/id_rsa").map(Path.explode(_))): SSH =
       
    22   {
    19   {
       
    20     val config_dir = Path.explode(options.string("ssh_config_dir"))
    23     if (!config_dir.is_dir) error("Bad ssh config directory: " + config_dir)
    21     if (!config_dir.is_dir) error("Bad ssh config directory: " + config_dir)
    24 
    22 
    25     val jsch = new JSch
    23     val jsch = new JSch
    26 
    24 
       
    25     val config_file = Path.explode(options.string("ssh_config_file"))
    27     if (config_file.is_file)
    26     if (config_file.is_file)
    28       jsch.setConfigRepository(OpenSSHConfig.parseFile(File.platform_path(config_file)))
    27       jsch.setConfigRepository(OpenSSHConfig.parseFile(File.platform_path(config_file)))
    29 
    28 
    30     val known_hosts = config_dir + Path.explode("known_hosts")
    29     val known_hosts = config_dir + Path.explode("known_hosts")
    31     if (!known_hosts.is_file) known_hosts.file.createNewFile
    30     if (!known_hosts.is_file) known_hosts.file.createNewFile
    32     jsch.setKnownHosts(File.platform_path(known_hosts))
    31     jsch.setKnownHosts(File.platform_path(known_hosts))
    33 
    32 
       
    33     val identity_files =
       
    34       Library.space_explode(':', options.string("ssh_identity_files")).map(Path.explode(_))
    34     for (identity_file <- identity_files if identity_file.is_file)
    35     for (identity_file <- identity_files if identity_file.is_file)
    35       jsch.addIdentity(File.platform_path(identity_file))
    36       jsch.addIdentity(File.platform_path(identity_file))
    36 
    37 
    37     new SSH(jsch)
    38     new SSH(options, jsch)
    38   }
    39   }
       
    40 
       
    41   def connect_timeout(options: Options): Int =
       
    42     options.seconds("ssh_connect_timeout").ms.toInt
    39 
    43 
    40 
    44 
    41   /* logging */
    45   /* logging */
    42 
    46 
    43   def logging(verbose: Boolean = true, debug: Boolean = false)
    47   def logging(verbose: Boolean = true, debug: Boolean = false)
    71     def promptYesNo(msg: String): Boolean = false
    75     def promptYesNo(msg: String): Boolean = false
    72     def showMessage(msg: String): Unit = Output.writeln(msg)
    76     def showMessage(msg: String): Unit = Output.writeln(msg)
    73   }
    77   }
    74 
    78 
    75 
    79 
    76   /* remote command execution */
    80   /* channel: exec, sftp etc. */
    77 
    81 
    78   class Exec private [SSH](val session: Session, val channel: ChannelExec)
    82   class Channel[C <: JSch_Channel] private[SSH](val session: Session,
       
    83     val kind: String, val channel_options: Options, val channel: C)
    79   {
    84   {
    80     override def toString: String = "exec " + session.toString
    85     override def toString: String = kind + " " + session.toString
    81 
    86 
    82     def close { channel.disconnect }
    87     def close { channel.disconnect }
    83   }
    88   }
    84 
    89 
    85 
    90 
    86   /* session */
    91   /* session */
    87 
    92 
    88   class Session private[SSH](val session: JSch_Session)
    93   class Session private[SSH](val session_options: Options, val session: JSch_Session)
    89   {
    94   {
    90     override def toString: String =
    95     override def toString: String =
    91       (if (session.getUserName == null) "" else session.getUserName + "@") +
    96       (if (session.getUserName == null) "" else session.getUserName + "@") +
    92       (if (session.getHost == null) "" else session.getHost) +
    97       (if (session.getHost == null) "" else session.getHost) +
    93       (if (session.getPort == 22) "" else ":" + session.getPort) +
    98       (if (session.getPort == 22) "" else ":" + session.getPort) +
    94       (if (session.isConnected) "" else " (disconnected)")
    99       (if (session.isConnected) "" else " (disconnected)")
    95 
   100 
    96     def close { session.disconnect }
   101     def close { session.disconnect }
    97 
   102 
    98     def exec(command: String, connect_timeout: Time = Time.seconds(60)): Exec =
   103     def exec(command: String, options: Options = session_options): Channel[ChannelExec] =
    99     {
   104     {
   100       val channel = session.openChannel("exec").asInstanceOf[ChannelExec]
   105       val kind = "exec"
       
   106       val channel = session.openChannel(kind).asInstanceOf[ChannelExec]
   101       channel.setCommand(command)
   107       channel.setCommand(command)
   102       channel.connect(connect_timeout.ms.toInt)
       
   103 
   108 
   104       new Exec(this, channel)
   109       channel.connect(connect_timeout(options))
       
   110       new Channel(this, kind, options, channel)
   105     }
   111     }
   106 
   112 
   107     def channel_sftp: ChannelSftp =
   113     def sftp(options: Options = session_options): Channel[ChannelSftp] =
   108       session.openChannel("sftp").asInstanceOf[ChannelSftp]
   114     {
       
   115       val kind = "sftp"
       
   116       val channel = session.openChannel(kind).asInstanceOf[ChannelSftp]
       
   117 
       
   118       channel.connect(connect_timeout(options))
       
   119       new Channel(this, kind, options, channel)
       
   120     }
   109   }
   121   }
   110 }
   122 }
   111 
   123 
   112 class SSH private(val jsch: JSch)
   124 class SSH private(val options: Options, val jsch: JSch)
   113 {
   125 {
   114   def open_session(host: String, port: Int = 22, user: String = null,
   126   def open_session(host: String, port: Int = 22, user: String = null): SSH.Session =
   115     connect_timeout: Time = Time.seconds(60),
       
   116     compression: Boolean = true): SSH.Session =
       
   117   {
   127   {
   118     val session = jsch.getSession(user, host, port)
   128     val session = jsch.getSession(user, host, port)
   119 
   129 
   120     session.setUserInfo(SSH.No_User_Info)
   130     session.setUserInfo(SSH.No_User_Info)
   121     session.setConfig("MaxAuthTries", "3")
   131     session.setConfig("MaxAuthTries", "3")
   122 
   132 
   123     if (compression) {
   133     if (options.bool("ssh_compression")) {
   124       session.setConfig("compression.s2c", "zlib@openssh.com,zlib,none")
   134       session.setConfig("compression.s2c", "zlib@openssh.com,zlib,none")
   125       session.setConfig("compression.c2s", "zlib@openssh.com,zlib,none")
   135       session.setConfig("compression.c2s", "zlib@openssh.com,zlib,none")
   126       session.setConfig("compression_level", "9")
   136       session.setConfig("compression_level", "9")
   127     }
   137     }
   128 
   138 
   129     session.connect(connect_timeout.ms.toInt)
   139     session.connect(SSH.connect_timeout(options))
   130 
   140     new SSH.Session(options, session)
   131     new SSH.Session(session)
       
   132   }
   141   }
   133 }
   142 }