permissions.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. __package__ = 'archivebox.config'
  2. import os
  3. import pwd
  4. import sys
  5. from rich import print
  6. from pathlib import Path
  7. from contextlib import contextmanager
  8. #############################################################################################
  9. DATA_DIR = Path(os.getcwd())
  10. try:
  11. DATA_DIR_STAT = DATA_DIR.stat()
  12. DATA_DIR_UID = DATA_DIR_STAT.st_uid
  13. DATA_DIR_GID = DATA_DIR_STAT.st_gid
  14. except PermissionError:
  15. DATA_DIR_UID = 0
  16. DATA_DIR_GID = 0
  17. DEFAULT_PUID = 911
  18. DEFAULT_PGID = 911
  19. RUNNING_AS_UID = os.getuid()
  20. RUNNING_AS_GID = os.getgid()
  21. EUID = os.geteuid()
  22. EGID = os.getegid()
  23. SUDO_UID = int(os.environ.get('SUDO_UID', 0))
  24. SUDO_GID = int(os.environ.get('SUDO_GID', 0))
  25. USER: str = Path('~').expanduser().resolve().name
  26. IS_ROOT = RUNNING_AS_UID == 0
  27. IN_DOCKER = os.environ.get('IN_DOCKER', False) in ('1', 'true', 'True', 'TRUE', 'yes')
  28. FALLBACK_UID = RUNNING_AS_UID or SUDO_UID
  29. FALLBACK_GID = RUNNING_AS_GID or SUDO_GID
  30. if RUNNING_AS_UID == 0:
  31. try:
  32. # if we are running as root it's really hard to figure out what the correct archivebox user should be
  33. # as a last resort instead of setting DATA_DIR ownership to 0:0 (which breaks it for non-root users)
  34. # check if 911:911 archivebox user exists on host system, and use it instead of 0
  35. import pwd
  36. if pwd.getpwuid(DEFAULT_PUID).pw_name == 'archivebox':
  37. FALLBACK_UID = DEFAULT_PUID
  38. FALLBACK_GID = DEFAULT_PGID
  39. except Exception:
  40. pass
  41. os.environ.setdefault('PUID', str(DATA_DIR_UID or EUID or RUNNING_AS_UID or FALLBACK_UID))
  42. os.environ.setdefault('PGID', str(DATA_DIR_GID or EGID or RUNNING_AS_GID or FALLBACK_GID))
  43. ARCHIVEBOX_USER = int(os.environ['PUID'])
  44. ARCHIVEBOX_GROUP = int(os.environ['PGID'])
  45. if not USER:
  46. try:
  47. # alternative method 1 to get username
  48. USER = pwd.getpwuid(ARCHIVEBOX_USER).pw_name
  49. except Exception:
  50. pass
  51. if not USER:
  52. try:
  53. # alternative method 2 to get username
  54. import getpass
  55. USER = getpass.getuser()
  56. except Exception:
  57. pass
  58. if not USER:
  59. try:
  60. # alternative method 3 to get username
  61. USER = os.getlogin() or 'archivebox'
  62. except Exception:
  63. USER = 'archivebox'
  64. ARCHIVEBOX_USER_EXISTS = False
  65. try:
  66. pwd.getpwuid(ARCHIVEBOX_USER)
  67. ARCHIVEBOX_USER_EXISTS = True
  68. except Exception:
  69. ARCHIVEBOX_USER_EXISTS = False
  70. #############################################################################################
  71. def drop_privileges():
  72. """If running as root, drop privileges to the user that owns the data dir (or PUID)"""
  73. # always run archivebox as the user that owns the data dir, never as root
  74. if os.getuid() == 0:
  75. # drop permissions to the user that owns the data dir / provided PUID
  76. if os.geteuid() != ARCHIVEBOX_USER and ARCHIVEBOX_USER != 0 and ARCHIVEBOX_USER_EXISTS:
  77. # drop our effective UID to the archivebox user's UID
  78. os.seteuid(ARCHIVEBOX_USER)
  79. # update environment variables so that subprocesses dont try to write to /root
  80. pw_record = pwd.getpwuid(ARCHIVEBOX_USER)
  81. os.environ['HOME'] = pw_record.pw_dir
  82. os.environ['LOGNAME'] = pw_record.pw_name
  83. os.environ['USER'] = pw_record.pw_name
  84. if ARCHIVEBOX_USER == 0 or not ARCHIVEBOX_USER_EXISTS:
  85. print('[yellow]:warning: Running as [red]root[/red] is not recommended and may make your [blue]DATA_DIR[/blue] inaccessible to other users on your system.[/yellow] (use [blue]sudo[/blue] instead)', file=sys.stderr)
  86. @contextmanager
  87. def SudoPermission(uid=0, fallback=False):
  88. """Attempt to run code with sudo permissions for a given user (or root)"""
  89. if os.geteuid() == uid:
  90. # no need to change effective UID, we are already that user
  91. yield
  92. return
  93. try:
  94. # change our effective UID to the given UID
  95. os.seteuid(uid)
  96. except PermissionError as err:
  97. if not fallback:
  98. raise PermissionError(f'Not enough permissions to run code as uid={uid}, please retry with sudo') from err
  99. try:
  100. # yield back to the caller so they can run code inside context as root
  101. yield
  102. finally:
  103. # then set effective UID back to DATA_DIR owner
  104. try:
  105. os.seteuid(ARCHIVEBOX_USER)
  106. except PermissionError as err:
  107. if not fallback:
  108. raise PermissionError(f'Failed to revert uid={uid} back to {ARCHIVEBOX_USER} after running code with sudo') from err