Added xdotool modifier key support and refactored server documents

This commit is contained in:
Kevin F 2023-01-29 19:45:00 -06:00
parent 50a46e4c42
commit 9f44a9cffd
7 changed files with 197 additions and 175 deletions

View File

@ -3,13 +3,20 @@
## 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
alwaysUseXdotool, alwaysUseXdotoolExists := os.LookupEnv("KEYBOARD_ALWAYS_USE_XDOTOOL")
var alwaysUseXdotool := false
alwaysUseXdotoolEnv, alwaysUseXdotoolExists := os.LookupEnv("KEYBOARD_ALWAYS_USE_XDOTOOL")
if alwaysUseXdotoolExists {
if alwaysUseXdotoolEnv == "true" || alwaysUseXdotoolEnv == "1" {
alwaysUseXdotool = true
}
}
---
```
## Authentication token file

View File

@ -1,8 +1,10 @@
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
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:
rm -rf docs
find smartkeyboard/ -type f -not -name "*_test.go" -delete

View File

@ -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
View 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
View 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
View 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
}
---
```