2016-08-02 02:58:10 +00:00
package main
import (
2016-10-16 21:50:34 +00:00
"bytes"
2016-11-05 05:25:05 +00:00
"flag"
2016-08-17 00:58:39 +00:00
"fmt"
2016-10-23 18:37:57 +00:00
"github.com/chzyer/readline"
2016-11-05 05:25:05 +00:00
ricochet "github.com/ricochet-im/ricochet-go/core"
2016-10-17 04:26:35 +00:00
rpc "github.com/ricochet-im/ricochet-go/rpc"
2016-08-02 02:58:10 +00:00
"google.golang.org/grpc"
"log"
2016-11-05 05:25:05 +00:00
"net"
2016-09-30 05:17:03 +00:00
"os"
2016-11-05 05:25:05 +00:00
"strings"
"time"
2016-08-02 02:58:10 +00:00
)
2016-11-05 05:25:05 +00:00
var (
LogBuffer bytes . Buffer
2016-08-02 02:58:10 +00:00
2016-11-05 05:25:05 +00:00
// Flags
2016-11-05 22:19:33 +00:00
backendConnect string
backendServer string
2016-11-05 05:25:05 +00:00
unsafeBackend bool
2016-11-05 22:19:33 +00:00
backendMode bool
configPath string = "identity.ricochet"
2016-11-05 05:25:05 +00:00
)
2016-10-16 21:50:34 +00:00
2016-08-17 00:58:39 +00:00
func main ( ) {
2016-11-05 22:19:33 +00:00
flag . Usage = func ( ) {
fmt . Fprintf ( os . Stderr , "Usage:\n" )
fmt . Fprintf ( os . Stderr , " %s [<args>] [<identity>]\n\tStandalone client using <identity> (default \"./identity.ricochet\")\n" , os . Args [ 0 ] )
fmt . Fprintf ( os . Stderr , " %s -listen <address> [<args>] [<identity>]\n\tListen on <address> for Ricochet client frontend connections\n" , os . Args [ 0 ] )
fmt . Fprintf ( os . Stderr , " %s -attach <address> [<args>]\n\tAttach to a client backend running on <address>\n" , os . Args [ 0 ] )
fmt . Fprintf ( os . Stderr , "\nArgs:\n" )
flag . PrintDefaults ( )
fmt . Fprintf ( os . Stderr , "\n" )
}
flag . StringVar ( & configPath , "identity" , configPath , "Load identity from `<file>`" )
flag . StringVar ( & backendConnect , "attach" , "" , "Attach to the client backend running on `<address>`" )
flag . StringVar ( & backendServer , "listen" , "" , "Listen on `<address>` for client frontend connections" )
2016-11-05 05:25:05 +00:00
flag . BoolVar ( & unsafeBackend , "allow-unsafe-backend" , false , "Allow a remote backend address. This is NOT RECOMMENDED and may harm your security or privacy. Do not use without a secure, trusted link" )
2016-11-05 22:19:33 +00:00
flag . BoolVar ( & backendMode , "only-backend" , false , "Run backend without any commandline UI" )
2016-11-05 05:25:05 +00:00
flag . Parse ( )
2016-11-05 22:19:33 +00:00
if len ( flag . Args ( ) ) > 1 {
flag . Usage ( )
os . Exit ( 1 )
} else if len ( flag . Args ( ) ) == 1 {
configPath = flag . Arg ( 0 )
}
// Check for flag combinations that make no sense
if backendConnect != "" {
if backendMode {
fmt . Printf ( "Cannot use -only-backend with -attach, because attach implies not running a backend\n" )
os . Exit ( 1 )
} else if backendServer != "" {
fmt . Printf ( "Cannot use -listen with -attach, because attach implies not running a backend\n" )
os . Exit ( 1 )
} else if configPath != "identity.ricochet" {
fmt . Printf ( "Cannot use -identity with -attach, because identity is stored at the backend\n" )
os . Exit ( 1 )
}
}
// Redirect log before starting backend, unless in backend mode
if ! backendMode {
log . SetOutput ( & LogBuffer )
}
// Unless backendConnect is set, start the in-process backend
if backendConnect == "" {
err := startBackend ( )
if err != nil {
fmt . Printf ( "backend failed: %v\n" , err )
os . Exit ( 1 )
}
if backendMode {
// Block forever; backend uses os.Exit on failure
c := make ( chan struct { } )
<- c
os . Exit ( 0 )
}
}
// Connect to RPC backend
conn , err := connectClientBackend ( )
if err != nil {
fmt . Printf ( "backend connection failed: %v\n" , err )
os . Exit ( 1 )
}
defer conn . Close ( )
2016-11-05 05:25:05 +00:00
// Set up readline
2016-10-26 21:26:21 +00:00
input , err := readline . NewEx ( & readline . Config {
InterruptPrompt : "^C" ,
EOFPrompt : "exit" ,
} )
2016-08-02 23:28:26 +00:00
if err != nil {
2016-10-16 21:50:34 +00:00
fmt . Println ( err )
2016-09-30 05:17:03 +00:00
os . Exit ( 1 )
2016-08-02 23:28:26 +00:00
}
2016-10-16 21:50:34 +00:00
defer input . Close ( )
2016-08-17 00:58:39 +00:00
2016-11-05 05:25:05 +00:00
// Configure client and UI
2016-10-16 04:57:16 +00:00
client := & Client {
2016-08-17 00:58:39 +00:00
Backend : rpc . NewRicochetCoreClient ( conn ) ,
}
2016-10-23 00:50:21 +00:00
Ui = UI {
2016-10-16 04:57:16 +00:00
Input : input ,
2016-10-23 19:42:45 +00:00
Stdout : input . Stdout ( ) ,
2016-10-16 04:57:16 +00:00
Client : client ,
}
2016-08-17 00:58:39 +00:00
2016-11-05 05:25:05 +00:00
// Initialize data from backend and start UI command loop
2016-10-16 21:50:34 +00:00
fmt . Print ( "Connecting to backend...\n" )
go func ( ) {
if err := client . Initialize ( ) ; err != nil {
fmt . Printf ( "Error: %s\n" , err )
os . Exit ( 1 )
}
client . Block ( )
2016-10-23 00:50:21 +00:00
Ui . PrintStatus ( )
2016-10-16 21:50:34 +00:00
client . Unblock ( )
} ( )
2016-08-17 00:58:39 +00:00
2016-10-23 00:50:21 +00:00
Ui . CommandLoop ( )
2016-08-02 02:58:10 +00:00
}
2016-11-05 05:25:05 +00:00
func connectClientBackend ( ) ( * grpc . ClientConn , error ) {
2016-11-05 22:19:33 +00:00
address := backendConnect
if address == "" {
// If not explicitly specified, use the listen address. If that is also empty, default to in-process
address = backendServer
if address == "" {
// In-process backend, using 'InnerNet' as a fake socket
return grpc . Dial ( "ricochet.rpc" , grpc . WithInsecure ( ) , grpc . WithDialer ( DialInnerNet ) )
2016-11-05 05:25:05 +00:00
}
2016-11-05 22:19:33 +00:00
}
2016-11-05 05:25:05 +00:00
2016-11-05 22:19:33 +00:00
// External backend
if strings . HasPrefix ( address , "unix:" ) {
return grpc . Dial ( address [ 5 : ] , grpc . WithInsecure ( ) ,
grpc . WithDialer ( func ( addr string , timeout time . Duration ) ( net . Conn , error ) {
return net . DialTimeout ( "unix" , addr , timeout )
} ) )
} else {
if err := checkBackendAddressSafety ( address ) ; err != nil {
return nil , err
2016-11-05 05:25:05 +00:00
}
2016-11-05 22:19:33 +00:00
return grpc . Dial ( address , grpc . WithInsecure ( ) )
}
}
func checkBackendAddressSafety ( address string ) error {
host , _ , err := net . SplitHostPort ( address )
if err != nil {
return err
}
ip := net . ParseIP ( host )
if ! unsafeBackend && ( ip == nil || ! ip . IsLoopback ( ) ) {
return fmt . Errorf ( "Host '%s' is not a loopback address.\nRead the warnings and use -allow-unsafe-backend for non-local addresses" , host )
2016-11-05 05:25:05 +00:00
}
2016-11-05 22:19:33 +00:00
return nil
2016-11-05 05:25:05 +00:00
}
2016-11-05 22:19:33 +00:00
func startBackend ( ) error {
config , err := ricochet . LoadConfig ( configPath )
2016-11-05 05:25:05 +00:00
if err != nil {
2016-11-05 22:19:33 +00:00
return err
2016-11-05 05:25:05 +00:00
}
core := new ( ricochet . Ricochet )
if err := core . Init ( config ) ; err != nil {
2016-11-05 22:19:33 +00:00
return err
2016-11-05 05:25:05 +00:00
}
2016-11-05 22:19:33 +00:00
var listener net . Listener
if backendServer == "" {
// In-process backend, using 'InnerNet' as a fake socket
listener , err = ListenInnerNet ( "ricochet.rpc" )
} else {
if strings . HasPrefix ( backendServer , "unix:" ) {
// XXX Need the right behavior for cleaning up old sockets, permissions, etc
listener , err = net . Listen ( "unix" , backendServer [ 5 : ] )
} else {
if err := checkBackendAddressSafety ( backendServer ) ; err != nil {
return err
}
listener , err = net . Listen ( "tcp" , backendServer )
}
}
2016-11-05 05:25:05 +00:00
if err != nil {
2016-11-05 22:19:33 +00:00
return err
2016-11-05 05:25:05 +00:00
}
server := & ricochet . RpcServer {
Core : core ,
}
go func ( ) {
grpcServer := grpc . NewServer ( )
rpc . RegisterRicochetCoreServer ( grpcServer , server )
err := grpcServer . Serve ( listener )
if err != nil {
log . Printf ( "backend exited: %v" , err )
os . Exit ( 1 )
}
} ( )
2016-11-05 22:19:33 +00:00
return nil
2016-11-05 05:25:05 +00:00
}