blob: 450cbe6fb0b89c86372ebe9f8db5b4263755105d [file] [log] [blame]
// Copyright 2015 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package overlord
import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"github.com/googollee/go-socket.io"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/satori/go.uuid"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"time"
)
const (
systemAppDir = "../share/overlord"
webServerHost = "0.0.0.0"
ldInterval = 5
keepAlivePeriod = 1
)
// SpawnTerminalCmd is an overlord intend to launch a terminal.
type SpawnTerminalCmd struct {
Sid string // Session ID
TtyDevice string // Termainl device to open
}
// SpawnShellCmd is an overlord intend to launch a shell command.
type SpawnShellCmd struct {
Sid string // Session ID
Command string // Command to execute
}
// SpawnFileCmd is an overlord intend to perform file transfer.
type SpawnFileCmd struct {
Sid string // Session ID
TerminalSid string // Target terminal's session ID
Action string // Action, download or upload
Filename string // File to perform action on
Dest string // Destination, use for upload
Perm int // File permissions to set
CheckOnly bool // Check permission only (writable?)
}
// SpawnModeForwarderCmd is an overlord intend to perform port forwarding.
type SpawnModeForwarderCmd struct {
Sid string // Session ID
Port int // Port to forward
}
// ConnectLogcatCmd is an overlord intend to connect to a logcat session.
type ConnectLogcatCmd struct {
Conn *websocket.Conn
}
// webSocketContext is used for maintaining the session information of
// WebSocket requests. When requests come from Web Server, we create a new
// WebSocketConext to store the session ID and WebSocket connection. ConnServer
// will request a new terminal connection with the given session ID.
// This way, the ConnServer can retreive the connresponding webSocketContext
// with it's the given session ID and get the WebSocket.
type webSocketContext struct {
Sid string // Session ID
Conn *websocket.Conn // WebSocket connection
}
// newWebsocketContext create webSocketContext object.
func newWebsocketContext(conn *websocket.Conn) *webSocketContext {
return &webSocketContext{
Sid: uuid.Must(uuid.NewV4()).String(),
Conn: conn,
}
}
// TLSCerts stores the TLS certificate filenames.
type TLSCerts struct {
Cert string // cert.pem filename
Key string // key.pem filename
}
// Overlord type is the main context for storing the overlord server state.
type Overlord struct {
lanDiscInterface string // Network interface used for broadcasting LAN discovery packet
lanDisc bool // Enable LAN discovery broadcasting
auth bool // Enable HTTP basic authentication
certs *TLSCerts // TLS certificate
linkTLS bool // Enable TLS between ghost and overlord
htpasswdPath string // Path to .htpasswd file
agents map[string]*ConnServer // Normal ghost agents
logcats map[string]map[string]*ConnServer // logcat clients
wsctxs map[string]*webSocketContext // (sid, webSocketContext) mapping
downloads map[string]*ConnServer // Download file agents
uploads map[string]*ConnServer // Upload file agents
ioserver *socketio.Server // SocketIO server handle
agentsMu sync.Mutex // Mutex for agents
logcatsMu sync.Mutex // Mutex for logcats
wsctxsMu sync.Mutex // Mutex for wsctxs
downloadsMu sync.Mutex // Mutex for downloads
uploadsMu sync.Mutex // Mutex for uploads
ioserverMu sync.Mutex // Mutex for ioserver
}
// NewOverlord creates an Overlord object.
func NewOverlord(lanDiscInterface string, lanDisc bool, auth bool,
certsString string, linkTLS bool, htpasswdPath string) *Overlord {
var certs *TLSCerts
if certsString != "" {
parts := strings.Split(certsString, ",")
if len(parts) != 2 {
log.Fatalf("TLSCerts: invalid TLS certs argument")
} else {
certs = &TLSCerts{parts[0], parts[1]}
}
}
if !filepath.IsAbs(htpasswdPath) {
execPath, err := GetExecutablePath()
if err != nil {
log.Fatalln(err)
}
execDir := filepath.Dir(execPath)
htpasswdPath, err = filepath.Abs(filepath.Join(execDir, htpasswdPath))
if err != nil {
log.Fatalln(err)
}
}
return &Overlord{
lanDiscInterface: lanDiscInterface,
lanDisc: lanDisc,
auth: auth,
certs: certs,
linkTLS: linkTLS,
htpasswdPath: htpasswdPath,
agents: make(map[string]*ConnServer),
logcats: make(map[string]map[string]*ConnServer),
wsctxs: make(map[string]*webSocketContext),
downloads: make(map[string]*ConnServer),
uploads: make(map[string]*ConnServer),
}
}
// Register a client.
func (ovl *Overlord) Register(conn *ConnServer) (*websocket.Conn, error) {
msg, err := json.Marshal(map[string]interface{}{
"mid": conn.Mid,
"sid": conn.Sid,
})
if err != nil {
return nil, err
}
var wsconn *websocket.Conn
switch conn.Mode {
case ModeControl:
ovl.agentsMu.Lock()
if _, ok := ovl.agents[conn.Mid]; ok {
ovl.agentsMu.Unlock()
return nil, errors.New("duplicate machine ID: " + conn.Mid)
}
ovl.agents[conn.Mid] = conn
ovl.agentsMu.Unlock()
ovl.ioserver.BroadcastTo("monitor", "agent joined", string(msg))
case ModeTerminal, ModeShell, ModeForward:
ovl.wsctxsMu.Lock()
ctx, ok := ovl.wsctxs[conn.Sid]
if !ok {
ovl.wsctxsMu.Unlock()
return nil, errors.New("client " + conn.Sid + " registered without context")
}
wsconn = ctx.Conn
ovl.wsctxsMu.Unlock()
case ModeLogcat:
ovl.logcatsMu.Lock()
if _, ok := ovl.logcats[conn.Mid]; !ok {
ovl.logcats[conn.Mid] = make(map[string]*ConnServer)
}
if _, ok := ovl.logcats[conn.Mid][conn.Sid]; ok {
ovl.logcatsMu.Unlock()
return nil, errors.New("duplicate session ID: " + conn.Sid)
}
ovl.logcats[conn.Mid][conn.Sid] = conn
ovl.logcatsMu.Unlock()
ovl.ioserver.BroadcastTo("monitor", "logcat joined", string(msg))
case ModeFile:
// Do nothing, we wait until 'request_to_download' call from client to
// send the message to the browser
default:
return nil, errors.New("Unknown client mode")
}
var id string
if conn.Mode == ModeControl {
id = conn.Mid
} else {
id = conn.Sid
}
log.Printf("%s %s registered\n", ModeStr(conn.Mode), id)
return wsconn, nil
}
// Unregister a client.
func (ovl *Overlord) Unregister(conn *ConnServer) {
msg, err := json.Marshal(map[string]interface{}{
"mid": conn.Mid,
"sid": conn.Sid,
})
if err != nil {
panic(err)
}
switch conn.Mode {
case ModeControl:
ovl.ioserver.BroadcastTo("monitor", "agent left", string(msg))
ovl.agentsMu.Lock()
delete(ovl.agents, conn.Mid)
ovl.agentsMu.Unlock()
case ModeLogcat:
ovl.logcatsMu.Lock()
if _, ok := ovl.logcats[conn.Mid]; ok {
ovl.ioserver.BroadcastTo("monitor", "logcat left", string(msg))
delete(ovl.logcats[conn.Mid], conn.Sid)
if len(ovl.logcats[conn.Mid]) == 0 {
delete(ovl.logcats, conn.Mid)
}
}
ovl.logcatsMu.Unlock()
case ModeFile:
ovl.downloadsMu.Lock()
if _, ok := ovl.downloads[conn.Sid]; ok {
delete(ovl.downloads, conn.Sid)
}
ovl.downloadsMu.Unlock()
ovl.uploadsMu.Lock()
if _, ok := ovl.uploads[conn.Sid]; ok {
delete(ovl.uploads, conn.Sid)
}
ovl.uploadsMu.Unlock()
default:
ovl.wsctxsMu.Lock()
if _, ok := ovl.wsctxs[conn.Sid]; ok {
delete(ovl.wsctxs, conn.Sid)
}
ovl.wsctxsMu.Unlock()
}
var id string
if conn.Mode == ModeControl {
id = conn.Mid
} else {
id = conn.Sid
}
log.Printf("%s %s unregistered\n", ModeStr(conn.Mode), id)
}
// AddWebsocketContext adds an websocket context to the overlord state.
func (ovl *Overlord) AddWebsocketContext(wc *webSocketContext) {
ovl.wsctxsMu.Lock()
ovl.wsctxs[wc.Sid] = wc
ovl.wsctxsMu.Unlock()
}
// RegisterDownloadRequest registers a file download request.
func (ovl *Overlord) RegisterDownloadRequest(conn *ConnServer) {
// Use session ID as download session ID instead of machine ID, so a machine
// can have multiple download at the same time
ovl.ioserver.BroadcastTo(conn.TerminalSid, "file download", string(conn.Sid))
ovl.downloadsMu.Lock()
ovl.downloads[conn.Sid] = conn
ovl.downloadsMu.Unlock()
}
// RegisterUploadRequest registers a file upload request.
func (ovl *Overlord) RegisterUploadRequest(conn *ConnServer) {
// Use session ID as upload session ID instead of machine ID, so a machine
// can have multiple upload at the same time
ovl.ioserver.BroadcastTo(conn.TerminalSid, "file upload", string(conn.Sid))
ovl.uploadsMu.Lock()
ovl.uploads[conn.Sid] = conn
ovl.uploadsMu.Unlock()
}
// Handle TCP Connection.
func (ovl *Overlord) handleConnection(conn net.Conn) {
handler := NewConnServer(ovl, conn)
go handler.Listen()
}
// ServSocket is the socket server main routine.
func (ovl *Overlord) ServSocket(port int) {
var (
err error
ln net.Listener
tlsConfig *tls.Config
)
addr := fmt.Sprintf("0.0.0.0:%d", port)
if ovl.certs != nil && ovl.linkTLS { // TLS enabled
cert, err := tls.LoadX509KeyPair(ovl.certs.Cert, ovl.certs.Key)
if err != nil {
log.Fatalf("Unable to load TLS cert files: %s\n", err)
}
tlsConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
}
}
ln, err = net.Listen("tcp", addr)
if err != nil {
log.Fatalf("Unable to listen at %s: %s\n", addr, err)
}
defer ln.Close()
tlsStatus := "disabled"
if tlsConfig != nil {
tlsStatus = "enabled"
}
log.Printf("TCP socket server started, listening at %s, TLS: %s",
addr, tlsStatus)
for {
conn, err := ln.Accept()
if err != nil {
log.Fatalln("Unable to accept client:", err)
}
log.Printf("Incoming connection from %s\n", conn.RemoteAddr())
// Set TCP Keep Alive
tcpConn := conn.(*net.TCPConn)
tcpConn.SetKeepAlive(true)
tcpConn.SetKeepAlivePeriod(keepAlivePeriod * time.Second)
if tlsConfig != nil {
conn = tls.Server(conn, tlsConfig)
}
ovl.handleConnection(conn)
}
}
// InitSocketIOServer initializes the Socket.io server.
func (ovl *Overlord) InitSocketIOServer() {
server, err := socketio.NewServer(nil)
if err != nil {
log.Fatalln(err)
}
server.On("connection", func(so socketio.Socket) {
so.Join("monitor")
})
server.On("error", func(so socketio.Socket, err error) {
log.Println("error:", err)
})
// Client initiated subscribtion
server.On("subscribe", func(so socketio.Socket, name string) {
so.Join(name)
})
// Client initiated unsubscribtion
server.On("unsubscribe", func(so socketio.Socket, name string) {
so.Leave(name)
})
ovl.ioserver = server
}
// GetAppDir returns the overlord application directory.
func (ovl *Overlord) GetAppDir() string {
execPath, err := GetExecutablePath()
if err != nil {
log.Fatalln(err)
}
execDir := filepath.Dir(execPath)
appDir, err := filepath.Abs(filepath.Join(execDir, "app"))
if err != nil {
log.Fatalln(err)
}
if _, err := os.Stat(appDir); err != nil {
// Try system install directory
appDir, err = filepath.Abs(filepath.Join(execDir, systemAppDir, "app"))
if err != nil {
log.Fatalln(err)
}
if _, err := os.Stat(appDir); err != nil {
log.Fatalln("Can not find app directory")
}
}
return appDir
}
// GetAppNames return the name of overlord apps.
func (ovl *Overlord) GetAppNames(ignoreSpecial bool) ([]string, error) {
var appNames []string
isSpecial := func(target string) bool {
for _, name := range []string{"common", "upgrade", "third_party"} {
if name == target {
return true
}
}
return false
}
apps, err := ioutil.ReadDir(ovl.GetAppDir())
if err != nil {
return nil, nil
}
for _, app := range apps {
if !app.IsDir() || (ignoreSpecial && isSpecial(app.Name())) {
continue
}
appNames = append(appNames, app.Name())
}
return appNames, nil
}
type byMid []map[string]interface{}
func (a byMid) Len() int { return len(a) }
func (a byMid) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byMid) Less(i, j int) bool {
return a[i]["mid"].(string) < a[j]["mid"].(string)
}
// RegisterHTTPHandlers register handlers for http routes.
func (ovl *Overlord) RegisterHTTPHandlers() {
var upgrader = websocket.Upgrader{
ReadBufferSize: bufferSize,
WriteBufferSize: bufferSize,
Subprotocols: []string{"binary"},
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// Helper function for writing error message to WebSocket
WebSocketSendError := func(ws *websocket.Conn, err string) {
log.Println(err)
msg := websocket.FormatCloseMessage(websocket.CloseProtocolError, err)
ws.WriteMessage(websocket.CloseMessage, msg)
ws.Close()
}
// List all apps available on Overlord.
AppsListHandler := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
apps, err := ovl.GetAppNames(true)
if err != nil {
w.Write([]byte(fmt.Sprintf(`{"error": "%s"}`, err)))
}
result, err := json.Marshal(map[string][]string{"apps": apps})
if err != nil {
w.Write([]byte(fmt.Sprintf(`{"error": "%s"}`, err)))
} else {
w.Write(result)
}
}
// List all agents connected to the Overlord.
AgentsListHandler := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var data = make([]map[string]interface{}, 0)
ovl.agentsMu.Lock()
for _, agent := range ovl.agents {
data = append(data, map[string]interface{}{
"mid": agent.Mid,
"sid": agent.Sid,
"properties": agent.Properties,
})
}
ovl.agentsMu.Unlock()
sort.Sort(byMid(data))
result, err := json.Marshal(data)
if err != nil {
w.Write([]byte(fmt.Sprintf(`{"error": "%s"}`, err)))
} else {
w.Write(result)
}
}
// Agent upgrade request handler.
AgentsUpgradeHandler := func(w http.ResponseWriter, r *http.Request) {
ovl.agentsMu.Lock()
for _, agent := range ovl.agents {
err := agent.SendUpgradeRequest()
if err != nil {
w.Write([]byte(fmt.Sprintf("Failed to send upgrade request for `%s'.\n",
agent.Mid)))
}
}
ovl.agentsMu.Unlock()
w.Write([]byte(`{"status": "success"}`))
}
// List all logcat clients connected to the Overlord.
LogcatsListHandler := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var data = make([]map[string]interface{}, 0)
ovl.logcatsMu.Lock()
for mid, logcats := range ovl.logcats {
var sids []string
for sid := range logcats {
sids = append(sids, sid)
}
data = append(data, map[string]interface{}{
"mid": mid,
"sids": sids,
})
}
ovl.logcatsMu.Unlock()
result, err := json.Marshal(data)
if err != nil {
w.Write([]byte(fmt.Sprintf(`{"error": "%s"}`, err)))
} else {
w.Write(result)
}
}
// Logcat request handler.
// We directly send the WebSocket connection to ConnServer for forwarding
// the log stream.
LogcatHandler := func(w http.ResponseWriter, r *http.Request) {
log.Printf("Logcat request from %s\n", r.RemoteAddr)
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
vars := mux.Vars(r)
mid := vars["mid"]
sid := vars["sid"]
ovl.logcatsMu.Lock()
if logcats, ok := ovl.logcats[mid]; ok {
ovl.logcatsMu.Unlock()
if logcat, ok := logcats[sid]; ok {
logcat.Command <- ConnectLogcatCmd{conn}
} else {
WebSocketSendError(conn, "No client with sid "+sid)
}
} else {
ovl.logcatsMu.Unlock()
WebSocketSendError(conn, "No client with mid "+mid)
}
}
// TTY stream request handler.
// We first create a webSocketContext to store the connection, then send a
// command to Overlord to client to spawn a terminal connection.
AgentTtyHandler := func(w http.ResponseWriter, r *http.Request) {
log.Printf("Terminal request from %s\n", r.RemoteAddr)
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
var ttyDevice string
vars := mux.Vars(r)
mid := vars["mid"]
if _ttyDevice, ok := r.URL.Query()["tty_device"]; ok {
ttyDevice = _ttyDevice[0]
}
ovl.agentsMu.Lock()
if agent, ok := ovl.agents[mid]; ok {
ovl.agentsMu.Unlock()
wc := newWebsocketContext(conn)
ovl.AddWebsocketContext(wc)
agent.Command <- SpawnTerminalCmd{wc.Sid, ttyDevice}
if res := <-agent.Response; res != "" {
WebSocketSendError(conn, res)
}
} else {
ovl.agentsMu.Unlock()
WebSocketSendError(conn, "No client with mid "+mid)
}
}
// Shell command request handler.
// We first create a webSocketContext to store the connection, then send a
// command to ConnServer to client to spawn a shell connection.
ModeShellHandler := func(w http.ResponseWriter, r *http.Request) {
log.Printf("Shell request from %s\n", r.RemoteAddr)
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
vars := mux.Vars(r)
mid := vars["mid"]
ovl.agentsMu.Lock()
if agent, ok := ovl.agents[mid]; ok {
ovl.agentsMu.Unlock()
if command, ok := r.URL.Query()["command"]; ok {
wc := newWebsocketContext(conn)
ovl.AddWebsocketContext(wc)
agent.Command <- SpawnShellCmd{wc.Sid, command[0]}
if res := <-agent.Response; res != "" {
WebSocketSendError(conn, res)
}
} else {
WebSocketSendError(conn, "No command specified for shell request "+mid)
}
} else {
ovl.agentsMu.Unlock()
WebSocketSendError(conn, "No client with mid "+mid)
}
}
// Get agent properties as JSON.
AgentPropertiesHandler := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
vars := mux.Vars(r)
mid := vars["mid"]
ovl.agentsMu.Lock()
if agent, ok := ovl.agents[mid]; ok {
ovl.agentsMu.Unlock()
jsonResult, err := json.Marshal(agent.Properties)
if err != nil {
w.Write([]byte(fmt.Sprintf(`{"error": "%s"}`, err)))
return
}
w.Write(jsonResult)
} else {
ovl.agentsMu.Unlock()
w.Write([]byte(fmt.Sprintf(`{"error": "No client with mid %s"}`, mid)))
}
}
// Helper function for serving file and write it into response body.
serveFileHTTP := func(w http.ResponseWriter, c *ConnServer) {
defer func() {
if c != nil {
c.StopListen()
}
}()
c.SendClearToDownload()
dispose := fmt.Sprintf("attachment; filename=\"%s\"", c.Download.Name)
w.Header().Set("Content-Disposition", dispose)
w.Header().Set("Content-Length", strconv.FormatInt(c.Download.Size, 10))
for {
data := <-c.Download.Data
if data == nil {
return
}
if _, err := w.Write(data); err != nil {
break
}
}
}
// File download request handler.
// Handler for file download request, the filename target machine is
// specified in the request URL.
AgentDownloadHandler := func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
mid := vars["mid"]
var agent *ConnServer
var ok bool
ovl.agentsMu.Lock()
if agent, ok = ovl.agents[mid]; !ok {
ovl.agentsMu.Unlock()
http.NotFound(w, r)
return
}
ovl.agentsMu.Unlock()
var filename []string
if filename, ok = r.URL.Query()["filename"]; !ok {
http.NotFound(w, r)
return
}
sid := uuid.Must(uuid.NewV4()).String()
agent.Command <- SpawnFileCmd{
Sid: sid, Action: "download", Filename: filename[0]}
res := <-agent.Response
if res != "" {
http.Error(w, fmt.Sprintf(`{"error": "%s"}`, res),
http.StatusBadRequest)
return
}
var c *ConnServer
const maxTries = 100 // 20 seconds
count := 0
// Wait until download client connects
for {
if count++; count == maxTries {
http.NotFound(w, r)
return
}
ovl.downloadsMu.Lock()
if c, ok = ovl.downloads[sid]; ok {
ovl.downloadsMu.Unlock()
break
}
ovl.downloadsMu.Unlock()
time.Sleep(200 * time.Millisecond)
}
serveFileHTTP(w, c)
}
// Passive file download request handler.
// This handler deal with requests that are initiated by the client. We
// simply check if the session id exists in the download client list, than
// start to download the file if it does.
FileDownloadHandler := func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
sid := vars["sid"]
var c *ConnServer
var ok bool
ovl.downloadsMu.Lock()
if c, ok = ovl.downloads[sid]; !ok {
ovl.downloadsMu.Unlock()
http.NotFound(w, r)
return
}
ovl.downloadsMu.Unlock()
serveFileHTTP(w, c)
}
// File upload request handler.
AgentUploadHandler := func(w http.ResponseWriter, r *http.Request) {
var ok bool
var err error
var agent *ConnServer
var errMsg string
defer func() {
if errMsg != "" {
http.Error(w, fmt.Sprintf(`{"error": "%s"}`, errMsg),
http.StatusBadRequest)
}
}()
vars := mux.Vars(r)
mid := vars["mid"]
ovl.agentsMu.Lock()
if agent, ok = ovl.agents[mid]; !ok {
ovl.agentsMu.Unlock()
errMsg = fmt.Sprintf("No client with mid %s", mid)
return
}
ovl.agentsMu.Unlock()
// Target terminal session ID
var terminalSids []string
if terminalSids, ok = r.URL.Query()["terminal_sid"]; !ok {
terminalSids = []string{""}
}
sid := uuid.Must(uuid.NewV4()).String()
// Upload destination
var dsts []string
if dsts, ok = r.URL.Query()["dest"]; !ok {
dsts = []string{""}
}
// Upload destination
var perm int64
if perms, ok := r.URL.Query()["perm"]; ok {
if perm, err = strconv.ParseInt(perms[0], 8, 32); err != nil {
errMsg = err.Error()
return
}
}
// Check only
if r.Method == "GET" {
// If we are checking only, we need a extra filename parameters since
// we don't have a form to supply the filename.
var filenames []string
if filenames, ok = r.URL.Query()["filename"]; !ok {
filenames = []string{""}
}
agent.Command <- SpawnFileCmd{sid, terminalSids[0], "upload",
filenames[0], dsts[0], int(perm), true}
errMsg = <-agent.Response
return
}
mr, err := r.MultipartReader()
if err != nil {
errMsg = err.Error()
return
}
p, err := mr.NextPart()
if err != nil {
errMsg = err.Error()
return
}
agent.Command <- SpawnFileCmd{sid, terminalSids[0], "upload",
p.FileName(), dsts[0], int(perm), false}
res := <-agent.Response
if res != "" {
http.NotFound(w, r)
return
}
const maxTries = 100 // 20 seconds
count := 0
// Wait until upload client connects
var c *ConnServer
for {
if count++; count == maxTries {
http.Error(w, "no response from client", http.StatusInternalServerError)
return
}
ovl.uploadsMu.Lock()
if c, ok = ovl.uploads[sid]; ok {
ovl.uploadsMu.Unlock()
break
}
ovl.uploadsMu.Unlock()
time.Sleep(200 * time.Millisecond)
}
_, err = io.Copy(c.Conn, p)
c.StopListen()
if err != nil {
errMsg = err.Error()
return
}
w.Write([]byte(`{"status": "success"}`))
return
}
// Port forwarding request handler
AgentModeForwardHandler := func(w http.ResponseWriter, r *http.Request) {
log.Printf("ModeForward request from %s\n", r.RemoteAddr)
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
var port int
vars := mux.Vars(r)
mid := vars["mid"]
if _port, ok := r.URL.Query()["port"]; ok {
if port, err = strconv.Atoi(_port[0]); err != nil {
WebSocketSendError(conn, "invalid port")
return
}
} else {
WebSocketSendError(conn, "no port specified")
return
}
ovl.agentsMu.Lock()
if agent, ok := ovl.agents[mid]; ok {
ovl.agentsMu.Unlock()
wc := newWebsocketContext(conn)
ovl.AddWebsocketContext(wc)
agent.Command <- SpawnModeForwarderCmd{wc.Sid, port}
if res := <-agent.Response; res != "" {
WebSocketSendError(conn, res)
}
} else {
ovl.agentsMu.Unlock()
WebSocketSendError(conn, "No client with mid "+mid)
}
}
// Initialize socket IO server
ovl.InitSocketIOServer()
appDir := ovl.GetAppDir()
// HTTP basic auth
auth := NewBasicAuth("Overlord", ovl.htpasswdPath, !ovl.auth)
// Register the request handlers.
r := mux.NewRouter()
r.HandleFunc("/api/apps/list", AppsListHandler)
r.HandleFunc("/api/agents/list", AgentsListHandler)
r.HandleFunc("/api/agents/upgrade", AgentsUpgradeHandler)
r.HandleFunc("/api/logcats/list", LogcatsListHandler)
// Logcat methods
r.HandleFunc("/api/log/{mid}/{sid}", LogcatHandler)
// Agent methods
r.HandleFunc("/api/agent/tty/{mid}", AgentTtyHandler)
r.HandleFunc("/api/agent/shell/{mid}", ModeShellHandler)
r.HandleFunc("/api/agent/properties/{mid}", AgentPropertiesHandler)
r.HandleFunc("/api/agent/download/{mid}", AgentDownloadHandler)
r.HandleFunc("/api/agent/upload/{mid}", AgentUploadHandler)
r.HandleFunc("/api/agent/forward/{mid}", AgentModeForwardHandler)
// File methods
r.HandleFunc("/api/file/download/{sid}", FileDownloadHandler)
http.Handle("/api/", auth.WrapHandler(r))
http.Handle("/api/socket.io/", auth.WrapHandler(ovl.ioserver))
// /upgrade/ does not need authenticiation
http.Handle("/upgrade/", http.StripPrefix("/upgrade/",
http.FileServer(http.Dir(filepath.Join(appDir, "upgrade")))))
http.Handle("/vendor/", auth.WrapHandler(http.FileServer(
http.Dir(filepath.Join(appDir, "common")))))
http.Handle("/", auth.WrapHandler(http.FileServer(
http.Dir(filepath.Join(appDir, "dashboard")))))
// Serve all apps
appNames, err := ovl.GetAppNames(false)
if err != nil {
log.Fatalln(err)
}
for _, app := range appNames {
if app == "upgrade" {
continue
}
if app != "common" && app != "third_party" {
log.Printf("App `%s' registered\n", app)
}
prefix := fmt.Sprintf("/%s/", app)
http.Handle(prefix, http.StripPrefix(prefix,
auth.WrapHandler(http.FileServer(http.Dir(filepath.Join(appDir, app))))))
}
}
// ServHTTP is the Web server main routine.
func (ovl *Overlord) ServHTTP(port int) {
var err error
tlsStatus := "disabled"
if ovl.certs != nil {
tlsStatus = "enabled"
}
addr := fmt.Sprintf("%s:%d", webServerHost, port)
log.Printf("HTTP server started, listening at %s, TLS: %s",
addr, tlsStatus)
if ovl.certs != nil {
err = http.ListenAndServeTLS(addr, ovl.certs.Cert,
ovl.certs.Key, nil)
} else {
err = http.ListenAndServe(addr, nil)
}
if err != nil {
log.Fatalf("net.http could not listen on address '%s': %s\n",
addr, err)
}
}
// StartUDPBroadcast is the main routine for broadcasting LAN discovery message.
func (ovl *Overlord) StartUDPBroadcast(port int) {
ifaceIP := ""
bcastIP := net.IPv4bcast.String()
if ovl.lanDiscInterface != "" {
interfaces, err := net.Interfaces()
if err != nil {
panic(err)
}
outter:
for _, iface := range interfaces {
if iface.Name == ovl.lanDiscInterface {
addrs, err := iface.Addrs()
if err != nil {
panic(err)
}
for _, addr := range addrs {
ip, ipnet, err := net.ParseCIDR(addr.String())
if err != nil {
continue
}
// Calculate broadcast IP
ip4 := ip.To4()
// We only care about IPv4 address
if ip4 == nil {
continue
}
bcastIPraw := make(net.IP, 4)
for i := 0; i < 4; i++ {
bcastIPraw[i] = ip4[i] | ^ipnet.Mask[i]
}
ifaceIP = ip.String()
bcastIP = bcastIPraw.String()
break outter
}
}
}
if ifaceIP == "" {
log.Fatalf("can not found any interface with name %s\n",
ovl.lanDiscInterface)
}
}
addr := fmt.Sprintf("%s:%d", bcastIP, port)
conn, err := net.Dial("udp", addr)
if err != nil {
log.Fatalln("Unable to start UDP broadcast:", err)
}
log.Printf("UDP Broadcasting started, broadcasting at %s", addr)
ticker := time.NewTicker(time.Duration(ldInterval * time.Second))
for {
select {
case <-ticker.C:
conn.Write([]byte(fmt.Sprintf("OVERLORD %s:%d", ifaceIP, OverlordPort)))
}
}
}
// Serv is the main routine for starting all the overlord sub-server.
func (ovl *Overlord) Serv() {
ovl.RegisterHTTPHandlers()
go ovl.ServSocket(OverlordPort)
go ovl.ServHTTP(OverlordHTTPPort)
if ovl.lanDisc {
go ovl.StartUDPBroadcast(OverlordLDPort)
}
ticker := time.NewTicker(time.Duration(60 * time.Second))
for {
select {
case <-ticker.C:
log.Printf("#Goroutines, #Ghostclient: %d, %d\n",
runtime.NumGoroutine(), len(ovl.agents))
}
}
}
// StartOverlord starts the overlord server.
func StartOverlord(lanDiscInterface string, lanDisc bool, auth bool,
certsString string, linkTLS bool, htpasswdPath string) {
ovl := NewOverlord(lanDiscInterface, lanDisc, auth, certsString, linkTLS,
htpasswdPath)
ovl.Serv()
}