2014-03-29 01:56:04 +08:00
// Read, write and edit the config file
2014-03-16 00:06:11 +08:00
package fs
import (
"bufio"
2016-02-16 23:25:27 +08:00
"bytes"
2016-08-14 19:04:43 +08:00
"crypto/aes"
"crypto/cipher"
2016-02-16 23:25:27 +08:00
"crypto/rand"
"crypto/sha256"
2015-09-02 05:33:34 +08:00
"encoding/base64"
2014-03-16 00:06:11 +08:00
"fmt"
2016-02-16 23:25:27 +08:00
"io"
"io/ioutil"
2014-03-16 00:06:11 +08:00
"log"
2017-07-23 23:10:23 +08:00
"net"
2014-03-16 00:06:11 +08:00
"os"
"os/user"
2017-01-14 11:47:55 +08:00
"path/filepath"
2016-12-21 02:03:09 +08:00
"regexp"
2014-03-16 00:06:11 +08:00
"sort"
"strconv"
"strings"
"time"
2016-02-16 23:25:27 +08:00
"unicode/utf8"
2014-03-16 00:06:11 +08:00
2016-02-17 18:45:05 +08:00
"github.com/Unknwon/goconfig"
2016-06-12 22:06:02 +08:00
"github.com/pkg/errors"
2017-02-10 05:22:46 +08:00
"github.com/spf13/pflag"
2016-02-16 23:25:27 +08:00
"golang.org/x/crypto/nacl/secretbox"
"golang.org/x/text/unicode/norm"
2014-03-16 00:06:11 +08:00
)
const (
2017-01-14 11:47:55 +08:00
configFileName = "rclone.conf"
hiddenConfigFileName = "." + configFileName
2016-01-07 23:20:32 +08:00
// ConfigToken is the key used to store the token under
ConfigToken = "token"
// ConfigClientID is the config key used to store the client id
ConfigClientID = "client_id"
// ConfigClientSecret is the config key used to store the client secret
ConfigClientSecret = "client_secret"
2017-06-09 03:35:32 +08:00
// ConfigAuthURL is the config key used to store the auth server endpoint
ConfigAuthURL = "auth_url"
// ConfigTokenURL is the config key used to store the token server endpoint
ConfigTokenURL = "token_url"
2016-01-07 23:20:32 +08:00
// ConfigAutomatic indicates that we want non-interactive configuration
ConfigAutomatic = "config_automatic"
2014-03-16 00:06:11 +08:00
)
// Global
var (
2016-12-21 02:03:09 +08:00
// configData is the config file data structure
configData * goconfig . ConfigFile
2015-09-23 01:47:16 +08:00
// ConfigPath points to the config file
2017-01-14 11:47:55 +08:00
ConfigPath = makeConfigPath ( )
2015-09-23 01:47:16 +08:00
// Config is the global config
2014-03-16 00:06:11 +08:00
Config = & ConfigInfo { }
// Flags
2017-09-11 14:26:53 +08:00
verbose = CountP ( "verbose" , "v" , "Print lots more stuff (repeat for more)" )
quiet = BoolP ( "quiet" , "q" , false , "Print as little stuff as possible" )
modifyWindow = DurationP ( "modify-window" , "" , time . Nanosecond , "Max time diff to be considered the same" )
checkers = IntP ( "checkers" , "" , 8 , "Number of checkers to run in parallel." )
transfers = IntP ( "transfers" , "" , 4 , "Number of file transfers to run in parallel." )
configFile = StringP ( "config" , "" , ConfigPath , "Config file." )
checkSum = BoolP ( "checksum" , "c" , false , "Skip based on checksum & size, not mod-time & size" )
sizeOnly = BoolP ( "size-only" , "" , false , "Skip based on size only, not mod-time or checksum" )
ignoreTimes = BoolP ( "ignore-times" , "I" , false , "Don't skip files that match size and time - transfer all files" )
ignoreExisting = BoolP ( "ignore-existing" , "" , false , "Skip all files that exist on destination" )
dryRun = BoolP ( "dry-run" , "n" , false , "Do a trial run with no permanent changes" )
connectTimeout = DurationP ( "contimeout" , "" , 60 * time . Second , "Connect timeout" )
timeout = DurationP ( "timeout" , "" , 5 * 60 * time . Second , "IO idle timeout" )
dumpHeaders = BoolP ( "dump-headers" , "" , false , "Dump HTTP headers - may contain sensitive info" )
dumpBodies = BoolP ( "dump-bodies" , "" , false , "Dump HTTP headers and bodies - may contain sensitive info" )
dumpAuth = BoolP ( "dump-auth" , "" , false , "Dump HTTP headers with auth info" )
skipVerify = BoolP ( "no-check-certificate" , "" , false , "Do not verify the server SSL certificate. Insecure." )
AskPassword = BoolP ( "ask-password" , "" , true , "Allow prompt for password for encrypted configuration." )
deleteBefore = BoolP ( "delete-before" , "" , false , "When synchronizing, delete files on destination before transfering" )
deleteDuring = BoolP ( "delete-during" , "" , false , "When synchronizing, delete files during transfer (default)" )
deleteAfter = BoolP ( "delete-after" , "" , false , "When synchronizing, delete files on destination after transfering" )
trackRenames = BoolP ( "track-renames" , "" , false , "When synchronizing, track file renames and do a server side move if possible" )
lowLevelRetries = IntP ( "low-level-retries" , "" , 10 , "Number of low level retries to do." )
updateOlder = BoolP ( "update" , "u" , false , "Skip files that are newer on the destination." )
noGzip = BoolP ( "no-gzip-encoding" , "" , false , "Don't set Accept-Encoding: gzip." )
maxDepth = IntP ( "max-depth" , "" , - 1 , "If set limits the recursion depth to this." )
ignoreSize = BoolP ( "ignore-size" , "" , false , "Ignore size when skipping use mod-time or checksum." )
ignoreChecksum = BoolP ( "ignore-checksum" , "" , false , "Skip post copy check of checksums." )
noTraverse = BoolP ( "no-traverse" , "" , false , "Don't traverse destination file system on copy." )
noUpdateModTime = BoolP ( "no-update-modtime" , "" , false , "Don't update destination mod-time if files identical." )
backupDir = StringP ( "backup-dir" , "" , "" , "Make backups into hierarchy based in DIR." )
suffix = StringP ( "suffix" , "" , "" , "Suffix for use with --backup-dir." )
useListR = BoolP ( "fast-list" , "" , false , "Use recursive list if available. Uses more memory but fewer transactions." )
tpsLimit = Float64P ( "tpslimit" , "" , 0 , "Limit HTTP transactions per second to this." )
tpsLimitBurst = IntP ( "tpslimit-burst" , "" , 1 , "Max burst of transactions for --tpslimit." )
bindAddr = StringP ( "bind" , "" , "" , "Local address to bind to for outgoing connections, IPv4, IPv6 or name." )
disableFeatures = StringP ( "disable" , "" , "" , "Disable a comma separated list of features. Use help to see a list." )
userAgent = StringP ( "user-agent" , "" , "rclone/" + Version , "Set the user-agent to a specified string. The default is rclone/ version" )
streamingUploadCutoff = SizeSuffix ( 100 * 1024 )
logLevel = LogLevelNotice
statsLogLevel = LogLevelInfo
bwLimit BwTimetable
bufferSize SizeSuffix = 16 << 20
2016-02-16 23:25:27 +08:00
// Key to use for password en/decryption.
// When nil, no encryption will be used for saving.
configKey [ ] byte
2014-03-16 00:06:11 +08:00
)
2015-02-20 03:26:00 +08:00
func init ( ) {
2017-06-27 05:46:45 +08:00
VarP ( & logLevel , "log-level" , "" , "Log level DEBUG|INFO|NOTICE|ERROR" )
VarP ( & statsLogLevel , "stats-log-level" , "" , "Log level to show --stats output DEBUG|INFO|NOTICE|ERROR" )
2016-12-20 23:50:46 +08:00
VarP ( & bwLimit , "bwlimit" , "" , "Bandwidth limit in kBytes/s, or use suffix b|k|M|G or a full timetable." )
2017-02-15 03:31:33 +08:00
VarP ( & bufferSize , "buffer-size" , "" , "Buffer size when copying files." )
2017-09-11 14:26:53 +08:00
VarP ( & streamingUploadCutoff , "streaming-upload-cutoff" , "" , "Cutoff for switching to chunked upload if file size is unknown. Upload starts after reaching cutoff or when file ends." )
2017-01-03 10:52:41 +08:00
}
2016-08-14 19:04:43 +08:00
// crypt internals
var (
cryptKey = [ ] byte {
0x9c , 0x93 , 0x5b , 0x48 , 0x73 , 0x0a , 0x55 , 0x4d ,
0x6b , 0xfd , 0x7c , 0x63 , 0xc8 , 0x86 , 0xa9 , 0x2b ,
0xd3 , 0x90 , 0x19 , 0x8e , 0xb8 , 0x12 , 0x8a , 0xfb ,
0xf4 , 0xde , 0x16 , 0x2b , 0x8b , 0x95 , 0xf6 , 0x38 ,
}
cryptBlock cipher . Block
cryptRand = rand . Reader
)
// crypt transforms in to out using iv under AES-CTR.
//
// in and out may be the same buffer.
//
// Note encryption and decryption are the same operation
func crypt ( out , in , iv [ ] byte ) error {
if cryptBlock == nil {
var err error
cryptBlock , err = aes . NewCipher ( cryptKey )
if err != nil {
return err
}
}
stream := cipher . NewCTR ( cryptBlock , iv )
stream . XORKeyStream ( out , in )
return nil
}
// Obscure a value
//
// This is done by encrypting with AES-CTR
func Obscure ( x string ) ( string , error ) {
plaintext := [ ] byte ( x )
ciphertext := make ( [ ] byte , aes . BlockSize + len ( plaintext ) )
iv := ciphertext [ : aes . BlockSize ]
if _ , err := io . ReadFull ( cryptRand , iv ) ; err != nil {
return "" , errors . Wrap ( err , "failed to read iv" )
}
if err := crypt ( ciphertext [ aes . BlockSize : ] , plaintext , iv ) ; err != nil {
return "" , errors . Wrap ( err , "encrypt failed" )
2015-09-02 05:33:34 +08:00
}
2016-08-14 19:04:43 +08:00
return base64 . RawURLEncoding . EncodeToString ( ciphertext ) , nil
2015-09-02 05:33:34 +08:00
}
2016-08-14 19:04:43 +08:00
// MustObscure obscures a value, exiting with a fatal error if it failed
func MustObscure ( x string ) string {
out , err := Obscure ( x )
2015-09-02 05:33:34 +08:00
if err != nil {
2016-08-14 19:04:43 +08:00
log . Fatalf ( "Obscure failed: %v" , err )
2015-09-02 05:33:34 +08:00
}
2016-08-14 19:04:43 +08:00
return out
}
// Reveal an obscured value
func Reveal ( x string ) ( string , error ) {
ciphertext , err := base64 . RawURLEncoding . DecodeString ( x )
if err != nil {
return "" , errors . Wrap ( err , "base64 decode failed" )
}
if len ( ciphertext ) < aes . BlockSize {
return "" , errors . New ( "input too short" )
}
buf := ciphertext [ aes . BlockSize : ]
iv := ciphertext [ : aes . BlockSize ]
if err := crypt ( buf , buf , iv ) ; err != nil {
return "" , errors . Wrap ( err , "decrypt failed" )
}
return string ( buf ) , nil
}
// MustReveal reveals an obscured value, exiting with a fatal error if it failed
func MustReveal ( x string ) string {
out , err := Reveal ( x )
if err != nil {
log . Fatalf ( "Reveal failed: %v" , err )
2015-09-02 05:33:34 +08:00
}
2016-08-14 19:04:43 +08:00
return out
2015-09-02 05:33:34 +08:00
}
2015-09-23 01:47:16 +08:00
// ConfigInfo is filesystem config options
2014-03-16 00:06:11 +08:00
type ConfigInfo struct {
2017-09-11 14:26:53 +08:00
LogLevel LogLevel
StatsLogLevel LogLevel
DryRun bool
CheckSum bool
SizeOnly bool
IgnoreTimes bool
IgnoreExisting bool
ModifyWindow time . Duration
Checkers int
Transfers int
ConnectTimeout time . Duration // Connect timeout
Timeout time . Duration // Data channel timeout
DumpHeaders bool
DumpBodies bool
DumpAuth bool
Filter * Filter
InsecureSkipVerify bool // Skip server certificate verification
DeleteMode DeleteMode
TrackRenames bool // Track file renames.
LowLevelRetries int
UpdateOlder bool // Skip files that are newer on the destination
NoGzip bool // Disable compression
MaxDepth int
IgnoreSize bool
IgnoreChecksum bool
NoTraverse bool
NoUpdateModTime bool
DataRateUnit string
BackupDir string
Suffix string
UseListR bool
BufferSize SizeSuffix
TPSLimit float64
TPSLimitBurst int
BindAddr net . IP
DisableFeatures [ ] string
StreamingUploadCutoff SizeSuffix
2015-05-10 18:25:54 +08:00
}
2017-01-14 11:47:55 +08:00
// Return the path to the configuration file
func makeConfigPath ( ) string {
// Find user's home directory
2014-03-16 01:01:13 +08:00
usr , err := user . Current ( )
2017-01-14 11:47:55 +08:00
var homedir string
2014-12-13 03:18:23 +08:00
if err == nil {
2017-01-14 11:47:55 +08:00
homedir = usr . HomeDir
} else {
// Fall back to reading $HOME - work around user.Current() not
// working for cross compiled binaries on OSX.
// https://github.com/golang/go/issues/6376
homedir = os . Getenv ( "HOME" )
2014-03-16 01:01:13 +08:00
}
2017-01-14 11:47:55 +08:00
// Possibly find the user's XDG config paths
// See XDG Base Directory specification
// https://specifications.freedesktop.org/basedir-spec/latest/
xdgdir := os . Getenv ( "XDG_CONFIG_HOME" )
var xdgcfgdir string
if xdgdir != "" {
xdgcfgdir = filepath . Join ( xdgdir , "rclone" )
} else if homedir != "" {
xdgdir = filepath . Join ( homedir , ".config" )
xdgcfgdir = filepath . Join ( xdgdir , "rclone" )
}
// Use $XDG_CONFIG_HOME/rclone/rclone.conf if already existing
var xdgconf string
if xdgcfgdir != "" {
xdgconf = filepath . Join ( xdgcfgdir , configFileName )
_ , err := os . Stat ( xdgconf )
if err == nil {
return xdgconf
}
}
// Use $HOME/.rclone.conf if already existing
var homeconf string
if homedir != "" {
homeconf = filepath . Join ( homedir , hiddenConfigFileName )
_ , err := os . Stat ( homeconf )
if err == nil {
return homeconf
}
2014-12-13 03:18:23 +08:00
}
2017-01-14 11:47:55 +08:00
// Try to create $XDG_CONFIG_HOME/rclone/rclone.conf
if xdgconf != "" {
// xdgconf != "" implies xdgcfgdir != ""
err := os . MkdirAll ( xdgcfgdir , os . ModePerm )
if err == nil {
return xdgconf
}
}
// Try to create $HOME/.rclone.conf
if homeconf != "" {
return homeconf
}
// Default to ./.rclone.conf (current working directory)
2017-02-09 19:01:20 +08:00
Errorf ( nil , "Couldn't find home directory or read HOME or XDG_CONFIG_HOME environment variables." )
Errorf ( nil , "Defaulting to storing config in current directory." )
Errorf ( nil , "Use -config flag to workaround." )
Errorf ( nil , "Error was: %v" , err )
2017-01-14 11:47:55 +08:00
return hiddenConfigFileName
2014-03-16 01:01:13 +08:00
}
2017-01-26 03:35:14 +08:00
// DeleteMode describes the possible delete modes in the config
type DeleteMode byte
// DeleteMode constants
const (
DeleteModeOff DeleteMode = iota
DeleteModeBefore
DeleteModeDuring
DeleteModeAfter
DeleteModeOnly
DeleteModeDefault = DeleteModeAfter
)
2015-09-23 01:47:16 +08:00
// LoadConfig loads the config file
2014-03-16 00:06:11 +08:00
func LoadConfig ( ) {
// Read some flags if set
//
// FIXME read these from the config file too
2017-02-10 05:22:46 +08:00
Config . LogLevel = LogLevelNotice
if * verbose >= 2 {
Config . LogLevel = LogLevelDebug
} else if * verbose >= 1 {
Config . LogLevel = LogLevelInfo
}
if * quiet {
if * verbose > 0 {
log . Fatalf ( "Can't set -v and -q" )
}
Config . LogLevel = LogLevelError
}
logLevelFlag := pflag . Lookup ( "log-level" )
if logLevelFlag != nil && logLevelFlag . Changed {
if * verbose > 0 {
log . Fatalf ( "Can't set -v and --log-level" )
}
if * quiet {
log . Fatalf ( "Can't set -q and --log-level" )
}
2017-06-27 05:46:45 +08:00
Config . LogLevel = logLevel
2017-02-10 05:22:46 +08:00
}
2017-06-27 05:46:45 +08:00
Config . StatsLogLevel = statsLogLevel
2014-03-16 00:06:11 +08:00
Config . ModifyWindow = * modifyWindow
Config . Checkers = * checkers
Config . Transfers = * transfers
2014-06-26 22:33:06 +08:00
Config . DryRun = * dryRun
2015-05-10 18:25:54 +08:00
Config . Timeout = * timeout
Config . ConnectTimeout = * connectTimeout
2015-06-03 22:08:27 +08:00
Config . CheckSum = * checkSum
2015-06-06 15:38:45 +08:00
Config . SizeOnly = * sizeOnly
2016-03-23 01:02:27 +08:00
Config . IgnoreTimes = * ignoreTimes
2016-01-05 18:35:36 +08:00
Config . IgnoreExisting = * ignoreExisting
2015-09-09 04:01:26 +08:00
Config . DumpHeaders = * dumpHeaders
Config . DumpBodies = * dumpBodies
2016-11-02 23:53:43 +08:00
Config . DumpAuth = * dumpAuth
2015-10-29 23:42:25 +08:00
Config . InsecureSkipVerify = * skipVerify
2016-01-13 01:38:28 +08:00
Config . LowLevelRetries = * lowLevelRetries
2016-03-01 01:46:40 +08:00
Config . UpdateOlder = * updateOlder
2016-02-15 02:14:41 +08:00
Config . NoGzip = * noGzip
2016-06-03 04:02:44 +08:00
Config . MaxDepth = * maxDepth
2016-06-18 00:20:08 +08:00
Config . IgnoreSize = * ignoreSize
2017-01-06 01:32:42 +08:00
Config . IgnoreChecksum = * ignoreChecksum
2016-06-25 21:28:26 +08:00
Config . NoTraverse = * noTraverse
2016-07-12 17:46:45 +08:00
Config . NoUpdateModTime = * noUpdateModTime
2017-01-11 05:47:03 +08:00
Config . BackupDir = * backupDir
2017-01-20 01:26:29 +08:00
Config . Suffix = * suffix
2017-06-06 23:40:00 +08:00
Config . UseListR = * useListR
2017-07-13 12:11:24 +08:00
Config . TPSLimit = * tpsLimit
Config . TPSLimitBurst = * tpsLimitBurst
2017-02-15 03:31:33 +08:00
Config . BufferSize = bufferSize
2017-09-11 14:26:53 +08:00
Config . StreamingUploadCutoff = streamingUploadCutoff
2014-03-16 00:06:11 +08:00
2016-12-18 18:03:56 +08:00
Config . TrackRenames = * trackRenames
2016-01-12 21:33:03 +08:00
switch {
case * deleteBefore && ( * deleteDuring || * deleteAfter ) ,
* deleteDuring && * deleteAfter :
log . Fatalf ( ` Only one of --delete-before, --delete-during or --delete-after can be used. ` )
2017-01-26 03:35:14 +08:00
case * deleteBefore :
Config . DeleteMode = DeleteModeBefore
case * deleteDuring :
Config . DeleteMode = DeleteModeDuring
case * deleteAfter :
Config . DeleteMode = DeleteModeAfter
default :
Config . DeleteMode = DeleteModeDefault
2016-01-12 21:33:03 +08:00
}
2016-06-18 00:20:08 +08:00
if Config . IgnoreSize && Config . SizeOnly {
log . Fatalf ( ` Can't use --size-only and --ignore-size together. ` )
}
2017-01-20 01:26:29 +08:00
if Config . Suffix != "" && Config . BackupDir == "" {
log . Fatalf ( ` Can only use --suffix with --backup-dir. ` )
}
2017-07-23 23:10:23 +08:00
if * bindAddr != "" {
addrs , err := net . LookupIP ( * bindAddr )
if err != nil {
log . Fatalf ( "--bind: Failed to parse %q as IP address: %v" , * bindAddr , err )
}
if len ( addrs ) != 1 {
log . Fatalf ( "--bind: Expecting 1 IP address for %q but got %d" , * bindAddr , len ( addrs ) )
}
Config . BindAddr = addrs [ 0 ]
}
2017-08-08 04:10:03 +08:00
if * disableFeatures != "" {
if * disableFeatures == "help" {
log . Fatalf ( "Possible backend features are: %s\n" , strings . Join ( new ( Features ) . List ( ) , ", " ) )
}
Config . DisableFeatures = strings . Split ( * disableFeatures , "," )
}
2014-03-16 00:06:11 +08:00
// Load configuration file.
2014-03-16 01:01:13 +08:00
var err error
2017-08-05 18:12:49 +08:00
ConfigPath , err = filepath . Abs ( * configFile )
if err != nil {
ConfigPath = * configFile
}
2016-12-21 02:03:09 +08:00
configData , err = loadConfigFile ( )
2016-12-19 23:04:07 +08:00
if err == errorConfigFileNotFound {
2017-02-09 19:01:20 +08:00
Logf ( nil , "Config file %q not found - using defaults" , ConfigPath )
2016-12-21 02:03:09 +08:00
configData , _ = goconfig . LoadFromReader ( & bytes . Buffer { } )
2016-12-19 23:04:07 +08:00
} else if err != nil {
log . Fatalf ( "Failed to load config file %q: %v" , ConfigPath , err )
2014-03-16 00:06:11 +08:00
}
2015-02-20 03:26:00 +08:00
2015-09-27 23:13:20 +08:00
// Load filters
Config . Filter , err = NewFilter ( )
if err != nil {
log . Fatalf ( "Failed to load filters: %v" , err )
}
2015-02-20 03:26:00 +08:00
// Start the token bucket limiter
startTokenBucket ( )
2017-01-03 10:52:41 +08:00
// Start the bandwidth update ticker
startTokenTicker ( )
2017-07-13 12:11:24 +08:00
// Start the transactions per second limiter
startHTTPTokenBucket ( )
2014-03-16 00:06:11 +08:00
}
2016-12-19 23:04:07 +08:00
var errorConfigFileNotFound = errors . New ( "config file not found" )
2016-02-16 23:25:27 +08:00
// loadConfigFile will load a config file, and
// automatically decrypt it.
func loadConfigFile ( ) ( * goconfig . ConfigFile , error ) {
b , err := ioutil . ReadFile ( ConfigPath )
if err != nil {
2016-12-19 23:04:07 +08:00
if os . IsNotExist ( err ) {
return nil , errorConfigFileNotFound
}
return nil , err
2016-02-16 23:25:27 +08:00
}
// Find first non-empty line
r := bufio . NewReader ( bytes . NewBuffer ( b ) )
for {
line , _ , err := r . ReadLine ( )
if err != nil {
if err == io . EOF {
2016-02-17 18:45:05 +08:00
return goconfig . LoadFromReader ( bytes . NewBuffer ( b ) )
2016-02-16 23:25:27 +08:00
}
return nil , err
}
l := strings . TrimSpace ( string ( line ) )
if len ( l ) == 0 || strings . HasPrefix ( l , ";" ) || strings . HasPrefix ( l , "#" ) {
continue
}
// First non-empty or non-comment must be ENCRYPT_V0
if l == "RCLONE_ENCRYPT_V0:" {
break
}
if strings . HasPrefix ( l , "RCLONE_ENCRYPT_V" ) {
2016-06-12 22:06:02 +08:00
return nil , errors . New ( "unsupported configuration encryption - update rclone for support" )
2016-02-16 23:25:27 +08:00
}
2016-02-17 18:45:05 +08:00
return goconfig . LoadFromReader ( bytes . NewBuffer ( b ) )
2016-02-16 23:25:27 +08:00
}
// Encrypted content is base64 encoded.
dec := base64 . NewDecoder ( base64 . StdEncoding , r )
box , err := ioutil . ReadAll ( dec )
if err != nil {
2016-06-12 22:06:02 +08:00
return nil , errors . Wrap ( err , "failed to load base64 encoded data" )
2016-02-16 23:25:27 +08:00
}
if len ( box ) < 24 + secretbox . Overhead {
2016-06-12 22:06:02 +08:00
return nil , errors . New ( "Configuration data too short" )
2016-02-16 23:25:27 +08:00
}
envpw := os . Getenv ( "RCLONE_CONFIG_PASS" )
var out [ ] byte
for {
if len ( configKey ) == 0 && envpw != "" {
2016-08-15 00:16:06 +08:00
err := setConfigPassword ( envpw )
2016-02-16 23:25:27 +08:00
if err != nil {
fmt . Println ( "Using RCLONE_CONFIG_PASS returned:" , err )
} else {
2017-02-09 19:01:20 +08:00
Debugf ( nil , "Using RCLONE_CONFIG_PASS password." )
2016-02-16 23:25:27 +08:00
}
}
if len ( configKey ) == 0 {
if ! * AskPassword {
2016-06-12 22:06:02 +08:00
return nil , errors . New ( "unable to decrypt configuration and not allowed to ask for password - set RCLONE_CONFIG_PASS to your configuration password" )
2016-02-16 23:25:27 +08:00
}
2016-08-15 00:16:06 +08:00
getConfigPassword ( "Enter configuration password:" )
2016-02-16 23:25:27 +08:00
}
// Nonce is first 24 bytes of the ciphertext
var nonce [ 24 ] byte
copy ( nonce [ : ] , box [ : 24 ] )
var key [ 32 ] byte
copy ( key [ : ] , configKey [ : 32 ] )
// Attempt to decrypt
var ok bool
out , ok = secretbox . Open ( nil , box [ 24 : ] , & nonce , & key )
if ok {
break
}
// Retry
2017-02-09 19:01:20 +08:00
Errorf ( nil , "Couldn't decrypt configuration, most likely wrong password." )
2016-02-16 23:25:27 +08:00
configKey = nil
envpw = ""
}
2016-02-17 18:45:05 +08:00
return goconfig . LoadFromReader ( bytes . NewBuffer ( out ) )
2016-02-16 23:25:27 +08:00
}
2016-08-15 00:16:06 +08:00
// checkPassword normalises and validates the password
func checkPassword ( password string ) ( string , error ) {
if ! utf8 . ValidString ( password ) {
return "" , errors . New ( "password contains invalid utf8 characters" )
}
// Remove leading+trailing whitespace
password = strings . TrimSpace ( password )
// Normalize to reduce weird variations.
password = norm . NFKC . String ( password )
if len ( password ) == 0 {
return "" , errors . New ( "no characters in password" )
}
return password , nil
}
// GetPassword asks the user for a password with the prompt given.
func GetPassword ( prompt string ) string {
2017-04-16 01:17:53 +08:00
fmt . Fprintln ( os . Stderr , prompt )
2016-08-15 00:16:06 +08:00
for {
2017-04-16 01:17:53 +08:00
fmt . Fprint ( os . Stderr , "password:" )
2016-08-15 00:16:06 +08:00
password := ReadPassword ( )
password , err := checkPassword ( password )
if err == nil {
return password
}
2017-04-16 01:17:53 +08:00
fmt . Fprintf ( os . Stderr , "Bad password: %v\n" , err )
2016-08-15 00:16:06 +08:00
}
}
// ChangePassword will query the user twice for the named password. If
// the same password is entered it is returned.
func ChangePassword ( name string ) string {
for {
a := GetPassword ( fmt . Sprintf ( "Enter %s password:" , name ) )
b := GetPassword ( fmt . Sprintf ( "Confirm %s password:" , name ) )
if a == b {
return a
}
fmt . Println ( "Passwords do not match!" )
}
}
// getConfigPassword will query the user for a password the
2016-02-16 23:25:27 +08:00
// first time it is required.
2016-08-15 00:16:06 +08:00
func getConfigPassword ( q string ) {
2016-02-16 23:25:27 +08:00
if len ( configKey ) != 0 {
return
}
for {
2016-08-15 00:16:06 +08:00
password := GetPassword ( q )
err := setConfigPassword ( password )
2016-02-16 23:25:27 +08:00
if err == nil {
return
}
2017-04-16 01:17:53 +08:00
fmt . Fprintln ( os . Stderr , "Error:" , err )
2016-02-16 23:25:27 +08:00
}
}
2016-08-15 00:16:06 +08:00
// setConfigPassword will set the configKey to the hash of
2016-02-16 23:25:27 +08:00
// the password. If the length of the password is
// zero after trimming+normalization, an error is returned.
2016-08-15 00:16:06 +08:00
func setConfigPassword ( password string ) error {
password , err := checkPassword ( password )
if err != nil {
return err
2016-02-16 23:25:27 +08:00
}
// Create SHA256 has of the password
sha := sha256 . New ( )
2016-08-15 00:16:06 +08:00
_ , err = sha . Write ( [ ] byte ( "[" + password + "][rclone-config]" ) )
2016-02-16 23:25:27 +08:00
if err != nil {
return err
}
configKey = sha . Sum ( nil )
return nil
}
2016-08-15 00:16:06 +08:00
// changeConfigPassword will query the user twice
// for a password. If the same password is entered
// twice the key is updated.
func changeConfigPassword ( ) {
err := setConfigPassword ( ChangePassword ( "NEW configuration" ) )
if err != nil {
fmt . Printf ( "Failed to set config password: %v\n" , err )
return
}
}
2015-09-23 01:47:16 +08:00
// SaveConfig saves configuration file.
2016-02-16 23:25:27 +08:00
// if configKey has been set, the file will be encrypted.
2014-03-16 00:06:11 +08:00
func SaveConfig ( ) {
2017-06-01 23:38:19 +08:00
dir , name := filepath . Split ( ConfigPath )
2017-06-01 15:57:10 +08:00
f , err := ioutil . TempFile ( dir , name )
2014-03-16 00:06:11 +08:00
if err != nil {
2017-06-01 15:57:10 +08:00
log . Fatalf ( "Failed to create temp file for new config: %v" , err )
return
2014-03-16 00:06:11 +08:00
}
2017-06-01 15:57:10 +08:00
defer func ( ) {
if err := os . Remove ( f . Name ( ) ) ; err != nil && ! os . IsNotExist ( err ) {
Errorf ( nil , "Failed to remove temp config file: %v" , err )
}
} ( )
2016-02-16 23:25:27 +08:00
2017-06-01 15:57:10 +08:00
var buf bytes . Buffer
err = goconfig . SaveConfigData ( configData , & buf )
2016-02-16 23:25:27 +08:00
if err != nil {
log . Fatalf ( "Failed to save config file: %v" , err )
}
2017-06-01 15:57:10 +08:00
if len ( configKey ) == 0 {
if _ , err := buf . WriteTo ( f ) ; err != nil {
log . Fatalf ( "Failed to write temp config file: %v" , err )
}
} else {
fmt . Fprintln ( f , "# Encrypted rclone configuration File" )
fmt . Fprintln ( f , "" )
fmt . Fprintln ( f , "RCLONE_ENCRYPT_V0:" )
2016-02-16 23:25:27 +08:00
2017-06-01 15:57:10 +08:00
// Generate new nonce and write it to the start of the ciphertext
var nonce [ 24 ] byte
n , _ := rand . Read ( nonce [ : ] )
if n != 24 {
log . Fatalf ( "nonce short read: %d" , n )
}
enc := base64 . NewEncoder ( base64 . StdEncoding , f )
_ , err = enc . Write ( nonce [ : ] )
if err != nil {
log . Fatalf ( "Failed to write temp config file: %v" , err )
}
2016-02-16 23:25:27 +08:00
2017-06-01 15:57:10 +08:00
var key [ 32 ] byte
copy ( key [ : ] , configKey [ : 32 ] )
2016-02-16 23:25:27 +08:00
2017-06-01 15:57:10 +08:00
b := secretbox . Seal ( nil , buf . Bytes ( ) , & nonce , & key )
_ , err = enc . Write ( b )
if err != nil {
log . Fatalf ( "Failed to write temp config file: %v" , err )
}
_ = enc . Close ( )
2016-02-16 23:25:27 +08:00
}
2017-06-01 15:57:10 +08:00
2016-02-16 23:25:27 +08:00
err = f . Close ( )
if err != nil {
log . Fatalf ( "Failed to close config file: %v" , err )
}
2017-06-25 15:05:24 +08:00
var fileMode os . FileMode = 0600
info , err := os . Stat ( ConfigPath )
if err != nil {
Debugf ( nil , "Using default permissions for config file: %v" , fileMode )
} else if info . Mode ( ) != fileMode {
Debugf ( nil , "Keeping previous permissions for config file: %v" , info . Mode ( ) )
fileMode = info . Mode ( )
}
attemptCopyGroup ( ConfigPath , f . Name ( ) )
err = os . Chmod ( f . Name ( ) , fileMode )
2014-03-16 21:53:51 +08:00
if err != nil {
2017-02-09 19:01:20 +08:00
Errorf ( nil , "Failed to set permissions on config file: %v" , err )
2014-03-16 21:53:51 +08:00
}
2017-06-01 15:57:10 +08:00
if err = os . Rename ( ConfigPath , ConfigPath + ".old" ) ; err != nil && ! os . IsNotExist ( err ) {
log . Fatalf ( "Failed to move previous config to backup location: %v" , err )
}
if err = os . Rename ( f . Name ( ) , ConfigPath ) ; err != nil {
log . Fatalf ( "Failed to move newly written config from %s to final location: %v" , f . Name ( ) , err )
}
if err := os . Remove ( ConfigPath + ".old" ) ; err != nil && ! os . IsNotExist ( err ) {
Errorf ( nil , "Failed to remove backup config file: %v" , err )
}
2014-03-16 00:06:11 +08:00
}
2016-12-19 23:04:07 +08:00
// ConfigSetValueAndSave sets the key to the value and saves just that
// value in the config file. It loads the old config file in from
// disk first and overwrites the given value only.
func ConfigSetValueAndSave ( name , key , value string ) ( err error ) {
// Set the value in config in case we fail to reload it
2016-12-21 02:03:09 +08:00
configData . SetValue ( name , key , value )
2016-12-19 23:04:07 +08:00
// Reload the config file
reloadedConfigFile , err := loadConfigFile ( )
if err == errorConfigFileNotFound {
// Config file not written yet so ignore reload
return nil
} else if err != nil {
return err
}
_ , err = reloadedConfigFile . GetSection ( name )
if err != nil {
// Section doesn't exist yet so ignore reload
return err
}
// Update the config file with the reloaded version
2016-12-21 02:03:09 +08:00
configData = reloadedConfigFile
2016-12-19 23:04:07 +08:00
// Set the value in the reloaded version
reloadedConfigFile . SetValue ( name , key , value )
// Save it again
SaveConfig ( )
return nil
}
2015-09-23 01:47:16 +08:00
// ShowRemotes shows an overview of the config file
2014-03-16 00:52:51 +08:00
func ShowRemotes ( ) {
2016-12-21 02:03:09 +08:00
remotes := configData . GetSectionList ( )
2014-03-16 00:52:51 +08:00
if len ( remotes ) == 0 {
return
}
2014-03-16 00:06:11 +08:00
sort . Strings ( remotes )
fmt . Printf ( "%-20s %s\n" , "Name" , "Type" )
fmt . Printf ( "%-20s %s\n" , "====" , "====" )
for _ , remote := range remotes {
2016-12-21 02:03:09 +08:00
fmt . Printf ( "%-20s %s\n" , remote , ConfigFileGet ( remote , "type" ) )
2014-03-16 00:06:11 +08:00
}
}
// ChooseRemote chooses a remote name
func ChooseRemote ( ) string {
2016-12-21 02:03:09 +08:00
remotes := configData . GetSectionList ( )
2014-03-16 00:06:11 +08:00
sort . Strings ( remotes )
return Choose ( "remote" , remotes , nil , false )
}
2015-09-23 01:47:16 +08:00
// ReadLine reads some input
2017-06-25 14:55:54 +08:00
var ReadLine = func ( ) string {
2014-03-16 00:06:11 +08:00
buf := bufio . NewReader ( os . Stdin )
line , err := buf . ReadString ( '\n' )
if err != nil {
log . Fatalf ( "Failed to read line: %v" , err )
}
return strings . TrimSpace ( line )
}
// Command - choose one
2014-03-16 00:52:51 +08:00
func Command ( commands [ ] string ) byte {
2014-03-16 00:06:11 +08:00
opts := [ ] string { }
for _ , text := range commands {
fmt . Printf ( "%c) %s\n" , text [ 0 ] , text [ 1 : ] )
opts = append ( opts , text [ : 1 ] )
}
optString := strings . Join ( opts , "" )
optHelp := strings . Join ( opts , "/" )
for {
fmt . Printf ( "%s> " , optHelp )
result := strings . ToLower ( ReadLine ( ) )
if len ( result ) != 1 {
continue
}
2014-06-26 22:18:48 +08:00
i := strings . Index ( optString , string ( result [ 0 ] ) )
2014-03-16 00:06:11 +08:00
if i >= 0 {
2014-03-16 00:52:51 +08:00
return result [ 0 ]
2014-03-16 00:06:11 +08:00
}
}
}
2015-09-23 01:47:16 +08:00
// Confirm asks the user for Yes or No and returns true or false
2014-03-16 21:54:43 +08:00
func Confirm ( ) bool {
return Command ( [ ] string { "yYes" , "nNo" } ) == 'y'
}
2014-03-16 00:06:11 +08:00
// Choose one of the defaults or type a new string if newOk is set
func Choose ( what string , defaults , help [ ] string , newOk bool ) string {
2017-01-29 23:51:26 +08:00
valueDescripton := "an existing"
2014-03-16 00:06:11 +08:00
if newOk {
2017-01-29 23:51:26 +08:00
valueDescripton = "your own"
2014-03-16 00:06:11 +08:00
}
2017-01-29 23:51:26 +08:00
fmt . Printf ( "Choose a number from below, or type in %s value\n" , valueDescripton )
2014-03-16 00:06:11 +08:00
for i , text := range defaults {
2016-02-21 20:25:37 +08:00
var lines [ ] string
2014-03-16 00:06:11 +08:00
if help != nil {
parts := strings . Split ( help [ i ] , "\n" )
2016-02-21 20:25:37 +08:00
lines = append ( lines , parts ... )
}
lines = append ( lines , fmt . Sprintf ( "%q" , text ) )
pos := i + 1
if len ( lines ) == 1 {
fmt . Printf ( "%2d > %s\n" , pos , text )
} else {
mid := ( len ( lines ) - 1 ) / 2
for i , line := range lines {
var sep rune
switch i {
case 0 :
sep = '/'
case len ( lines ) - 1 :
sep = '\\'
default :
sep = '|'
}
number := " "
if i == mid {
number = fmt . Sprintf ( "%2d" , pos )
}
fmt . Printf ( "%s %c %s\n" , number , sep , line )
2014-03-16 00:06:11 +08:00
}
}
}
for {
fmt . Printf ( "%s> " , what )
result := ReadLine ( )
i , err := strconv . Atoi ( result )
if err != nil {
if newOk {
return result
}
2017-01-29 23:51:26 +08:00
for _ , v := range defaults {
if result == v {
return result
}
}
2014-03-16 00:06:11 +08:00
continue
}
if i >= 1 && i <= len ( defaults ) {
return defaults [ i - 1 ]
}
}
}
2016-01-31 20:58:41 +08:00
// ChooseNumber asks the user to enter a number between min and max
// inclusive prompting them with what.
func ChooseNumber ( what string , min , max int ) int {
for {
fmt . Printf ( "%s> " , what )
result := ReadLine ( )
i , err := strconv . Atoi ( result )
if err != nil {
fmt . Printf ( "Bad number: %v\n" , err )
continue
}
if i < min || i > max {
fmt . Printf ( "Out of range - %d to %d inclusive\n" , min , max )
continue
}
return i
}
}
2015-09-23 01:47:16 +08:00
// ShowRemote shows the contents of the remote
2014-03-16 00:06:11 +08:00
func ShowRemote ( name string ) {
fmt . Printf ( "--------------------\n" )
fmt . Printf ( "[%s]\n" , name )
2016-10-08 18:26:14 +08:00
fs := MustFindByName ( name )
2016-12-21 02:03:09 +08:00
for _ , key := range configData . GetKeyList ( name ) {
2016-10-08 18:26:14 +08:00
isPassword := false
for _ , option := range fs . Options {
if option . Name == key && option . IsPassword {
isPassword = true
break
}
}
2016-12-21 02:03:09 +08:00
value := ConfigFileGet ( name , key )
2016-10-08 18:26:14 +08:00
if isPassword && value != "" {
fmt . Printf ( "%s = *** ENCRYPTED ***\n" , key )
} else {
fmt . Printf ( "%s = %s\n" , key , value )
}
2014-03-16 00:06:11 +08:00
}
fmt . Printf ( "--------------------\n" )
}
2015-09-23 01:47:16 +08:00
// OkRemote prints the contents of the remote and ask if it is OK
2014-03-16 00:06:11 +08:00
func OkRemote ( name string ) bool {
ShowRemote ( name )
switch i := Command ( [ ] string { "yYes this is OK" , "eEdit this remote" , "dDelete this remote" } ) ; i {
2014-03-16 00:52:51 +08:00
case 'y' :
2014-03-16 00:06:11 +08:00
return true
2014-03-16 00:52:51 +08:00
case 'e' :
2014-03-16 00:06:11 +08:00
return false
2014-03-16 00:52:51 +08:00
case 'd' :
2016-12-21 02:03:09 +08:00
configData . DeleteSection ( name )
2014-03-16 00:06:11 +08:00
return true
default :
2017-02-09 19:01:20 +08:00
Errorf ( nil , "Bad choice %c" , i )
2014-03-16 00:06:11 +08:00
}
return false
}
2016-10-08 18:26:14 +08:00
// MustFindByName finds the RegInfo for the remote name passed in or
// exits with a fatal error.
func MustFindByName ( name string ) * RegInfo {
2016-12-21 02:03:09 +08:00
fsType := ConfigFileGet ( name , "type" )
2016-10-08 18:26:14 +08:00
if fsType == "" {
log . Fatalf ( "Couldn't find type of fs for %q" , name )
}
return MustFind ( fsType )
}
2015-09-23 01:47:16 +08:00
// RemoteConfig runs the config helper for the remote if needed
2014-03-16 21:54:43 +08:00
func RemoteConfig ( name string ) {
fmt . Printf ( "Remote config\n" )
2016-10-08 18:26:14 +08:00
f := MustFindByName ( name )
2014-03-16 21:54:43 +08:00
if f . Config != nil {
f . Config ( name )
}
}
2015-09-23 01:47:16 +08:00
// ChooseOption asks the user to choose an option
2014-03-29 01:56:04 +08:00
func ChooseOption ( o * Option ) string {
fmt . Println ( o . Help )
2016-08-15 00:16:06 +08:00
if o . IsPassword {
2016-08-20 03:00:05 +08:00
actions := [ ] string { "yYes type in my own password" , "gGenerate random password" }
if o . Optional {
actions = append ( actions , "nNo leave this optional password blank" )
}
var password string
switch i := Command ( actions ) ; i {
case 'y' :
password = ChangePassword ( "the" )
case 'g' :
for {
fmt . Printf ( "Password strength in bits.\n64 is just about memorable\n128 is secure\n1024 is the maximum\n" )
bits := ChooseNumber ( "Bits" , 64 , 1024 )
bytes := bits / 8
if bits % 8 != 0 {
bytes ++
}
var pw = make ( [ ] byte , bytes )
n , _ := rand . Read ( pw )
if n != bytes {
log . Fatalf ( "password short read: %d" , n )
}
password = base64 . RawURLEncoding . EncodeToString ( pw )
fmt . Printf ( "Your password is: %s\n" , password )
fmt . Printf ( "Use this password?\n" )
if Confirm ( ) {
break
}
}
case 'n' :
return ""
default :
2017-02-09 19:01:20 +08:00
Errorf ( nil , "Bad choice %c" , i )
2016-08-20 03:00:05 +08:00
}
return MustObscure ( password )
2016-08-15 00:16:06 +08:00
}
2014-03-29 01:56:04 +08:00
if len ( o . Examples ) > 0 {
var values [ ] string
var help [ ] string
for _ , example := range o . Examples {
values = append ( values , example . Value )
help = append ( help , example . Help )
}
return Choose ( o . Name , values , help , true )
}
fmt . Printf ( "%s> " , o . Name )
return ReadLine ( )
}
2016-02-16 02:11:53 +08:00
// fsOption returns an Option describing the possible remotes
func fsOption ( ) * Option {
o := & Option {
Name : "Storage" ,
Help : "Type of storage to configure." ,
}
2014-03-16 00:06:11 +08:00
for _ , item := range fsRegistry {
2016-02-16 02:11:53 +08:00
example := OptionExample {
Value : item . Name ,
Help : item . Description ,
}
o . Examples = append ( o . Examples , example )
2014-03-16 00:06:11 +08:00
}
2016-02-16 02:11:53 +08:00
o . Examples . Sort ( )
return o
}
2017-01-29 23:37:44 +08:00
// NewRemoteName asks the user for a name for a remote
func NewRemoteName ( ) ( name string ) {
for {
fmt . Printf ( "name> " )
name = ReadLine ( )
parts := matcher . FindStringSubmatch ( name + ":" )
switch {
case name == "" :
fmt . Printf ( "Can't use empty name.\n" )
case isDriveLetter ( name ) :
fmt . Printf ( "Can't use %q as it can be confused a drive letter.\n" , name )
case parts == nil :
fmt . Printf ( "Can't use %q as it has invalid characters in it.\n" , name )
default :
return name
}
}
}
2016-02-16 02:11:53 +08:00
// NewRemote make a new remote from its name
func NewRemote ( name string ) {
newType := ChooseOption ( fsOption ( ) )
2016-12-21 02:03:09 +08:00
configData . SetValue ( name , "type" , newType )
2016-10-08 18:26:14 +08:00
fs := MustFind ( newType )
2014-03-16 00:06:11 +08:00
for _ , option := range fs . Options {
2016-12-21 02:03:09 +08:00
configData . SetValue ( name , option . Name , ChooseOption ( & option ) )
2014-03-16 00:06:11 +08:00
}
2014-03-16 21:54:43 +08:00
RemoteConfig ( name )
2014-03-16 00:06:11 +08:00
if OkRemote ( name ) {
SaveConfig ( )
return
}
2016-08-15 00:16:06 +08:00
EditRemote ( fs , name )
2014-03-16 00:06:11 +08:00
}
2015-09-23 01:47:16 +08:00
// EditRemote gets the user to edit a remote
2016-08-15 00:16:06 +08:00
func EditRemote ( fs * RegInfo , name string ) {
2014-03-16 00:06:11 +08:00
ShowRemote ( name )
fmt . Printf ( "Edit remote\n" )
for {
2016-08-15 00:16:06 +08:00
for _ , option := range fs . Options {
key := option . Name
2016-12-21 02:03:09 +08:00
value := ConfigFileGet ( name , key )
2016-08-15 00:16:06 +08:00
fmt . Printf ( "Value %q = %q\n" , key , value )
fmt . Printf ( "Edit? (y/n)>\n" )
if Confirm ( ) {
newValue := ChooseOption ( & option )
2016-12-21 02:03:09 +08:00
configData . SetValue ( name , key , newValue )
2014-03-16 00:06:11 +08:00
}
}
2014-03-16 21:54:43 +08:00
RemoteConfig ( name )
2014-03-16 00:06:11 +08:00
if OkRemote ( name ) {
break
}
}
SaveConfig ( )
}
2015-09-23 01:47:16 +08:00
// DeleteRemote gets the user to delete a remote
2014-03-16 00:52:51 +08:00
func DeleteRemote ( name string ) {
2016-12-21 02:03:09 +08:00
configData . DeleteSection ( name )
2014-03-16 00:52:51 +08:00
SaveConfig ( )
}
2017-01-29 23:37:44 +08:00
// copyRemote asks the user for a new remote name and copies name into
2017-06-25 14:55:54 +08:00
// it. Returns the new name.
func copyRemote ( name string ) string {
2017-01-29 23:37:44 +08:00
newName := NewRemoteName ( )
// Copy the keys
for _ , key := range configData . GetKeyList ( name ) {
value := configData . MustValue ( name , key , "" )
configData . SetValue ( newName , key , value )
}
2017-06-25 14:55:54 +08:00
return newName
2017-01-29 23:37:44 +08:00
}
// RenameRemote renames a config section
func RenameRemote ( name string ) {
fmt . Printf ( "Enter new name for %q remote.\n" , name )
2017-06-25 14:55:54 +08:00
newName := copyRemote ( name )
if name != newName {
configData . DeleteSection ( name )
SaveConfig ( )
}
2017-01-29 23:37:44 +08:00
}
// CopyRemote copies a config section
func CopyRemote ( name string ) {
fmt . Printf ( "Enter name for copy of %q remote.\n" , name )
copyRemote ( name )
SaveConfig ( )
}
2015-09-23 01:47:16 +08:00
// EditConfig edits the config file interactively
2014-03-16 00:06:11 +08:00
func EditConfig ( ) {
for {
2016-12-21 02:03:09 +08:00
haveRemotes := len ( configData . GetSectionList ( ) ) != 0
2017-01-29 23:37:44 +08:00
what := [ ] string { "eEdit existing remote" , "nNew remote" , "dDelete remote" , "rRename remote" , "cCopy remote" , "sSet configuration password" , "qQuit config" }
2014-03-16 00:52:51 +08:00
if haveRemotes {
fmt . Printf ( "Current remotes:\n\n" )
ShowRemotes ( )
fmt . Printf ( "\n" )
} else {
fmt . Printf ( "No remotes found - make a new one\n" )
2017-06-10 17:43:38 +08:00
// take 2nd item and last 2 items of menu list
what = append ( what [ 1 : 2 ] , what [ len ( what ) - 2 : ] ... )
2014-03-16 00:52:51 +08:00
}
switch i := Command ( what ) ; i {
case 'e' :
2014-03-16 00:06:11 +08:00
name := ChooseRemote ( )
2016-10-08 18:26:14 +08:00
fs := MustFindByName ( name )
2016-08-15 00:16:06 +08:00
EditRemote ( fs , name )
2014-03-16 00:52:51 +08:00
case 'n' :
2017-01-29 23:37:44 +08:00
NewRemote ( NewRemoteName ( ) )
2014-03-16 00:52:51 +08:00
case 'd' :
2014-03-16 00:06:11 +08:00
name := ChooseRemote ( )
2014-03-16 00:52:51 +08:00
DeleteRemote ( name )
2017-01-29 23:37:44 +08:00
case 'r' :
RenameRemote ( ChooseRemote ( ) )
case 'c' :
CopyRemote ( ChooseRemote ( ) )
2016-02-16 23:25:27 +08:00
case 's' :
SetPassword ( )
2014-03-16 00:52:51 +08:00
case 'q' :
2014-03-16 00:06:11 +08:00
return
2016-02-16 23:25:27 +08:00
}
}
}
// SetPassword will allow the user to modify the current
// configuration encryption settings.
func SetPassword ( ) {
for {
if len ( configKey ) > 0 {
fmt . Println ( "Your configuration is encrypted." )
what := [ ] string { "cChange Password" , "uUnencrypt configuration" , "qQuit to main menu" }
switch i := Command ( what ) ; i {
case 'c' :
2016-08-15 00:16:06 +08:00
changeConfigPassword ( )
2016-02-16 23:25:27 +08:00
SaveConfig ( )
fmt . Println ( "Password changed" )
continue
case 'u' :
configKey = nil
SaveConfig ( )
continue
case 'q' :
return
}
} else {
fmt . Println ( "Your configuration is not encrypted." )
fmt . Println ( "If you add a password, you will protect your login information to cloud services." )
what := [ ] string { "aAdd Password" , "qQuit to main menu" }
switch i := Command ( what ) ; i {
case 'a' :
2016-08-15 00:16:06 +08:00
changeConfigPassword ( )
2016-02-16 23:25:27 +08:00
SaveConfig ( )
fmt . Println ( "Password set" )
continue
case 'q' :
return
}
}
}
}
2016-01-04 23:13:36 +08:00
// Authorize is for remote authorization of headless machines.
2016-01-07 23:20:32 +08:00
//
// It expects 1 or 3 arguments
//
// rclone authorize "fs name"
// rclone authorize "fs name" "client id" "client secret"
func Authorize ( args [ ] string ) {
2016-01-04 23:13:36 +08:00
switch len ( args ) {
case 1 , 3 :
default :
log . Fatalf ( "Invalid number of arguments: %d" , len ( args ) )
}
newType := args [ 0 ]
2016-10-08 18:26:14 +08:00
fs := MustFind ( newType )
2016-01-04 23:13:36 +08:00
if fs . Config == nil {
2016-01-07 23:20:32 +08:00
log . Fatalf ( "Can't authorize fs %q" , newType )
2016-01-04 23:13:36 +08:00
}
// Name used for temporary fs
name := "**temp-fs**"
// Make sure we delete it
defer DeleteRemote ( name )
// Indicate that we want fully automatic configuration.
2016-12-21 02:03:09 +08:00
configData . SetValue ( name , ConfigAutomatic , "yes" )
2016-01-04 23:13:36 +08:00
if len ( args ) == 3 {
2016-12-21 02:03:09 +08:00
configData . SetValue ( name , ConfigClientID , args [ 1 ] )
configData . SetValue ( name , ConfigClientSecret , args [ 2 ] )
2016-01-04 23:13:36 +08:00
}
fs . Config ( name )
}
2016-12-21 02:03:09 +08:00
// configToEnv converts an config section and name, eg ("myremote",
// "ignore-size") into an environment name
// "RCLONE_CONFIG_MYREMOTE_IGNORE_SIZE"
func configToEnv ( section , name string ) string {
return "RCLONE_CONFIG_" + strings . ToUpper ( strings . Replace ( section + "_" + name , "-" , "_" , - 1 ) )
}
// ConfigFileGet gets the config key under section returning the
// default or empty string if not set.
//
// It looks up defaults in the environment if they are present
func ConfigFileGet ( section , key string , defaultVal ... string ) string {
envKey := configToEnv ( section , key )
newValue , found := os . LookupEnv ( envKey )
if found {
defaultVal = [ ] string { newValue }
}
return configData . MustValue ( section , key , defaultVal ... )
}
// ConfigFileGetBool gets the config key under section returning the
// default or false if not set.
//
// It looks up defaults in the environment if they are present
func ConfigFileGetBool ( section , key string , defaultVal ... bool ) bool {
envKey := configToEnv ( section , key )
newValue , found := os . LookupEnv ( envKey )
if found {
newBool , err := strconv . ParseBool ( newValue )
if err != nil {
2017-02-09 19:01:20 +08:00
Errorf ( nil , "Couldn't parse %q into bool - ignoring: %v" , envKey , err )
2016-12-21 02:03:09 +08:00
} else {
defaultVal = [ ] bool { newBool }
}
}
return configData . MustBool ( section , key , defaultVal ... )
}
// ConfigFileGetInt gets the config key under section returning the
// default or 0 if not set.
//
// It looks up defaults in the environment if they are present
func ConfigFileGetInt ( section , key string , defaultVal ... int ) int {
envKey := configToEnv ( section , key )
newValue , found := os . LookupEnv ( envKey )
if found {
newInt , err := strconv . Atoi ( newValue )
if err != nil {
2017-02-09 19:01:20 +08:00
Errorf ( nil , "Couldn't parse %q into int - ignoring: %v" , envKey , err )
2016-12-21 02:03:09 +08:00
} else {
defaultVal = [ ] int { newInt }
}
}
return configData . MustInt ( section , key , defaultVal ... )
}
// ConfigFileSet sets the key in section to value. It doesn't save
// the config file.
func ConfigFileSet ( section , key , value string ) {
configData . SetValue ( section , key , value )
}
2017-05-25 17:15:22 +08:00
// ConfigFileDeleteKey deletes the config key in the config file.
// It returns true if the key was deleted,
// or returns false if the section or key didn't exist.
func ConfigFileDeleteKey ( section , key string ) bool {
return configData . DeleteKey ( section , key )
}
2016-12-21 02:03:09 +08:00
var matchEnv = regexp . MustCompile ( ` ^RCLONE_CONFIG_(.*?)_TYPE=.*$ ` )
// ConfigFileSections returns the sections in the config file
// including any defined by environment variables.
func ConfigFileSections ( ) [ ] string {
sections := configData . GetSectionList ( )
for _ , item := range os . Environ ( ) {
matches := matchEnv . FindStringSubmatch ( item )
if len ( matches ) == 2 {
sections = append ( sections , strings . ToLower ( matches [ 1 ] ) )
}
}
return sections
}