Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gssapi #767

Merged
merged 15 commits into from
Nov 9, 2023
19 changes: 12 additions & 7 deletions core/remote_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class RemoteFileClient(threading.Thread):

remote_password_dict = {}

def __init__(self, ssh_host, ssh_user, ssh_port, server_port, callback):
def __init__(self, ssh_host, ssh_user, ssh_port, server_port, callback, use_gssapi=False):
threading.Thread.__init__(self)

# Init.
Expand All @@ -42,7 +42,7 @@ def __init__(self, ssh_host, ssh_user, ssh_port, server_port, callback):


# Build SSH channel between local client and remote server.
self.ssh = self.connect_ssh()
self.ssh = self.connect_ssh(use_gssapi)
self.transport = self.ssh.get_transport()
self.chan = self.transport.open_channel("direct-tcpip", (self.ssh_host, self.server_port), ('0.0.0.0', 0))

Expand All @@ -52,18 +52,23 @@ def ssh_pub_key(self):
pub_keys = glob.glob(os.path.join(ssh_dir, '*.pub'))
return pub_keys[0]

def connect_ssh(self):
def connect_ssh(self, use_gssapi):
import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

try:
# Login server with ssh public key.
pub_key = self.ssh_pub_key()
ssh.connect(self.ssh_host, port=self.ssh_port, username=self.ssh_user, key_filename=pub_key)
if use_gssapi:
import gssapi
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@werhner I got error from ruff "gssapi imported but unused`, we should remove this line?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's ok to remote this line.

ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(self.ssh_host, port=self.ssh_port, username=self.ssh_user, gss_auth=True, gss_kex=True)
else:
# Login server with ssh public key.
pub_key = self.ssh_pub_key()
ssh.connect(self.ssh_host, port=self.ssh_port, username=self.ssh_user, key_filename=pub_key)
except:
# Try login server with password if public key is not available.
password = RemoteFileClient.remote_password_dict[self.ssh_host] if self.ssh_host in RemoteFileClient.remote_password_dict else get_ssh_password(self.ssh_host)
password = RemoteFileClient.remote_password_dict[self.ssh_host] if self.ssh_host in RemoteFileClient.remote_password_dict else get_ssh_password(self.ssh_user, self.ssh_host, self.ssh_port)
try:
ssh.connect(self.ssh_host, port=self.ssh_port, username=self.ssh_user, password=password)

Expand Down
8 changes: 6 additions & 2 deletions core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ def get_file_content_from_file_server(filename):
def get_current_line():
return get_emacs_func_result('get-current-line')

def get_ssh_password(host):
return get_emacs_func_result('get-ssh-password', host)
def get_ssh_password(user, host, port):
return get_emacs_func_result('get-ssh-password', user, host, port)

remote_eval_socket = None
def set_remote_eval_socket(socket):
Expand Down Expand Up @@ -411,6 +411,10 @@ def cmp(x, y):
else:
return 0

def is_valid_ip(ip):
m = re.match(r"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$", ip)
return bool(m) and all(map(lambda n: 0 <= int(n) <= 255, m.groups()))

def is_valid_ip_path(ssh_path):
"""Check if SSH-PATH is a valid ssh path."""
pattern = r"^(?:([a-z_][a-z0-9_-]*)@)?((?:[0-9]{1,3}\.){3}[0-9]{1,3})(?::(\d+))?:~?(.*)$"
Expand Down
67 changes: 39 additions & 28 deletions lsp-bridge.el
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ Then LSP-Bridge will start by gdb, please send new issue with `*lsp-bridge*' buf

(defcustom lsp-bridge-single-lang-server-mode-list
'(
((c-mode c-ts-mode c++-mode c++-ts-mode objc-mode) . lsp-bridge-c-lsp-server)
((c-mode c-ts-mode c++-mode c++-ts-mode objc-mode c-or-c++-ts-mode) . lsp-bridge-c-lsp-server)
((cmake-mode cmake-ts-mode) . "cmake-language-server")
((java-mode java-ts-mode) . "jdtls")
((julia-mode) . "julials")
Expand Down Expand Up @@ -675,6 +675,9 @@ you can customize `lsp-bridge-get-workspace-folder' to return workspace folder p
(defvar lsp-bridge-enable-with-tramp nil
"Whether enable lsp-bridge when editing tramp file.")

(defvar lsp-bridge-remote-save-password nil
"Whether save password in netrc file.")

(defun lsp-bridge-find-file-hook-function ()
(when (and lsp-bridge-enable-with-tramp (file-remote-p (buffer-file-name)))
(lsp-bridge-sync-tramp-remote)))
Expand Down Expand Up @@ -780,9 +783,23 @@ So we build this macro to restore postion after code format."
(defun lsp-bridge--get-current-line-func ()
(buffer-substring-no-properties (line-beginning-position) (line-end-position)))

(defun lsp-bridge--get-ssh-password-func (host)
(defvar-local lsp-bridge-tramp-sync-var nil)

(defun lsp-bridge--get-ssh-password-func (user host port)
(condition-case nil
(read-passwd (format "Password for %s: " host))
(let* ((auth-source-creation-prompts
'((secret . "password for %u@%h: ")))
(found (nth 0 (auth-source-search :max 1
:host host
:user user
:port port
:require '(:secret)
:create t))))
(when (and lsp-bridge-remote-save-password (plist-get found :save-function))
(funcall (plist-get found :save-function)))
(if found
(auth-info-password found)
nil))
(quit (progn
(message "Cancelled password input.")
nil))))
Expand Down Expand Up @@ -2420,38 +2437,32 @@ We need exclude `markdown-code-fontification:*' buffer in `lsp-bridge-monitor-be
(split-string (buffer-string) "\n" t)))))
(lsp-bridge-call-async "open_remote_file" path (list :line 0 :character 0))))

(defun lsp-bridge-update-tramp-file-info (file-name host path tramp-method)
(unless (assoc host lsp-bridge-tramp-alias-alist)
(push `(,host . ,tramp-method) lsp-bridge-tramp-alias-alist))

(with-current-buffer (get-file-buffer file-name)
(setq-local lsp-bridge-remote-file-flag t)
(setq-local lsp-bridge-remote-file-host host)
(setq-local lsp-bridge-remote-file-path path)

(add-hook 'kill-buffer-hook 'lsp-bridge-remote-kill-buffer nil t)
(setq lsp-bridge-tramp-sync-var t)))

(defcustom lsp-bridge-tramp-blacklist nil "tramp hosts that don't use lsp-bridge")

(defun lsp-bridge-sync-tramp-remote ()
(interactive)
(let* ((tramp-file-name (tramp-dissect-file-name (buffer-file-name)))
(let* ((file-name (buffer-file-name))
(tramp-file-name (tramp-dissect-file-name file-name))
(username (tramp-file-name-user tramp-file-name))
(domain (tramp-file-name-domain tramp-file-name))
(port (tramp-file-name-port tramp-file-name))
(host (tramp-file-name-host tramp-file-name))
(path (tramp-file-name-localname tramp-file-name))
alias)

(unless (network-lookup-address-info host 'ipv4 'numeric)
(setq alias host))

(if alias
(if-let (alias-host (assoc alias lsp-bridge-tramp-alias-alist))
(setq host (cdr alias-host))
(let ((default-directory "~/"))
(setq host (string-trim (shell-command-to-string
(format "ssh -G -T %s | grep '^hostname' | cut -d ' ' -f 2" alias)))))
(push `(,alias . ,host) lsp-bridge-tramp-alias-alist)))

(unless (assoc host lsp-bridge-tramp-alias-alist)
(push `(,host . ,(concat (string-join (butlast (string-split (buffer-file-name) ":" t)) ":") ":"))
lsp-bridge-tramp-alias-alist))

(lsp-bridge-call-async "sync_tramp_remote" username host port alias)

(setq-local lsp-bridge-remote-file-flag t)
(setq-local lsp-bridge-remote-file-host host)
(setq-local lsp-bridge-remote-file-path path)
(path (tramp-file-name-localname tramp-file-name)))

(add-hook 'kill-buffer-hook 'lsp-bridge-remote-kill-buffer nil t)))
(when (not (member host lsp-bridge-tramp-blacklist))
(lsp-bridge-call-async "sync_tramp_remote" username host port file-name))))

(defun lsp-bridge-open-remote-file--response(server path content position)
(let ((buf-name (format "[LBR] %s" (file-name-nondirectory path))))
Expand Down
27 changes: 20 additions & 7 deletions lsp_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ def open_remote_file(self, path, jump_define_pos):
ssh_port = 22

if server_username and ssh_port:
self.host_names[server_host] = {"username": server_username, "ssh_port": ssh_port}
self.host_names[server_host] = {"username": server_username, "ssh_port": ssh_port, "use_gssapi": False}

try:
client_id = f"{server_host}:{REMOTE_FILE_ELISP_CHANNEL}"
Expand All @@ -238,23 +238,32 @@ def open_remote_file(self, path, jump_define_pos):
message_emacs("Please input valid path match rule: 'ip:/path/file'.")

@threaded
def sync_tramp_remote(self, server_username, server_host, ssh_port, alias):
import paramiko
if alias:
def sync_tramp_remote(self, server_username, server_host, ssh_port, filename):
use_gssapi = False
alias = None
if not is_valid_ip(server_host):
alias = server_host
if alias in self.host_names:
server_host = self.host_names[alias]["server_host"]
server_username = self.host_names[alias]["username"]
ssh_port = self.host_names[alias]["ssh_port"]
use_gssapi = self.host_names[alias]["use_gssapi"]
else:
import paramiko
ssh_config = paramiko.SSHConfig()
ssh_config.parse(open(os.path.expanduser('~/.ssh/config')))
conf = ssh_config.lookup(alias)

server_host = conf.get('hostname', server_host)
server_username = conf.get('user', server_username)
ssh_port = conf.get('port', ssh_port)
use_gssapi = conf.get('gssapiauthentication', 'no') in ('yes')

self.host_names[alias] = {"server_host": server_host, "username": server_username, "ssh_port": ssh_port}
self.host_names[alias] = {"server_host": server_host, "username": server_username, "ssh_port": ssh_port, "use_gssapi": use_gssapi}

tramp_file_split = filename.rsplit(":", 1)
tramp_method = tramp_file_split[0] + ":"
file_path = tramp_file_split[1]

if not server_username:
if server_host in self.host_names:
Expand All @@ -268,7 +277,7 @@ def sync_tramp_remote(self, server_username, server_host, ssh_port, alias):
else:
ssh_port = 22

self.host_names[server_host] = {"username": server_username, "ssh_port": ssh_port}
self.host_names[server_host] = {"username": server_username, "ssh_port": ssh_port, "use_gssapi": use_gssapi}

try:
client_id = f"{server_host}:{REMOTE_FILE_ELISP_CHANNEL}"
Expand All @@ -277,6 +286,8 @@ def sync_tramp_remote(self, server_username, server_host, ssh_port, alias):
client.send_message("Connect")
message_emacs(f"Connect {server_username}@{server_host}#{ssh_port}...")

eval_in_emacs("lsp-bridge-update-tramp-file-info", filename, server_host, file_path, tramp_method)

except paramiko.ssh_exception.ChannelException:
message_emacs(f"Connect {server_username}@{server_host}:{ssh_port} failed, please make sure `lsp_bridge.py` has start at server.")

Expand Down Expand Up @@ -396,7 +407,9 @@ def get_socket_client(self, server_host, server_port):
self.host_names[server_host]["username"],
self.host_names[server_host]["ssh_port"],
server_port,
lambda message: self.receive_socket_message(message, server_port))
lambda message: self.receive_socket_message(message, server_port),
self.host_names[server_host]["use_gssapi"]
)
client.start()

self.client_dict[client_id] = client
Expand Down
Loading