def _main(argv=None): if sys.version_info < (3, 5): print( "Error: Your version of Python is too old, 3.5+ is required: %d.%d.%d" % sys.version_info[:3]) return -1 try: check_runtime_requirements() except RuntimeError as e: print("Error: %s" % (e, )) return -1 # Protect access token and potentially encryption keys block_tracing() if argv is None: argv = sys.argv parser = argparse.ArgumentParser() userspacefs.add_cli_arguments(parser) parser.add_argument("-c", "--config-file", help="config file path") parser.add_argument( "-e", "--encrypted-folder", dest='encrypted_folders', type=parse_encrypted_folder_arg, default=[], action='append', help= "relative paths of encrypted folders, can be used multiple times. requires safefs" ) parser.add_argument( "--print-default-config-file", action='store_true', help="print default config file path to standard out and quit") parser.add_argument("mount_point", nargs='?') args = parser.parse_args(argv[1:]) try: version = pkg_resources.require("dbxfs")[0].version except Exception: log.warning("Failed to get version", exc_info=True) version = '' if version: try: with urllib.request.urlopen( "https://pypi.org/pypi/dbxfs/json") as f: rversion = json.load(io.TextIOWrapper(f))['info']['version'] if rversion != version: print( "\033[0;31m\033[1mWarning: dbxfs is out of date (%s vs %s), upgrade with 'pip3 install --upgrade dbxfs'\033[0;0m" % (rversion, version)) except Exception: log.warning("Failed to get most recent version", exc_info=True) config_dir = appdirs.user_config_dir(APP_NAME) if args.config_file is not None: config_file = args.config_file else: config_file = os.path.join(config_dir, "config.json") if args.print_default_config_file: print(config_file) return 0 try: os.makedirs(config_dir, exist_ok=True) except OSError as e: print("Unable to create configuration directory: %s" % (e, )) return -1 config = {} try: f = open(config_file) except IOError as e: if e.errno != errno.ENOENT: raise else: try: with f: config = json.load(f) except ValueError as e: print("Config file %r is not valid json: %s" % (config_file, e)) return -1 mount_point = args.mount_point if mount_point is None: mount_point = config.get("mount_point") if not args.smb_no_mount and mount_point is None: parser.print_usage() print("%s: error: please provide the mount_point argument" % (os.path.basename(argv[0]), )) return 1 encrypted_folders = config.get("encrypted_folders", []) + args.encrypted_folders if safefs_wrap_create_fs is None and encrypted_folders: print( "safefs not installed, can't transparently decrypt encrypted folders" ) return 1 access_token = None save_access_token = False save_config = False access_token_command = config.get("access_token_command", None) if access_token_command is not None: print("Running %r for access token" % (' '.join(access_token_command), )) try: access_token = subprocess.check_output( access_token_command).decode("utf-8") except UnicodeDecodeError: print("Access token command output is not utf-8 encoded") return -1 except TypeError: print("Bad access token command: %r, " % (access_token_command, )) return -1 # NB: access tokens never contain white-space and the access token # command often accidentally appends a newline character. access_token = access_token.strip() if access_token is None: keyring_user = config.get("keyring_user", None) if keyring_user is not None: try: access_token = keyring.get_password(APP_NAME, keyring_user) except KeyringError as e: print("Failed to get access token from keyring: %s" % (e, )) if access_token is None: access_token_privy = config.get("access_token_privy", None) if access_token_privy is not None: passwd = None while True: passwd = getpass.getpass( "Enter access token passphrase (not your Dropbox password) (Ctrl-C to quit): " ) try: access_token = privy.peek(access_token_privy, passwd).decode('utf-8') except ValueError: if not yes_no_input( "Incorrect password, create new access token?"): continue break del passwd try_directly = False while True: if access_token is None: save_access_token = True if (access_token is None and try_directly and yes_no_input( "Want to try entering the access token directly?")): print("Go to https://dropbox.com/developers/apps to " "create an app and generate a personal access token.") while True: access_token = getpass.getpass( "Enter Access token (Ctrl-C to quit): ") if not access_token: print("Access tokens cannot be empty") continue break if access_token is None: auth_flow = dropbox.DropboxOAuth2FlowNoRedirect( APP_KEY, APP_SECRET) authorize_url = auth_flow.start() print("We need an access token. Perform the following steps:") print("1. Go to " + authorize_url) print("2. Click \"Allow\" (you may have to log in first)") print("3. Copy the authorization code.") while True: auth_code = input( "Enter authorization code (Ctrl-C to quit): ") if not auth_code: print("Authorization code cannot be empty") continue break try: oauth_result = auth_flow.finish(auth_code) except Exception as e: print("Authorization code was invalid!") try_directly = True continue access_token = oauth_result.access_token # test out access token try: dropbox.Dropbox(access_token).users_get_current_account() except (dropbox.exceptions.BadInputError, dropbox.exceptions.AuthError, ValueError) as e: print("Error using access token: %s" % (e, )) access_token = None try_directly = True except OSError: if not yes_no_input("Error connecting to Dropbox, Try again?"): return 1 else: break if save_access_token and yes_no_input( "We're all connected. Do you want to save your credentials for future runs?", default_yes=True): keyring_user = ''.join( [random.choice("asdfghjklzxcvbnmqwertyuiop") for _ in range(24)]) try: keyring.set_password(APP_NAME, keyring_user, access_token) except (KeyringError, RuntimeError) as e: print( "We need a passphrase to encrypt your access token before we can save it." ) print( "Warning: Your access token passphrase must contain enough randomness to be resistent to hacking. You can read this for more info: https://blogs.dropbox.com/tech/2012/04/zxcvbn-realistic-password-strength-estimation/" ) while True: pass_ = getpass.getpass("Enter new access token passphrase: ") pass2_ = getpass.getpass( "Enter new access token passphrase (again): ") if pass_ != pass2_: print("Passphrases didn't match, please re-enter") else: del pass2_ break config.pop('keyring_user', None) config['access_token_privy'] = privy.hide( access_token.encode('utf-8'), pass_, server=False) del pass_ save_config = True else: config.pop('access_token_privy', None) config['keyring_user'] = keyring_user save_config = True if not config.get("asked_send_error_reports", False): if yes_no_input( "Would you like to help us improve %s by providing anonymous error reports?" % (APP_NAME, ), default_yes=True): config['send_error_reports'] = True config['asked_send_error_reports'] = True save_config = True if save_access_token and yes_no_input( "Do you want \"%s\" to be the default mount point?" % (mount_point, ), default_yes=True): config['mount_point'] = mount_point save_config = True if save_config: with open(config_file, "w") as f: json.dump(config, f) log.info("Starting %s...", APP_NAME) if config.get('send_error_reports', False): try: sentry_sdk.init( "https://[email protected]/1293235", release='%s@%s' % (APP_NAME, version), with_locals=False) except Exception: log.warning("Failed to initialize sentry", exc_info=True) cache_folder = os.path.join(appdirs.user_cache_dir(APP_NAME), "file_cache") try: os.makedirs(cache_folder, exist_ok=True) except OSError: log.warning( "Failed to create cache folder, running without file cache") cache_folder = None def create_fs(): fs = CachingFileSystem(DropboxFileSystem(access_token), cache_folder=cache_folder) # From a purity standpoint the following layer ideally would # go between the caching fs and dropbox fs, but because the # contract between those two is highly specialized, just put # it on top fs = TranslateIgnoredFilesFileSystem(fs) if sys.platform == 'darwin': fs = DisableQuickLookFileSystem(fs) return fs if safefs_wrap_create_fs is not None: create_fs = safefs_wrap_create_fs(create_fs, encrypted_folders) if not os.path.exists(mount_point): if yes_no_input( "Mount point \"%s\" doesn't exist, do you want to create it?" % (mount_point, ), default_yes=True): try: os.makedirs(mount_point, exist_ok=True) except OSError as e: print("Unable to create mount point: %s" % (e, )) return -1 return userspacefs.simple_main( mount_point, "dbxfs", create_fs, args, on_new_process=None if BLOCK_TRACING_INHERITS else block_tracing)
def main(argv=None): # Protect access token and potentially encryption keys block_tracing() if argv is None: argv = sys.argv parser = argparse.ArgumentParser() userspacefs.add_cli_arguments(parser) parser.add_argument("-c", "--config-file", help="config file path") parser.add_argument( "-e", "--encrypted-folder", dest='encrypted_folders', type=parse_encrypted_folder_arg, default=[], action='append', help= "relative paths of encrypted folders, can be used multiple times. requires safefs" ) parser.add_argument( "--print-default-config-file", action='store_true', help="print default config file path to standard out and quit") parser.add_argument("mount_point", nargs='?') args = parser.parse_args(argv[1:]) config_dir = appdirs.user_config_dir(APP_NAME) if args.config_file is not None: config_file = args.config_file else: config_file = os.path.join(config_dir, "config.json") if args.print_default_config_file: print(config_file) return 0 if not args.smb_no_mount and args.mount_point is None: parser.print_usage() print("%s: error: please provide the mount_point argument" % (os.path.basename(argv[0]), )) return 1 os.makedirs(config_dir, exist_ok=True) config = {} try: f = open(config_file) except IOError as e: if e.errno != errno.ENOENT: raise else: try: with f: config = json.load(f) except ValueError as e: print("Config file %r is not valid json: %s" % (config_file, e)) return -1 access_token = None save_access_token = False save_config = False access_token_command = config.get("access_token_command", None) if access_token_command is not None: print("Running %r for access token" % (' '.join(access_token_command), )) try: access_token = subprocess.check_output( access_token_command).decode("utf-8") except TypeError: print("Bad access token command: %r, " % (access_token_command, )) return -1 if access_token is None: keyring_user = config.get("keyring_user", None) if keyring_user is not None: try: access_token = keyring.get_password(APP_NAME, keyring_user) except KeyringError as e: print("Failed to get access token from keyring: %s" % (e, )) if access_token is None: access_token_privy = config.get("access_token_privy", None) if access_token_privy is not None: passwd = None while True: passwd = getpass.getpass( "Enter access token passphrase (not your Dropbox password) (Ctrl-C to quit): " ) try: access_token = privy.peek(access_token_privy, passwd).decode('utf-8') except ValueError: if not yes_no_input( "Incorrect password, create new access token?"): continue break del passwd try_directly = False while True: if access_token is None: save_access_token = True if (access_token is None and try_directly and yes_no_input( "Want to try entering the access token directly?")): print("Go to https://dropbox.com/developers/apps to " "create an app and generate a personal access token.") while True: access_token = getpass.getpass( "Enter Access token (Ctrl-C to quit): ") if not access_token: print("Access tokens cannot be empty") continue break if access_token is None: auth_flow = dropbox.DropboxOAuth2FlowNoRedirect( APP_KEY, APP_SECRET) authorize_url = auth_flow.start() print("We need an access token. Perform the following steps:") print("1. Go to " + authorize_url) print("2. Click \"Allow\" (you may have to log in first)") print("3. Copy the authorization code.") while True: auth_code = input( "Enter authoritization code (Ctrl-C to quit): ") if not auth_code: print("Authorization code cannot be empty") continue break try: oauth_result = auth_flow.finish(auth_code) except Exception as e: print("Authorization code was invalid!") try_directly = True continue access_token = oauth_result.access_token # test out access token try: dropbox.Dropbox(access_token).users_get_current_account() except (dropbox.exceptions.BadInputError, dropbox.exceptions.AuthError) as e: print("Error using access token: %s" % (e, )) access_token = None try_directly = True else: break if save_access_token and yes_no_input( "We're all connected. Do you want to save your credentials for future runs?", default_yes=True): keyring_user = ''.join( [random.choice("asdfghjklzxcvbnmqwertyuiop") for _ in range(24)]) try: keyring.set_password(APP_NAME, keyring_user, access_token) except (KeyringError, RuntimeError) as e: print( "We need a passphrase to encrypt your access token before we can save it." ) print( "Warning: Your access token passphrase must contain enough randomness to be resistent to hacking. You can read this for more info: https://blogs.dropbox.com/tech/2012/04/zxcvbn-realistic-password-strength-estimation/" ) while True: pass_ = getpass.getpass("Enter new access token passphrase: ") pass2_ = getpass.getpass( "Enter new access token passphrase (again): ") if pass_ != pass2_: print("Passphrases didn't match, please re-enter") else: del pass2_ break config.pop('keyring_user', None) config['access_token_privy'] = privy.hide( access_token.encode('utf-8'), pass_, server=False) del pass_ save_config = True else: config.pop('access_token_privy', None) config['keyring_user'] = keyring_user save_config = True if not config.get("asked_send_error_reports", False): if yes_no_input( "Would you like to help us improve %s by providing anonymous error reports?" % (APP_NAME, ), default_yes=True): config['send_error_reports'] = True config['asked_send_error_reports'] = True save_config = True if save_config: with open(config_file, "w") as f: json.dump(config, f) log.info("Starting %s...", APP_NAME) wrap_fs_errors = True if config.get('send_error_reports', False): try: version = pkg_resources.require("dbxfs")[0].version except Exception: log.warning("Failed to get version", exc_info=True) version = '' try: sentry_sdk.init( "https://[email protected]/1293235", release='%s@%s' % (APP_NAME, version), with_locals=False) wrap_fs_errors = True except Exception: log.warning("Failed to initialize sentry", exc_info=True) cache_folder = os.path.join(appdirs.user_cache_dir(APP_NAME), "file_cache") with contextlib.suppress(FileExistsError): os.makedirs(cache_folder) def create_fs(): fs = CachingFileSystem(DropboxFileSystem(access_token), cache_folder=cache_folder) if sys.platform == 'darwin': fs = DisableQuickLookFileSystem(fs) if wrap_fs_errors: fs = WrapErrorsFileSystem(fs) return fs encrypted_folders = config.get("encrypted_folders", []) + args.encrypted_folders create_fs = safefs_wrap_create_fs(create_fs, encrypted_folders) if not os.path.exists(args.mount_point): if yes_no_input( "Mount point \"%s\" doesn't exist, do you want to create it?" % (args.mount_point, ), default_yes=True): os.makedirs(args.mount_point, exist_ok=True) return userspacefs.simple_main(args.mount_point, "dbxfs", create_fs, args)