Artifact [fa2ac06db0]

Artifact fa2ac06db0ef7c22266b8d58d79258aa440186ae:


     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
   100
   101
   102
   103
   104
   105
   106
   107
   108
   109
   110
   111
   112
   113
   114
   115
   116
   117
   118
   119
   120
   121
   122
   123
   124
   125
   126
   127
   128
   129
   130
   131
   132
   133
   134
   135
   136
   137
   138
   139
   140
   141
   142
   143
   144
   145
   146
   147
   148
   149
   150
   151
   152
   153
   154
   155
   156
   157
   158
   159
   160
   161
   162
   163
   164
   165
   166
   167
#! /opt/appfs/rkeene.org/crystal/platform/latest/bin/crystal

require "sqlite3"
require "ini"

# User-specified constants
## UID offset when mapping Flint user IDs to OS user IDs
UID_OFFSET = 1024 * 1024

## 
FLINT_ROOT = "/home/chiselapp/chisel"

# Import the C functions for setuid/setgid
lib C
	fun setuid(uid : Int32) : Int32
	fun setgid(gid : Int32) : Int32
end

# Usage information
def print_help
	print("Usage: suid-fossil <fossil-args...>\n")
end

# Convert a UserID or a Username to the other via the DB
def userid_from_db(userdb : String, username : String) : Int32
	userid = nil

	DB.open "sqlite3://#{userdb}" {|db|
		userid = db.query_one "SELECT id FROM users WHERE username = ? LIMIT 1", username, as: {Int32}
	}

	if userid.nil?
		raise "User Name could not be found"
	end

	userid
end

def username_from_db(userdb : String, userid : Int32) : String
	username = nil

	DB.open "sqlite3://#{userdb}" {|db|
		username = db.query_one "SELECT username FROM users WHERE id = ? LIMIT 1", userid, as: {String}
	}

	if username.nil?
		raise "User ID could not be found"
	end

	username
end

# Find the Flint DB given a path to the Flint root
def find_db(root : String) : String
	dbconfig_file = File.join(root, "config", "sqlite.cnf")

	dbconfig_text = File.read(dbconfig_file)

	dbconfig = INI.parse(dbconfig_text)

	dbfile = dbconfig["config"]["database"]
	dbfile = File.expand_path(dbfile, File.join(root, "db"))

	dbfile
end

# Find a Flint User ID from an OS File
def userid_from_file(file : String) : Int32
	info = File.info(file)

	Int32.new(info.owner - UID_OFFSET)
end

# Run Fossil, wrapped as a Flint UserName/UserID
def suid_fossil(username : String, userid : Int32, fossil_args : Array, fossil_command = "fossil")
	# Ensure that the user ID is sane
	if userid < 0
		raise "User ID out of bounds (too low)"
	end

	if userid > (UInt32.new(Int32::MAX) - UID_OFFSET - 1)
		raise "User ID out of bounds (too high)"
	end

	# Compute OS UID from Flint User ID
	uid = userid + UID_OFFSET

	# Create Fossil home directory
	home = "/tmp/suid-fossil/#{userid}"

	if !Dir.exists?(home)
		Dir.mkdir(home, 0o700)
		File.chown(home, uid, uid)
	end

	ENV["HOME"] = home

	# Set OS UID/GID
	## Opportunistic -- if it fails, we do not care
	C.setgid(uid)

	uidcheck = C.setuid(uid)
	if (uidcheck != 0)
		raise "Unable to switch to UID #{uid}"
	end

	# If possible, update environment with usernames
	ENV["USER"] = username
	ENV["USERNAME"] = username

	Process.exec(fossil_command, fossil_args)

	raise "Failed to run Fossil"
end

# -------------------------------
# MAIN
# -------------------------------
fossil_args = ARGV

flint_root = ENV.fetch("FLINT_ROOT", FLINT_ROOT)
userid     = ENV["FLINT_USERID"]?
username   = ENV["FLINT_USERNAME"]?

# Find DB if possible
userdb   = ENV["FLINT_USERDB"]?

if userdb.nil?
	if !flint_root.nil?
		userdb = find_db(flint_root)
	end
end

# Find User ID
## Check to see if this is a CGI call, if so
## we take the user ID from the filename
if userid.nil?
	if ENV.has_key?("GATEWAY_INTERFACE")
		userid = userid_from_file(ARGV[0])
	end
end

if userid.nil?
	if username.nil?
		raise "Unhandled -- must specify one of FLINT_USERNAME or FLINT_USERID"
	end

	if userdb.nil?
		raise "Unhandled -- must specify FLINT_USERDB or FLINT_ROOT"
	end

	userid = userid_from_db(userdb, username)
else
	userid = userid.to_i32()
end

# Find User Name
if username.nil?
	if userdb.nil?
		raise "Unhandled -- must specify FLINT_USERDB or FLINT_ROOT"
	end

	username = username_from_db(userdb, userid)
end

# Run Fossil
suid_fossil(username, userid, fossil_args)