Check-in [91893d0dc3]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Started work on running Fossil as different OS user per Flint user
Timelines: family | ancestors | descendants | both | setuid-fossil
Files: files | file ages | folders
SHA1: 91893d0dc39ba9f1efa44037f0685572c8d5b443
User & Date: rkeene 2019-01-07 22:40:17.093
Context
2019-01-07
23:07
Ensure User ID is sane before calling fossil check-in: 0874801b3e user: rkeene tags: setuid-fossil
22:40
Started work on running Fossil as different OS user per Flint user check-in: 91893d0dc3 user: rkeene tags: setuid-fossil
2017-03-16
16:32
Fixed missing semicolons check-in: f6600ad1a1 user: rkeene tags: trunk
Changes
Unified Diff Ignore Whitespace Patch
Added .fossil-settings/ignore-glob.




>
>
1
2
scripts/fossil-as-user/suid-fossil
scripts/fossil-as-user/lib/*
Added scripts/fossil-as-user/Makefile.


























































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
CRYSTAL_PATH := /opt/appfs/rkeene.org/crystal/platform/latest
CRYSTAL      := $(CRYSTAL_PATH)/bin/crystal
SHARDS       := $(CRYSTAL_PATH)/bin/shards
DEBUG_OR_RELEASE := --debug

all: suid-fossil

lib: shard.lock
	rm -rf lib
	$(SHARDS) install

shard.lock: shard.yml

suid-fossil: suid-fossil.cr lib
	$(CRYSTAL) build $(DEBUG_OR_RELEASE) -o suid-fossil suid-fossil.cr

clean:
	rm -f suid-fossil

distclean: clean
	rm -rf lib

mrproper: distclean
	rm -f shard.lock
	$(MAKE) lib
	rm -rf lib

.PHONY: all clean distclean
.SUFFIXES:
Added scripts/fossil-as-user/shard.lock.




















>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
version: 1.0
shards:
  db:
    github: crystal-lang/crystal-db
    version: 0.5.1

  sqlite3:
    github: crystal-lang/crystal-sqlite3
    version: 0.10.0

Added scripts/fossil-as-user/shard.yml.










>
>
>
>
>
1
2
3
4
5
name: fossil-as-user
version: 1
dependencies:
  sqlite3:
    github: crystal-lang/crystal-sqlite3
Added scripts/fossil-as-user/suid-fossil.cr.




























































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
#! /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")
	# 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)