Added xdotool modifier key support and refactored server documents
This commit is contained in:
parent
50a46e4c42
commit
9f44a9cffd
@ -3,13 +3,20 @@
|
|||||||
|
|
||||||
## Always use xdotool
|
## Always use xdotool
|
||||||
|
|
||||||
Some users may always want xdotool, see the [Streaming.md](Streaming.md) file for more information.
|
Some users may always want xdotool, see the [Streaming.md](Streaming.md) file for more information
|
||||||
|
|
||||||
|
``` go
|
||||||
--- always use xdotool environment variable
|
--- always use xdotool environment variable
|
||||||
|
var alwaysUseXdotool := false
|
||||||
alwaysUseXdotool, alwaysUseXdotoolExists := os.LookupEnv("KEYBOARD_ALWAYS_USE_XDOTOOL")
|
alwaysUseXdotoolEnv, alwaysUseXdotoolExists := os.LookupEnv("KEYBOARD_ALWAYS_USE_XDOTOOL")
|
||||||
|
if alwaysUseXdotoolExists {
|
||||||
|
if alwaysUseXdotoolEnv == "true" || alwaysUseXdotoolEnv == "1" {
|
||||||
|
alwaysUseXdotool = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Authentication token file
|
## Authentication token file
|
||||||
|
6
Makefile
6
Makefile
@ -1,8 +1,10 @@
|
|||||||
weave:
|
weave:
|
||||||
srcweave --formatter srcweave-format --weave docs/ ReadMe.md security/Authentication.md EnvironmentVariables.md Dependencies.md Server.md Streaming.md ThreatModel.md Client.md
|
srcweave --formatter srcweave-format --weave docs/ ReadMe.md security/Authentication.md EnvironmentVariables.md Dependencies.md server/Server.md server/Streaming.md \
|
||||||
|
server/XdotoolCommands.md ThreatModel.md Client.md
|
||||||
util/removefencedcode.py
|
util/removefencedcode.py
|
||||||
tangle:
|
tangle:
|
||||||
srcweave --formatter srcweave-format --tangle smartkeyboard/ ReadMe.md security/Authentication.md EnvironmentVariables.md Dependencies.md Server.md Streaming.md ThreatModel.md Client.md tools/Tools.md tools/Editor.md tools/Input.md
|
srcweave --formatter srcweave-format --tangle smartkeyboard/ ReadMe.md security/Authentication.md EnvironmentVariables.md Dependencies.md \
|
||||||
|
server/Server.md server/Streaming.md server/Sendkeys.md server/XdotoolCommands.md ThreatModel.md Client.md tools/Tools.md tools/Editor.md tools/Input.md
|
||||||
clean:
|
clean:
|
||||||
rm -rf docs
|
rm -rf docs
|
||||||
find smartkeyboard/ -type f -not -name "*_test.go" -delete
|
find smartkeyboard/ -type f -not -name "*_test.go" -delete
|
||||||
|
170
Streaming.md
170
Streaming.md
@ -1,170 +0,0 @@
|
|||||||
# Streaming keyboard input
|
|
||||||
|
|
||||||
We use the Gorilla websocket library to handle the websocket connection.
|
|
||||||
|
|
||||||
Most of the time, we can use sendkeys (which uses libinput) to effeciently press keys. However, if we need to send a character that sendkeys doesn't know about, we can use the xdotool command. xdotool is also useful if one does not want to use root.
|
|
||||||
|
|
||||||
xdotool spawns a new process for each keypress, so it's not as effecient as sendkeys.
|
|
||||||
|
|
||||||
To specify xdotool usage, the client should send a message with the format `{kb_cmd:xdotool}:message` where message is a utf-8 string.
|
|
||||||
|
|
||||||
``` go
|
|
||||||
--- streaming keyboard input
|
|
||||||
|
|
||||||
func clientConnected(w http.ResponseWriter, r *http.Request) {
|
|
||||||
keyboard, err := sendkeys.NewKBWrapWithOptions(sendkeys.Noisy)
|
|
||||||
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
c, err := upgrader.Upgrade(w, r, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("upgrade:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
// get auth token
|
|
||||||
_, message, err := c.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("read:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if auth.CheckAuthToken(string(message)) != nil {
|
|
||||||
log.Println("invalid token")
|
|
||||||
c.WriteMessage(websocket.TextMessage, []byte("invalid token"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.WriteMessage(websocket.TextMessage, []byte("authenticated"))
|
|
||||||
|
|
||||||
for {
|
|
||||||
time.Sleep(25 * time.Millisecond)
|
|
||||||
_, message, err := c.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
log.Println("read:", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
log.Printf("recv: %s", message)
|
|
||||||
message_string := string(message)
|
|
||||||
if message_string == "" {
|
|
||||||
message_string = "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
@{send keys to system}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
---
|
|
||||||
```
|
|
||||||
|
|
||||||
# Sending the keys
|
|
||||||
|
|
||||||
Sending the keys is a bit tricky as we need to manually convert backspace, tab, enter and modifier keys.
|
|
||||||
|
|
||||||
``` go
|
|
||||||
|
|
||||||
--- send keys to system
|
|
||||||
|
|
||||||
|
|
||||||
// regex if string has characters we need to convert to key presses
|
|
||||||
characterRegex, _ := regexp.Compile(`[^\x08]\x08|\t|\n`)
|
|
||||||
|
|
||||||
doXDoTool := func(command string, keys string)(err error) {
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
if command == "type" {
|
|
||||||
cmd = exec.Command("xdotool", command, "--delay", "25", keys)
|
|
||||||
} else {
|
|
||||||
cmd = exec.Command("xdotool", command, keys)
|
|
||||||
}
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(message_string, "{kb_cmd:xdotool}:") {
|
|
||||||
message_string = strings.TrimPrefix(message_string, "{kb_cmd:xdotool}:")
|
|
||||||
if message_string == "" {
|
|
||||||
message_string = "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if characterRegex.MatchString(message_string) {
|
|
||||||
for _, character := range message_string {
|
|
||||||
charString := string(character)
|
|
||||||
if charString == "\n" {
|
|
||||||
charString = "Enter"
|
|
||||||
} else if charString == "\t" {
|
|
||||||
charString = "Tab"
|
|
||||||
} else if charString == "\b" {
|
|
||||||
charString = "BackSpace"
|
|
||||||
} else{
|
|
||||||
doXDoTool("type", charString)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// key is required for special characters
|
|
||||||
err = doXDoTool("key", charString)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
doXDoTool("type", message_string)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
} else if strings.HasPrefix(message_string, "{kb_cmd:xdotoolk}:") {
|
|
||||||
message_string = strings.TrimPrefix(message_string, "{kb_cmd:xdotoolk}:")
|
|
||||||
if message_string == "" {
|
|
||||||
message_string = "\n"
|
|
||||||
}
|
|
||||||
if characterRegex.MatchString(message_string) {
|
|
||||||
for _, character := range message_string {
|
|
||||||
charString := string(character)
|
|
||||||
if charString == "\n" {
|
|
||||||
charString = "Enter"
|
|
||||||
} else if charString == "\t" {
|
|
||||||
charString = "Tab"
|
|
||||||
} else if charString == "\b" {
|
|
||||||
charString = "BackSpace"
|
|
||||||
} else{
|
|
||||||
doXDoTool("key", charString)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// key is required for special characters
|
|
||||||
err = doXDoTool("key", charString)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
doXDoTool("key", message_string)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if characterRegex.MatchString(message_string) {
|
|
||||||
for _, character := range message_string {
|
|
||||||
charString := string(character)
|
|
||||||
if charString == "\n" {
|
|
||||||
keyboard.Enter()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if charString == "\t" {
|
|
||||||
keyboard.Tab()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if charString == "\b" {
|
|
||||||
keyboard.BackSpace()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err = keyboard.Type(charString)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("type:", err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err = keyboard.Type(message_string)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("type:", err)
|
|
||||||
}
|
|
||||||
---
|
|
||||||
```
|
|
39
server/Sendkeys.md
Normal file
39
server/Sendkeys.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Send keys streaming approach
|
||||||
|
|
||||||
|
When applicable to use send keys, we need to detect Enter, Tab, and BackSpace and use the applicatble functions.
|
||||||
|
|
||||||
|
We test if it meets the characterRegex, and if so we iterate for each character and send the corresponding key.
|
||||||
|
Otherwise we send the entire string at once.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
--- do streaming sendkeys approach
|
||||||
|
if characterRegex.MatchString(message_string) {
|
||||||
|
for _, character := range message_string {
|
||||||
|
charString := string(character)
|
||||||
|
if charString == "\n" {
|
||||||
|
keyboard.Enter()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if charString == "\t" {
|
||||||
|
keyboard.Tab()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if charString == "\b" {
|
||||||
|
keyboard.BackSpace()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = keyboard.Type(charString)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("type:", err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = keyboard.Type(message_string)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("type:", err)
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
```
|
90
server/Streaming.md
Normal file
90
server/Streaming.md
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
# Streaming keyboard input
|
||||||
|
|
||||||
|
We use the Gorilla websocket library to handle the websocket connection.
|
||||||
|
|
||||||
|
Most of the time, we can use sendkeys (which uses libinput) to effeciently press keys. However, if we need to send a character that sendkeys doesn't know about, we can use the xdotool command. xdotool is also useful if one does not want to use root.
|
||||||
|
|
||||||
|
xdotool spawns a new process for each keypress, so it's not as effecient as sendkeys.
|
||||||
|
|
||||||
|
To specify xdotool usage, the client should send a message with the format `{kb_cmd:xdotool}:message` where message is a utf-8 string.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
--- streaming keyboard input
|
||||||
|
|
||||||
|
func clientConnected(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// regex if string has characters we need to convert to key presses
|
||||||
|
characterRegex, _ := regexp.Compile(`[^\x08]\x08|\t|\n`)
|
||||||
|
keyboard, err := sendkeys.NewKBWrapWithOptions(sendkeys.Noisy)
|
||||||
|
|
||||||
|
@{always use xdotool environment variable}
|
||||||
|
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
c, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Print("upgrade:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
// get auth token
|
||||||
|
_, message, err := c.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("read:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if auth.CheckAuthToken(string(message)) != nil {
|
||||||
|
log.Println("invalid token")
|
||||||
|
c.WriteMessage(websocket.TextMessage, []byte("invalid token"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.WriteMessage(websocket.TextMessage, []byte("authenticated"))
|
||||||
|
|
||||||
|
for {
|
||||||
|
time.Sleep(25 * time.Millisecond)
|
||||||
|
_, message, err := c.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("read:", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Printf("recv: %s", message)
|
||||||
|
message_string := string(message)
|
||||||
|
if message_string == "" {
|
||||||
|
message_string = "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
@{send keys to system}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
# Sending the keys
|
||||||
|
|
||||||
|
Sending the keys is a bit tricky as we need to manually convert backspace, tab, enter and modifier keys.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
|
||||||
|
--- send keys to system
|
||||||
|
|
||||||
|
|
||||||
|
doXDoTool := func(command string, keys string)(err error) {
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
if command == "type" {
|
||||||
|
cmd = exec.Command("xdotool", command, "--delay", "25", keys)
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command("xdotool", command, keys)
|
||||||
|
}
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
@{handle xdotoool commands}
|
||||||
|
|
||||||
|
@{do streaming sendkeys approach}
|
||||||
|
|
||||||
|
---
|
||||||
|
```
|
54
server/XdotoolCommands.md
Normal file
54
server/XdotoolCommands.md
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# Handle xdotool commands
|
||||||
|
|
||||||
|
Currently the two commands are `type` and `key`. `type` is used to type a character and `key` is used to type a special character like `Enter` or `Backspace`.
|
||||||
|
|
||||||
|
`type` is specified by '{kb_cmd:xdotool}:', and `key` is specified by '{kb_cmd:kxdotool}:'. If the command is not specified and `alwaysUseXdotool` is set from the environment variable, it will default to `type`.
|
||||||
|
|
||||||
|
``` go
|
||||||
|
|
||||||
|
``` go
|
||||||
|
|
||||||
|
|
||||||
|
``` go
|
||||||
|
--- handle xdotoool commands
|
||||||
|
|
||||||
|
if alwaysUseXdotool || strings.HasPrefix(message_string, "{kb_cmd:xdotool}:") {
|
||||||
|
message_string = strings.TrimPrefix(message_string, "{kb_cmd:xdotool}:")
|
||||||
|
if message_string == "" {
|
||||||
|
message_string = "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if characterRegex.MatchString(message_string) {
|
||||||
|
for _, character := range message_string {
|
||||||
|
charString := string(character)
|
||||||
|
if charString == "\n" {
|
||||||
|
charString = "Enter"
|
||||||
|
} else if charString == "\t" {
|
||||||
|
charString = "Tab"
|
||||||
|
} else if charString == "\b" {
|
||||||
|
charString = "BackSpace"
|
||||||
|
} else{
|
||||||
|
doXDoTool("type", charString)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// key is required for special characters
|
||||||
|
err = doXDoTool("key", charString)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
doXDoTool("type", message_string)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else if strings.HasPrefix(message_string, "{kb_cmd:kxdotool}:") {
|
||||||
|
message_string = strings.TrimPrefix(message_string, "{kb_cmd:kxdotool}:")
|
||||||
|
if message_string == "" {
|
||||||
|
message_string = "\n"
|
||||||
|
}
|
||||||
|
doXDoTool("key", message_string)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
```
|
Loading…
Reference in New Issue
Block a user