main.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package at
  3. import (
  4. "bytes"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "os"
  10. "reflect"
  11. "strconv"
  12. "strings"
  13. "time"
  14. "unicode/utf16"
  15. "golang.org/x/sys/unix"
  16. "kitty"
  17. "kitty/tools/cli"
  18. "kitty/tools/crypto"
  19. "kitty/tools/tty"
  20. "kitty/tools/tui"
  21. "kitty/tools/tui/loop"
  22. "kitty/tools/utils"
  23. "kitty/tools/utils/base85"
  24. "kitty/tools/utils/shlex"
  25. )
  26. const lowerhex = "0123456789abcdef"
  27. var ProtocolVersion [3]int = [3]int{0, 26, 0}
  28. type password struct {
  29. val string
  30. is_set bool
  31. }
  32. type GlobalOptions struct {
  33. to_network, to_address string
  34. password password
  35. to_address_is_from_env_var bool
  36. already_setup bool
  37. }
  38. var global_options GlobalOptions
  39. func expand_ansi_c_escapes_in_args(args ...string) (escaped_string, error) {
  40. for i, x := range args {
  41. args[i] = shlex.ExpandANSICEscapes(x)
  42. }
  43. return escaped_string(strings.Join(args, " ")), nil
  44. }
  45. func escape_list_of_strings(args []string) []escaped_string {
  46. ans := make([]escaped_string, len(args))
  47. for i, x := range args {
  48. ans[i] = escaped_string(x)
  49. }
  50. return ans
  51. }
  52. func set_payload_string_field(io_data *rc_io_data, field, data string) {
  53. payload_interface := reflect.ValueOf(&io_data.rc.Payload).Elem()
  54. struct_in_interface := reflect.New(payload_interface.Elem().Type()).Elem()
  55. struct_in_interface.Set(payload_interface.Elem()) // copies the payload to struct_in_interface
  56. struct_in_interface.FieldByName(field).SetString(data)
  57. payload_interface.Set(struct_in_interface) // copies struct_in_interface back to payload
  58. }
  59. func get_pubkey(encoded_key string) (encryption_version string, pubkey []byte, err error) {
  60. if encoded_key == "" {
  61. encoded_key = os.Getenv("KITTY_PUBLIC_KEY")
  62. if encoded_key == "" {
  63. err = fmt.Errorf("Password usage requested but KITTY_PUBLIC_KEY environment variable is not available")
  64. return
  65. }
  66. }
  67. encryption_version, encoded_key, found := strings.Cut(encoded_key, ":")
  68. if !found {
  69. err = fmt.Errorf("KITTY_PUBLIC_KEY environment variable does not have a : in it")
  70. return
  71. }
  72. if encryption_version != kitty.RC_ENCRYPTION_PROTOCOL_VERSION {
  73. err = fmt.Errorf("KITTY_PUBLIC_KEY has unknown version, if you are running on a remote system, update kitty on this system")
  74. return
  75. }
  76. pubkey = make([]byte, base85.DecodedLen(len(encoded_key)))
  77. n, err := base85.Decode(pubkey, []byte(encoded_key))
  78. if err == nil {
  79. pubkey = pubkey[:n]
  80. }
  81. return
  82. }
  83. type escaped_string string
  84. func (s escaped_string) MarshalJSON() ([]byte, error) {
  85. // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON
  86. // we additionally escape all non-ascii chars so they can be safely transmitted inside an escape code
  87. src := utf16.Encode([]rune(s))
  88. buf := make([]byte, 0, len(src)+128)
  89. a := func(x ...byte) {
  90. buf = append(buf, x...)
  91. }
  92. a('"')
  93. for _, r := range src {
  94. if ' ' <= r && r <= 126 {
  95. if r == '\\' || r == '"' {
  96. buf = append(buf, '\\')
  97. }
  98. buf = append(buf, byte(r))
  99. continue
  100. }
  101. switch r {
  102. case '\n':
  103. a('\\', 'n')
  104. case '\t':
  105. a('\\', 't')
  106. case '\r':
  107. a('\\', 'r')
  108. case '\f':
  109. a('\\', 'f')
  110. case '\b':
  111. a('\\', 'b')
  112. default:
  113. a('\\', 'u')
  114. for s := 12; s >= 0; s -= 4 {
  115. a(lowerhex[r>>uint(s)&0xF])
  116. }
  117. }
  118. }
  119. a('"')
  120. return buf, nil
  121. }
  122. func simple_serializer(rc *utils.RemoteControlCmd) (ans []byte, err error) {
  123. return json.Marshal(rc)
  124. }
  125. type serializer_func func(rc *utils.RemoteControlCmd) ([]byte, error)
  126. func create_serializer(password password, encoded_pubkey string, io_data *rc_io_data) (err error) {
  127. io_data.serializer = simple_serializer
  128. if password.is_set {
  129. encryption_version, pubkey, err := get_pubkey(encoded_pubkey)
  130. if err != nil {
  131. return err
  132. }
  133. io_data.serializer = func(rc *utils.RemoteControlCmd) (ans []byte, err error) {
  134. ec, err := crypto.Encrypt_cmd(rc, global_options.password.val, pubkey, encryption_version)
  135. if err != nil {
  136. return
  137. }
  138. return json.Marshal(ec)
  139. }
  140. if io_data.timeout < 120*time.Second {
  141. io_data.timeout = 120 * time.Second
  142. }
  143. }
  144. return nil
  145. }
  146. type ResponseData struct {
  147. as_str string
  148. is_string bool
  149. }
  150. func (self *ResponseData) UnmarshalJSON(data []byte) error {
  151. if bytes.HasPrefix(data, []byte("\"")) {
  152. self.is_string = true
  153. return json.Unmarshal(data, &self.as_str)
  154. }
  155. if bytes.Equal(data, []byte("true")) {
  156. self.as_str = "True"
  157. } else if bytes.Equal(data, []byte("false")) {
  158. self.as_str = "False"
  159. } else {
  160. self.as_str = string(data)
  161. }
  162. return nil
  163. }
  164. type Response struct {
  165. Ok bool `json:"ok"`
  166. Data ResponseData `json:"data,omitempty"`
  167. Error string `json:"error,omitempty"`
  168. Traceback string `json:"tb,omitempty"`
  169. }
  170. type rc_io_data struct {
  171. cmd *cli.Command
  172. rc *utils.RemoteControlCmd
  173. serializer serializer_func
  174. on_key_event func(lp *loop.Loop, ke *loop.KeyEvent) error
  175. string_response_is_err bool
  176. handle_response func(data []byte) error
  177. timeout time.Duration
  178. multiple_payload_generator func(io_data *rc_io_data) (bool, error)
  179. chunks_done bool
  180. }
  181. func (self *rc_io_data) next_chunk() (chunk []byte, err error) {
  182. if self.chunks_done {
  183. return make([]byte, 0), nil
  184. }
  185. if self.multiple_payload_generator != nil {
  186. is_last, err := self.multiple_payload_generator(self)
  187. if err != nil {
  188. return nil, err
  189. }
  190. if is_last {
  191. self.chunks_done = true
  192. }
  193. return self.serializer(self.rc)
  194. }
  195. self.chunks_done = true
  196. return self.serializer(self.rc)
  197. }
  198. func get_response(do_io func(io_data *rc_io_data) ([]byte, error), io_data *rc_io_data) (ans *Response, err error) {
  199. serialized_response, err := do_io(io_data)
  200. if err != nil {
  201. if errors.Is(err, os.ErrDeadlineExceeded) && io_data.rc.Async != "" {
  202. io_data.rc.Payload = nil
  203. io_data.rc.CancelAsync = true
  204. io_data.multiple_payload_generator = nil
  205. io_data.rc.NoResponse = true
  206. io_data.chunks_done = false
  207. _, _ = do_io(io_data)
  208. err = fmt.Errorf("Timed out waiting for a response from kitty")
  209. }
  210. return nil, err
  211. }
  212. if len(serialized_response) == 0 {
  213. if io_data.rc.NoResponse {
  214. res := Response{Ok: true}
  215. ans = &res
  216. return
  217. }
  218. err = fmt.Errorf("Received empty response from kitty")
  219. return
  220. }
  221. var response Response
  222. err = json.Unmarshal(serialized_response, &response)
  223. if err != nil {
  224. err = fmt.Errorf("Invalid response received from kitty, unmarshalling error: %w", err)
  225. return
  226. }
  227. ans = &response
  228. return
  229. }
  230. var running_shell = false
  231. type exit_error struct {
  232. exit_code int
  233. }
  234. func (m *exit_error) Error() string {
  235. return fmt.Sprintf("Subprocess exit with code: %d", m.exit_code)
  236. }
  237. func send_rc_command(io_data *rc_io_data) (err error) {
  238. err = setup_global_options(io_data.cmd)
  239. if err != nil {
  240. return err
  241. }
  242. wid, err := strconv.Atoi(os.Getenv("KITTY_WINDOW_ID"))
  243. if err == nil && wid > 0 {
  244. io_data.rc.KittyWindowId = uint(wid)
  245. }
  246. err = create_serializer(global_options.password, "", io_data)
  247. if err != nil {
  248. return
  249. }
  250. var response *Response
  251. response, err = get_response(utils.IfElse(global_options.to_network == "", do_tty_io, do_socket_io), io_data)
  252. if err != nil || response == nil {
  253. return
  254. }
  255. if !response.Ok {
  256. if response.Traceback != "" {
  257. fmt.Fprintln(os.Stderr, response.Traceback)
  258. }
  259. return fmt.Errorf("%s", response.Error)
  260. }
  261. if io_data.handle_response != nil {
  262. return io_data.handle_response(utils.UnsafeStringToBytes(response.Data.as_str))
  263. }
  264. if response.Data.is_string && io_data.string_response_is_err {
  265. return fmt.Errorf("%s", response.Data.as_str)
  266. }
  267. if response.Data.as_str != "" {
  268. fmt.Println(strings.TrimRight(response.Data.as_str, "\n \t"))
  269. }
  270. return
  271. }
  272. func get_password(password string, password_file string, password_env string, use_password string) (ans password, err error) {
  273. if use_password == "never" {
  274. return
  275. }
  276. if password != "" {
  277. ans.is_set, ans.val = true, password
  278. }
  279. if !ans.is_set && password_file != "" {
  280. if password_file == "-" {
  281. if tty.IsTerminal(os.Stdin.Fd()) {
  282. p, err := tui.ReadPassword("Password: ", true)
  283. if err != nil {
  284. return ans, err
  285. }
  286. ans.is_set, ans.val = true, p
  287. } else {
  288. var q []byte
  289. q, err = io.ReadAll(os.Stdin)
  290. if err == nil {
  291. ans.is_set, ans.val = true, strings.TrimRight(string(q), " \n\t")
  292. }
  293. ttyf, err := os.Open(tty.Ctermid())
  294. if err == nil {
  295. err = unix.Dup2(int(ttyf.Fd()), int(os.Stdin.Fd())) //nolint ineffassign err is returned indicating duping failed
  296. ttyf.Close()
  297. }
  298. }
  299. } else if strings.HasPrefix(password_file, "fd:") {
  300. var fd int
  301. if fd, err = strconv.Atoi(password_file[3:]); err == nil {
  302. f := os.NewFile(uintptr(fd), password_file)
  303. var q []byte
  304. if q, err = io.ReadAll(f); err == nil {
  305. ans.is_set = true
  306. ans.val = string(q)
  307. }
  308. f.Close()
  309. }
  310. } else {
  311. var q []byte
  312. q, err = os.ReadFile(password_file)
  313. if err == nil {
  314. ans.is_set, ans.val = true, strings.TrimRight(string(q), " \n\t")
  315. } else {
  316. if errors.Is(err, os.ErrNotExist) {
  317. err = nil
  318. }
  319. }
  320. }
  321. if err != nil {
  322. return
  323. }
  324. }
  325. if !ans.is_set && password_env != "" {
  326. ans.val, ans.is_set = os.LookupEnv(password_env)
  327. }
  328. if !ans.is_set && use_password == "always" {
  329. ans.is_set = true
  330. return ans, nil
  331. }
  332. if len(ans.val) > 1024 {
  333. return ans, fmt.Errorf("Specified password is too long")
  334. }
  335. return ans, nil
  336. }
  337. var all_commands []func(*cli.Command) *cli.Command = make([]func(*cli.Command) *cli.Command, 0, 64)
  338. func register_at_cmd(f func(*cli.Command) *cli.Command) {
  339. all_commands = append(all_commands, f)
  340. }
  341. func setup_global_options(cmd *cli.Command) (err error) {
  342. if global_options.already_setup {
  343. return nil
  344. }
  345. err = cmd.GetOptionValues(&rc_global_opts)
  346. if err != nil {
  347. return err
  348. }
  349. if rc_global_opts.To == "" {
  350. rc_global_opts.To = os.Getenv("KITTY_LISTEN_ON")
  351. global_options.to_address_is_from_env_var = true
  352. }
  353. if rc_global_opts.To != "" {
  354. network, address, err := utils.ParseSocketAddress(rc_global_opts.To)
  355. if err != nil {
  356. return err
  357. }
  358. global_options.to_network = network
  359. global_options.to_address = address
  360. }
  361. q, err := get_password(rc_global_opts.Password, rc_global_opts.PasswordFile, rc_global_opts.PasswordEnv, rc_global_opts.UsePassword)
  362. global_options.password = q
  363. global_options.already_setup = true
  364. return err
  365. }
  366. func EntryPoint(tool_root *cli.Command) *cli.Command {
  367. at_root_command := tool_root.AddSubCommand(&cli.Command{
  368. Name: "@",
  369. Usage: "[global options] [sub-command] [sub-command options] [sub-command args]",
  370. ShortDescription: "Control kitty remotely",
  371. HelpText: "Control kitty by sending it commands. Set the allow_remote_control option in :file:`kitty.conf` for this to work. When run without any sub-commands this will start an interactive shell to control kitty.",
  372. Run: shell_main,
  373. })
  374. add_rc_global_opts(at_root_command)
  375. global_options_group := at_root_command.OptionGroups[0]
  376. for _, reg_func := range all_commands {
  377. c := reg_func(at_root_command)
  378. clone := tool_root.AddClone("", c)
  379. clone.Name = "@" + c.Name
  380. clone.Hidden = true
  381. clone.OptionGroups = append(clone.OptionGroups, global_options_group.Clone(clone))
  382. }
  383. return at_root_command
  384. }