rda-python-setuid 1.0.9__py3-none-any.whl → 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rda_python_setuid/install.py +163 -38
- rda_python_setuid/install.usg +47 -17
- rda_python_setuid/pgstart.py +3 -3
- rda_python_setuid/pywrapper.py +2 -0
- rda_python_setuid/setuid_setup.usg +75 -0
- rda_python_setuid/setup_guide.py +51 -0
- {rda_python_setuid-1.0.9.dist-info → rda_python_setuid-3.0.0.dist-info}/METADATA +97 -17
- rda_python_setuid-3.0.0.dist-info/RECORD +14 -0
- rda_python_setuid-1.0.9.dist-info/RECORD +0 -12
- {rda_python_setuid-1.0.9.dist-info → rda_python_setuid-3.0.0.dist-info}/WHEEL +0 -0
- {rda_python_setuid-1.0.9.dist-info → rda_python_setuid-3.0.0.dist-info}/entry_points.txt +0 -0
- {rda_python_setuid-1.0.9.dist-info → rda_python_setuid-3.0.0.dist-info}/licenses/LICENSE +0 -0
- {rda_python_setuid-1.0.9.dist-info → rda_python_setuid-3.0.0.dist-info}/top_level.txt +0 -0
rda_python_setuid/install.py
CHANGED
|
@@ -15,17 +15,23 @@
|
|
|
15
15
|
# pywrapper-install
|
|
16
16
|
#
|
|
17
17
|
# # 1. Compile and install pywrapper (run once per environment):
|
|
18
|
-
# pywrapper-install -c [-
|
|
18
|
+
# pywrapper-install -c [-n gdexdata] [-e $ENVHOME]
|
|
19
19
|
#
|
|
20
20
|
# # 2. Create pgstart_USER entry so USER can run commands as themselves:
|
|
21
|
-
# pywrapper-install -p [-
|
|
21
|
+
# pywrapper-install -p [-n zji] [-e $ENVHOME]
|
|
22
22
|
#
|
|
23
23
|
# # 3. Create a symlink so a program runs as CommonUser via pywrapper (setuid):
|
|
24
|
-
# pywrapper-install -l myprog [-
|
|
24
|
+
# pywrapper-install -l myprog [-n gdexdata] [-e $ENVHOME]
|
|
25
|
+
#
|
|
26
|
+
# # 3b. Auto-link all discovered setuid_* entries that are not yet linked:
|
|
27
|
+
# pywrapper-install -l all [-e $ENVHOME]
|
|
25
28
|
#
|
|
26
29
|
# # 4. Simple install: symlink PROGRAM -> setuid_PROGRAM (no setuid, runs as current user):
|
|
27
30
|
# pywrapper-install -l myprog -s [-e $ENVHOME]
|
|
28
31
|
#
|
|
32
|
+
# # 5. Update existing installation (recompile and reinstall all setuid binaries):
|
|
33
|
+
# pywrapper-install -u [-n gdexdata] [-e $ENVHOME]
|
|
34
|
+
#
|
|
29
35
|
# Convention for wrapped programs:
|
|
30
36
|
# The target package must register its connector entry point with a setuid_ prefix:
|
|
31
37
|
# [project.scripts]
|
|
@@ -77,7 +83,7 @@ def main():
|
|
|
77
83
|
help="Path to the venv root directory containing bin/ (default: parent of the current Python executable's bin/ dir)"
|
|
78
84
|
)
|
|
79
85
|
parser.add_argument(
|
|
80
|
-
'-
|
|
86
|
+
'-n', '--username', default=None,
|
|
81
87
|
help="User name to own the setuid binary (default: current login user for -p/--pgstart, gdexdata otherwise)"
|
|
82
88
|
)
|
|
83
89
|
parser.add_argument(
|
|
@@ -95,57 +101,105 @@ def main():
|
|
|
95
101
|
)
|
|
96
102
|
group.add_argument(
|
|
97
103
|
'-l', '--link', metavar='PROGRAM',
|
|
98
|
-
help="Create symlink PROGRAM -> pywrapper for running a fixed program as CommonUser (Mode 1)"
|
|
104
|
+
help="Create symlink PROGRAM -> pywrapper for running a fixed program as CommonUser (Mode 1); use 'all' to auto-link every setuid_* entry not yet linked"
|
|
105
|
+
)
|
|
106
|
+
group.add_argument(
|
|
107
|
+
'-u', '--update', action='store_true',
|
|
108
|
+
help="Update an existing installation: recompile pywrapper and reinstall all pgstart_USER setuid binaries"
|
|
99
109
|
)
|
|
100
110
|
args = parser.parse_args()
|
|
101
111
|
|
|
102
|
-
if not (args.compile or args.pgstart or args.link):
|
|
112
|
+
if not (args.compile or args.pgstart or args.link or args.update):
|
|
103
113
|
show_usage()
|
|
104
114
|
|
|
105
|
-
if args.
|
|
115
|
+
if args.username is None and not args.simple:
|
|
106
116
|
import pwd
|
|
107
117
|
if args.pgstart:
|
|
108
|
-
args.
|
|
118
|
+
args.username = pwd.getpwuid(os.getuid()).pw_name
|
|
109
119
|
else:
|
|
110
|
-
args.
|
|
120
|
+
args.username = 'gdexdata'
|
|
111
121
|
|
|
112
122
|
bindir = os.path.join(args.envhome, 'bin') if args.envhome else get_bindir()
|
|
113
123
|
pywrapper = os.path.join(bindir, 'pywrapper')
|
|
114
124
|
|
|
115
125
|
if args.link:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if os.path.
|
|
124
|
-
print("
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
# For appname -> pywrapper links, run `ln -s` via pgstart_<commonuser> so
|
|
127
|
+
# the resulting symlink is owned by the common user (pywrapper owner).
|
|
128
|
+
pgstart_common = None
|
|
129
|
+
if not args.simple and os.path.exists(pywrapper):
|
|
130
|
+
import pwd
|
|
131
|
+
common_user = pwd.getpwuid(os.stat(pywrapper).st_uid).pw_name
|
|
132
|
+
pgstart_common = os.path.join(bindir, 'pgstart_' + common_user)
|
|
133
|
+
if not os.path.exists(pgstart_common):
|
|
134
|
+
print("Error: {} not found. Run pywrapper-install --pgstart --username {} first.".format(pgstart_common, common_user))
|
|
135
|
+
sys.exit(1)
|
|
136
|
+
|
|
137
|
+
if args.link.lower() == 'all':
|
|
138
|
+
# Discover all setuid_* entries in bindir and link any that are missing
|
|
139
|
+
appnames = sorted(
|
|
140
|
+
f[len('setuid_'):] for f in os.listdir(bindir) if f.startswith('setuid_')
|
|
141
|
+
)
|
|
142
|
+
if not appnames:
|
|
143
|
+
print("No setuid_* entries found in {}".format(bindir))
|
|
144
|
+
for appname in appnames:
|
|
145
|
+
target = os.path.join(bindir, appname)
|
|
146
|
+
script = os.path.join(bindir, 'setuid_' + appname)
|
|
147
|
+
if args.simple:
|
|
148
|
+
if os.path.lexists(target):
|
|
149
|
+
print("Already exists: {}".format(target))
|
|
150
|
+
else:
|
|
151
|
+
os.symlink(script, target)
|
|
152
|
+
print("Created: {} -> setuid_{}".format(target, appname))
|
|
153
|
+
else:
|
|
154
|
+
if os.path.lexists(target):
|
|
155
|
+
print("Already exists: {}".format(target))
|
|
156
|
+
else:
|
|
157
|
+
run([pgstart_common, 'ln', '-s', pywrapper, target])
|
|
158
|
+
print("Created: {} -> pywrapper".format(target))
|
|
128
159
|
else:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if os.path.
|
|
132
|
-
print("
|
|
160
|
+
target = os.path.join(bindir, args.link)
|
|
161
|
+
script = os.path.join(bindir, 'setuid_{}'.format(args.link))
|
|
162
|
+
if not os.path.exists(script):
|
|
163
|
+
print("Error: {} not found. Install the package that provides it first.".format(script))
|
|
164
|
+
sys.exit(1)
|
|
165
|
+
if args.simple:
|
|
166
|
+
# Simple install: symlink PROGRAM -> setuid_PROGRAM, no setuid, runs as current user.
|
|
167
|
+
if os.path.lexists(target):
|
|
168
|
+
print("Already exists: {}".format(target))
|
|
169
|
+
else:
|
|
170
|
+
os.symlink(script, target)
|
|
171
|
+
print("Created: {} -> setuid_{}".format(target, args.link))
|
|
133
172
|
else:
|
|
134
|
-
|
|
135
|
-
|
|
173
|
+
# Mode 1: symlink PROGRAM -> pywrapper. setuid_PROGRAM is left with its
|
|
174
|
+
# default ownership/permissions so it can be loaded and executed normally.
|
|
175
|
+
if os.path.lexists(target):
|
|
176
|
+
print("Already exists: {}".format(target))
|
|
177
|
+
else:
|
|
178
|
+
run([pgstart_common, 'ln', '-s', pywrapper, target])
|
|
179
|
+
print("Created: {} -> pywrapper".format(target))
|
|
136
180
|
|
|
137
181
|
elif args.pgstart:
|
|
138
|
-
# Mode 2:
|
|
182
|
+
# Mode 2: create pgstart_USER with setuid owned by USER.
|
|
183
|
+
# When USER already owns pywrapper (i.e. the common user), pywrapper is
|
|
184
|
+
# already setuid owned by USER, so a symlink is sufficient; otherwise
|
|
185
|
+
# copy pywrapper and chmod 4750 as USER.
|
|
139
186
|
if not os.path.exists(pywrapper):
|
|
140
|
-
print("Error: {} not found. Run pywrapper-install --
|
|
187
|
+
print("Error: {} not found. Run pywrapper-install --compile --username COMMONUSER first.".format(pywrapper))
|
|
141
188
|
sys.exit(1)
|
|
142
|
-
target = os.path.join(bindir, 'pgstart_{}'.format(args.
|
|
189
|
+
target = os.path.join(bindir, 'pgstart_{}'.format(args.username))
|
|
143
190
|
import pwd
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
191
|
+
pywrapper_owner = pwd.getpwuid(os.stat(pywrapper).st_uid).pw_name
|
|
192
|
+
if args.username == pywrapper_owner:
|
|
193
|
+
if os.path.lexists(target):
|
|
194
|
+
os.remove(target)
|
|
195
|
+
os.symlink(pywrapper, target)
|
|
196
|
+
print("Linked: {} -> pywrapper (setuid, owned by {})".format(target, args.username))
|
|
197
|
+
else:
|
|
198
|
+
curuser = pwd.getpwuid(os.getuid()).pw_name
|
|
199
|
+
sudo_prefix = [] if curuser == args.username else ['sudo', '-u', args.username]
|
|
200
|
+
run(sudo_prefix + ['cp', pywrapper, target])
|
|
201
|
+
run(sudo_prefix + ['chmod', '4750', target])
|
|
202
|
+
print("Installed: {} (setuid, owned by {})".format(target, args.username))
|
|
149
203
|
|
|
150
204
|
elif args.compile:
|
|
151
205
|
# Compile pywrapper.c and install pywrapper with setuid
|
|
@@ -153,9 +207,80 @@ def main():
|
|
|
153
207
|
src_dest = os.path.join(bindir, 'pywrapper.c')
|
|
154
208
|
shutil.copy(src, src_dest)
|
|
155
209
|
print("Copied: {}".format(src_dest))
|
|
156
|
-
run(['sudo', '-u', args.
|
|
157
|
-
run(['sudo', '-u', args.
|
|
158
|
-
print("Installed: {} (setuid, owned by {})".format(pywrapper, args.
|
|
210
|
+
run(['sudo', '-u', args.username, 'gcc', '-o', pywrapper, src_dest])
|
|
211
|
+
run(['sudo', '-u', args.username, 'chmod', '4750', pywrapper])
|
|
212
|
+
print("Installed: {} (setuid, owned by {})".format(pywrapper, args.username))
|
|
213
|
+
|
|
214
|
+
elif args.update:
|
|
215
|
+
# Update an existing installation: recompile pywrapper and reinstall all setuid binaries
|
|
216
|
+
pgstart_files = sorted(f for f in os.listdir(bindir) if f.startswith('pgstart_'))
|
|
217
|
+
if not pgstart_files:
|
|
218
|
+
print("Error: No pgstart_* binaries found in {}".format(bindir))
|
|
219
|
+
sys.exit(1)
|
|
220
|
+
if not os.path.exists(pywrapper):
|
|
221
|
+
print("Error: {} not found.".format(pywrapper))
|
|
222
|
+
sys.exit(1)
|
|
223
|
+
|
|
224
|
+
gdexuser = args.username
|
|
225
|
+
pgstart_gdexdata = os.path.join(bindir, 'pgstart_' + gdexuser)
|
|
226
|
+
if not os.path.exists(pgstart_gdexdata):
|
|
227
|
+
print("Error: {} not found.".format(pgstart_gdexdata))
|
|
228
|
+
sys.exit(1)
|
|
229
|
+
|
|
230
|
+
update_tmp = os.path.join(bindir, 'update_tmp')
|
|
231
|
+
os.makedirs(update_tmp, exist_ok=True)
|
|
232
|
+
print("Created: {}".format(update_tmp))
|
|
233
|
+
|
|
234
|
+
# Symlink pgstart.py into update_tmp so pywrapper instances running from
|
|
235
|
+
# update_tmp can find it via the fpath/pgstart.py fallback lookup.
|
|
236
|
+
bindir_pgstart_py = os.path.join(bindir, 'pgstart.py')
|
|
237
|
+
update_pgstart_py = os.path.join(update_tmp, 'pgstart.py')
|
|
238
|
+
if not os.path.lexists(update_pgstart_py):
|
|
239
|
+
os.symlink(bindir_pgstart_py, update_pgstart_py)
|
|
240
|
+
print("Linked: {} -> {}".format(update_pgstart_py, bindir_pgstart_py))
|
|
241
|
+
|
|
242
|
+
# Copy each non-gdex pgstart_USERNAME into update_tmp using itself, then chmod 4750
|
|
243
|
+
for fname in pgstart_files:
|
|
244
|
+
username = fname[len('pgstart_'):]
|
|
245
|
+
if username == gdexuser:
|
|
246
|
+
continue
|
|
247
|
+
src_pgstart = os.path.join(bindir, fname)
|
|
248
|
+
dst_pgstart = os.path.join(update_tmp, fname)
|
|
249
|
+
run([src_pgstart, 'cp', src_pgstart, update_tmp + '/'])
|
|
250
|
+
run([src_pgstart, 'chmod', '4750', dst_pgstart])
|
|
251
|
+
|
|
252
|
+
# Copy pywrapper into update_tmp as gdexdata, chmod, then hardlink as pgstart_gdexdata
|
|
253
|
+
update_pywrapper = os.path.join(update_tmp, 'pywrapper')
|
|
254
|
+
update_pgstart_gdexdata = os.path.join(update_tmp, 'pgstart_' + gdexuser)
|
|
255
|
+
run([pgstart_gdexdata, 'cp', pywrapper, update_tmp + '/'])
|
|
256
|
+
run([pgstart_gdexdata, 'chmod', '4750', update_pywrapper])
|
|
257
|
+
run([pgstart_gdexdata, 'ln', update_pywrapper, update_pgstart_gdexdata])
|
|
258
|
+
|
|
259
|
+
# Compile new pywrapper using update_tmp/pgstart_gdexdata
|
|
260
|
+
src = get_c_source()
|
|
261
|
+
src_dest = os.path.join(bindir, 'pywrapper.c')
|
|
262
|
+
shutil.copy(src, src_dest)
|
|
263
|
+
print("Copied: {}".format(src_dest))
|
|
264
|
+
run([update_pgstart_gdexdata, 'gcc', '-o', pywrapper, src_dest])
|
|
265
|
+
run([update_pgstart_gdexdata, 'chmod', '4750', pywrapper])
|
|
266
|
+
print("Compiled: {} (setuid, owned by {})".format(pywrapper, gdexuser))
|
|
267
|
+
|
|
268
|
+
# Recreate each pgstart_* in bindir using the corresponding update_tmp/pgstart_*
|
|
269
|
+
for fname in sorted(f for f in os.listdir(update_tmp) if f.startswith('pgstart_')):
|
|
270
|
+
username = fname[len('pgstart_'):]
|
|
271
|
+
update_pgstart = os.path.join(update_tmp, fname)
|
|
272
|
+
target = os.path.join(bindir, fname)
|
|
273
|
+
if username == gdexuser:
|
|
274
|
+
run([update_pgstart_gdexdata, 'ln', '-sf', pywrapper, target])
|
|
275
|
+
print("Linked: {} -> pywrapper (setuid, owned by {})".format(target, gdexuser))
|
|
276
|
+
else:
|
|
277
|
+
run([update_pgstart, 'cp', pywrapper, target])
|
|
278
|
+
run([update_pgstart, 'chmod', '4750', target])
|
|
279
|
+
print("Updated: {} (setuid, owned by {})".format(target, username))
|
|
280
|
+
|
|
281
|
+
# Clean up the temporary working directory
|
|
282
|
+
shutil.rmtree(update_tmp)
|
|
283
|
+
print("Removed: {}".format(update_tmp))
|
|
159
284
|
|
|
160
285
|
|
|
161
286
|
if __name__ == '__main__': main()
|
rda_python_setuid/install.usg
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
that execute Python scripts as a common or effective user via the setuid mechanism.
|
|
3
3
|
Must be run inside the target Python virtual environment.
|
|
4
4
|
|
|
5
|
-
Usage: pywrapper-install [-
|
|
5
|
+
Usage: pywrapper-install [-n|--username USERNAME] [-e|--envhome ENVHOME]
|
|
6
6
|
( -c|--compile
|
|
7
7
|
| -p|--pgstart
|
|
8
|
-
| -l|--link PROGRAM [-s|--simple]
|
|
8
|
+
| -l|--link PROGRAM|all [-s|--simple]
|
|
9
|
+
| -u|--update )
|
|
9
10
|
|
|
10
11
|
Run pywrapper-install with no arguments to display this user guide. Exactly
|
|
11
|
-
one of -c/--compile, -p/--pgstart,
|
|
12
|
-
action.
|
|
12
|
+
one of -c/--compile, -p/--pgstart, -l/--link, or -u/--update must be given
|
|
13
|
+
to perform an action.
|
|
13
14
|
|
|
14
|
-
- Option -
|
|
15
|
+
- Option -n or --username USERNAME
|
|
15
16
|
The user name to own the setuid binary. For Mode 1 (CommonUser program),
|
|
16
17
|
this is the common user (e.g. gdexdata). For Mode 2 (pgstart), this is
|
|
17
18
|
the specialist user (e.g. zji). Defaults to 'gdexdata' unless -p/--pgstart
|
|
@@ -31,20 +32,34 @@
|
|
|
31
32
|
|
|
32
33
|
- Option -p or --pgstart
|
|
33
34
|
Mode 2: copy the compiled pywrapper binary to pgstart_USER (owned by USER,
|
|
34
|
-
chmod 4750). Allows USER
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
chmod 4750). Allows USER (any login user in the same group as the
|
|
36
|
+
common user PGLOG['COMMONUSER']) to run arbitrary commands as themselves
|
|
37
|
+
via the setuid wrapper. Two paths for setting up pgstart_USER:
|
|
38
|
+
(a) if PGLOG['ADMINUSER'] (default zji) can 'sudo -u USER', the admin
|
|
39
|
+
runs 'pywrapper-install -p -n USER' on behalf of USER; or
|
|
40
|
+
(b) USER runs the same command themselves (no sudo required).
|
|
41
|
+
Cannot be combined with -c/--compile or -l/--link.
|
|
42
|
+
|
|
43
|
+
- Option -l or --link PROGRAM|all
|
|
38
44
|
Mode 1: create a symlink PROGRAM -> pywrapper in the bin/ directory so
|
|
39
45
|
that running PROGRAM invokes the setuid wrapper, which then execs
|
|
40
46
|
setuid_PROGRAM as CommonUser. setuid_PROGRAM keeps its default
|
|
41
47
|
ownership and permissions. Cannot be combined with -c/--compile or
|
|
42
48
|
-p/--pgstart.
|
|
49
|
+
Use 'all' instead of a program name to scan bin/ for every setuid_*
|
|
50
|
+
entry and add any missing PROGRAM -> pywrapper symlinks in one pass.
|
|
51
|
+
|
|
52
|
+
- Option -u or --update
|
|
53
|
+
Update an existing installation. Discovers all pgstart_* and pywrapper
|
|
54
|
+
binaries already present in bin/, saves them to a temporary update_tmp/
|
|
55
|
+
subdirectory, recompiles pywrapper using update_tmp/pgstart_COMMONUSER, and
|
|
56
|
+
reinstalls every pgstart_* binary from update_tmp. Use -n/--username to
|
|
57
|
+
specify the gdex common user (default: gdexdata).
|
|
43
58
|
|
|
44
59
|
- Option -s or --simple (use with -l/--link)
|
|
45
60
|
Simple install: create a symlink PROGRAM -> setuid_PROGRAM directly,
|
|
46
61
|
skipping the setuid mechanism entirely. The program runs as the current
|
|
47
|
-
user with no privilege change. -
|
|
62
|
+
user with no privilege change. -n/--username is not required with this option.
|
|
48
63
|
Useful for users who do not need or cannot set up the setuid wrapper.
|
|
49
64
|
|
|
50
65
|
Convention for wrapped programs:
|
|
@@ -111,10 +126,14 @@
|
|
|
111
126
|
pywrapper-install -c
|
|
112
127
|
|
|
113
128
|
3. Wire up each program as a setuid entry:
|
|
114
|
-
pywrapper-install -l dsarch
|
|
129
|
+
pywrapper-install -l dsarch # one program
|
|
130
|
+
pywrapper-install -l all # or all setuid_* entries at once
|
|
115
131
|
|
|
116
|
-
4. Optionally,
|
|
117
|
-
|
|
132
|
+
4. Optionally, install a pgstart_<loginname> binary so <loginname> (any
|
|
133
|
+
user in the same group as PGLOG['COMMONUSER']) can run commands as
|
|
134
|
+
themselves. Either PGLOG['ADMINUSER'] (default zji, if it has
|
|
135
|
+
'sudo -u <loginname>'), or <loginname> directly, runs:
|
|
136
|
+
pywrapper-install -p -n <loginname>
|
|
118
137
|
|
|
119
138
|
Option B - Simple install (no sudo required, runs as current user):
|
|
120
139
|
|
|
@@ -122,7 +141,8 @@
|
|
|
122
141
|
pip install rda_python_dsarch
|
|
123
142
|
|
|
124
143
|
2. Create a direct symlink to the connector script:
|
|
125
|
-
pywrapper-install -l dsarch -s
|
|
144
|
+
pywrapper-install -l dsarch -s # one program
|
|
145
|
+
pywrapper-install -l all -s # or all setuid_* entries at once
|
|
126
146
|
|
|
127
147
|
Examples:
|
|
128
148
|
|
|
@@ -135,8 +155,18 @@
|
|
|
135
155
|
3. Wire up dsarch to run as gdexdata via pywrapper:
|
|
136
156
|
pywrapper-install -l dsarch
|
|
137
157
|
|
|
138
|
-
4.
|
|
139
|
-
pywrapper-install -
|
|
158
|
+
4. Wire up all setuid_* entries at once:
|
|
159
|
+
pywrapper-install -l all
|
|
140
160
|
|
|
141
|
-
5.
|
|
161
|
+
5. Install pgstart_zji so login user zji can run commands as themselves
|
|
162
|
+
via pgstart (run by ADMINUSER with 'sudo -u zji' available, or by zji):
|
|
163
|
+
pywrapper-install -p -n zji
|
|
164
|
+
|
|
165
|
+
6. Update an existing installation (recompile and reinstall all setuid binaries):
|
|
166
|
+
pywrapper-install -u
|
|
167
|
+
|
|
168
|
+
7. Simple install of dsarch for a user who does not need setuid:
|
|
142
169
|
pywrapper-install -l dsarch -s
|
|
170
|
+
|
|
171
|
+
8. Simple install of all setuid_* entries at once:
|
|
172
|
+
pywrapper-install -l all -s
|
rda_python_setuid/pgstart.py
CHANGED
|
@@ -40,7 +40,7 @@ def main():
|
|
|
40
40
|
pglog = PgLOG()
|
|
41
41
|
permit = False
|
|
42
42
|
pglog.PGLOG['LOGFILE'] = "pgstart.log"
|
|
43
|
-
aname =
|
|
43
|
+
aname = pglog.get_command()
|
|
44
44
|
bckgrd = False
|
|
45
45
|
workdir = None
|
|
46
46
|
argv = sys.argv[1:]
|
|
@@ -49,7 +49,7 @@ def main():
|
|
|
49
49
|
euid = pglog.PGLOG['EUID']
|
|
50
50
|
ruser = pwd.getpwuid(ruid).pw_name
|
|
51
51
|
euser = pwd.getpwuid(euid).pw_name
|
|
52
|
-
if ruser
|
|
52
|
+
if ruser in [pglog.PGLOG['ADMINUSER'], euser, pglog.PGLOG['COMMONUSER']] or euser == pglog.PGLOG['COMMONUSER']: permit = True
|
|
53
53
|
pglog.set_suid(euid)
|
|
54
54
|
|
|
55
55
|
while argv:
|
|
@@ -69,7 +69,7 @@ def main():
|
|
|
69
69
|
print("* Your Login Name is {}({}) & Effective User Name is {}({}).".format(ruser, ruid, euser, euid))
|
|
70
70
|
print("* Pass a command or options -(bg|fg|cwd|env|inc|plg) to run '{}'.".format(aname))
|
|
71
71
|
if not permit:
|
|
72
|
-
print("* You must be '{}' or '{}' to execute a command as user '{}'.".format(euser, pglog.PGLOG['
|
|
72
|
+
print("* You must be '{}' or '{}' to execute a command as user '{}'.".format(euser, pglog.PGLOG['COMMONUSER'], euser))
|
|
73
73
|
print("********************************************************************")
|
|
74
74
|
sys.exit(0)
|
|
75
75
|
|
rda_python_setuid/pywrapper.py
CHANGED
|
@@ -32,6 +32,8 @@ def main():
|
|
|
32
32
|
-plg -- print PGLOG variables and exit
|
|
33
33
|
"""
|
|
34
34
|
pglog = PgLOG()
|
|
35
|
+
from rda_python_setuid.setup_guide import show_setup_guide
|
|
36
|
+
show_setup_guide(pglog, 'rda_python_setuid', ['pywrapper'])
|
|
35
37
|
pglog.set_suid(pglog.PGLOG['EUID'])
|
|
36
38
|
inc = True
|
|
37
39
|
print("********************************************************************")
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
|
|
2
|
+
{PKGNAME} - Setuid Setup Guide
|
|
3
|
+
===============================
|
|
4
|
+
|
|
5
|
+
The following programs must run as the common user 'gdexdata' via the
|
|
6
|
+
rda_python_setuid mechanism:
|
|
7
|
+
|
|
8
|
+
{APPNAMES}
|
|
9
|
+
|
|
10
|
+
rda_python_setuid is installed automatically as a dependency.
|
|
11
|
+
|
|
12
|
+
If you are seeing this message after running a setuid_ connector script
|
|
13
|
+
directly, the setuid wrapper has not been set up yet. Follow the steps
|
|
14
|
+
below.
|
|
15
|
+
|
|
16
|
+
Run 'pywrapper-install' with no arguments for the full pywrapper user guide.
|
|
17
|
+
|
|
18
|
+
Environment Setup
|
|
19
|
+
-----------------
|
|
20
|
+
|
|
21
|
+
Option A - Python venv (DECS machines):
|
|
22
|
+
python3 -m venv $ENVHOME # e.g. /glade/u/home/gdexdata/gdexmsenv
|
|
23
|
+
source $ENVHOME/bin/activate
|
|
24
|
+
|
|
25
|
+
Option B - Conda (DAV/Casper):
|
|
26
|
+
conda create --prefix $ENVHOME python=3.12 # e.g. /glade/work/gdexdata/conda-envs/pg-gdex
|
|
27
|
+
conda activate $ENVHOME
|
|
28
|
+
|
|
29
|
+
Full Setuid Setup (requires sudo access to gdexdata)
|
|
30
|
+
----------------------------------------------------
|
|
31
|
+
|
|
32
|
+
# 1. Install the target package (pulls in rda_python_setuid automatically):
|
|
33
|
+
pip install {PKGNAME}
|
|
34
|
+
|
|
35
|
+
# 2. Compile the pywrapper C binary (once per environment):
|
|
36
|
+
pywrapper-install -c|--compile -n|--username gdexdata
|
|
37
|
+
|
|
38
|
+
# 3. Wire up each program as a setuid entry (specify name or use 'all'):
|
|
39
|
+
pywrapper-install -l|--link <program>
|
|
40
|
+
pywrapper-install -l|--link all # auto-link every setuid_* entry not yet linked
|
|
41
|
+
|
|
42
|
+
# 4. Optionally, install a pgstart_<loginname> binary so <loginname>
|
|
43
|
+
# (any user in the same group as PGLOG['COMMONUSER']) can run commands
|
|
44
|
+
# as themselves via the setuid wrapper. Same command in both cases,
|
|
45
|
+
# only the invoker differs:
|
|
46
|
+
#
|
|
47
|
+
# 4a. If PGLOG['ADMINUSER'] (default zji) can 'sudo -u <loginname>',
|
|
48
|
+
# the admin sets it up on the user's behalf:
|
|
49
|
+
pywrapper-install -p|--pgstart -n|--username <loginname>
|
|
50
|
+
#
|
|
51
|
+
# 4b. Otherwise <loginname> runs the same command themselves (no sudo
|
|
52
|
+
# from ADMINUSER required, since they already are <loginname>):
|
|
53
|
+
pywrapper-install -p|--pgstart -n|--username <loginname>
|
|
54
|
+
|
|
55
|
+
Update Existing Installation (no sudo required)
|
|
56
|
+
-----------------------------------------------
|
|
57
|
+
|
|
58
|
+
When the package is upgraded and a new pywrapper.c is bundled, use
|
|
59
|
+
-u/--update to recompile and reinstall all setuid binaries without
|
|
60
|
+
needing sudo. The existing pgstart_* binaries in bin/ are used to
|
|
61
|
+
perform the privileged operations:
|
|
62
|
+
|
|
63
|
+
pywrapper-install -u|--update [-n|--username gdexdata] [-e|--envhome $ENVHOME]
|
|
64
|
+
|
|
65
|
+
Simple Install (no sudo required, runs as current user)
|
|
66
|
+
-------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
Users who do not need the setuid mechanism can create direct symlinks
|
|
69
|
+
from <name> to setuid_<name>:
|
|
70
|
+
|
|
71
|
+
pywrapper-install -l|--link <program> -s|--simple
|
|
72
|
+
pywrapper-install -l|--link all -s|--simple # or link all setuid_* entries at once
|
|
73
|
+
|
|
74
|
+
The programs run as the current user with no privilege change.
|
|
75
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
##################################################################################
|
|
4
|
+
#
|
|
5
|
+
# Title: setup_guide
|
|
6
|
+
# Author: Zaihua Ji, zji@ucar.edu
|
|
7
|
+
# Date: 2026-05-21
|
|
8
|
+
# Purpose: Shared setuid setup guide displayed when a package's setuid_* entry
|
|
9
|
+
# point is invoked directly (i.e. before pywrapper symlinks are
|
|
10
|
+
# configured). Each package's setuid entry point calls
|
|
11
|
+
# show_setup_guide(pkgname, appnames) with its own metadata.
|
|
12
|
+
#
|
|
13
|
+
# Github: https://github.com/NCAR/rda-python-setuid.git
|
|
14
|
+
#
|
|
15
|
+
##################################################################################
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def show_setup_guide(obj, pkgname, appnames):
|
|
22
|
+
"""Display the setuid setup guide if invoked directly, otherwise return.
|
|
23
|
+
|
|
24
|
+
When a package's setuid entry point (e.g. ``setuid_dsarch``) is invoked
|
|
25
|
+
directly before pywrapper symlinks are set up, ``obj.get_command()``
|
|
26
|
+
returns the literal ``setuid_<appname>`` (no prefix stripping, since
|
|
27
|
+
euid is the real user, not COMMONUSER). In that case this function reads
|
|
28
|
+
``setuid_setup.usg`` bundled with rda_python_setuid, substitutes
|
|
29
|
+
``{PKGNAME}`` and ``{APPNAMES}``, prints the guide, and exits.
|
|
30
|
+
|
|
31
|
+
When invoked via the pywrapper symlink (euid = COMMONUSER), the
|
|
32
|
+
``setuid_`` prefix is stripped by ``get_command()``, the membership
|
|
33
|
+
check fails, and this function returns silently so the program runs
|
|
34
|
+
normally.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
obj: An instance derived from PgLOG (e.g. DsArch, RdaCp); provides
|
|
38
|
+
``get_command()`` with access to ``self.PGLOG['COMMONUSER']``.
|
|
39
|
+
pkgname: Distribution name (e.g. ``rda_python_dsarch``).
|
|
40
|
+
appnames: List of program names provided by the package that need
|
|
41
|
+
setuid (e.g. ``['dsarch']`` or ``['rdacp', 'rdakill', 'rdamod']``).
|
|
42
|
+
"""
|
|
43
|
+
if obj.get_command(sys.argv[0]) not in ['setuid_' + a for a in appnames]:
|
|
44
|
+
return
|
|
45
|
+
usgfile = os.path.join(os.path.dirname(__file__), 'setuid_setup.usg')
|
|
46
|
+
with open(usgfile) as f:
|
|
47
|
+
text = f.read()
|
|
48
|
+
text = text.replace('{PKGNAME}', pkgname)
|
|
49
|
+
text = text.replace('{APPNAMES}', ' '.join(appnames))
|
|
50
|
+
print(text)
|
|
51
|
+
sys.exit(0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rda_python_setuid
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: RDA Python Package to setuid for program executions as an effective or common user
|
|
5
5
|
Author-email: Zaihua Ji <zji@ucar.edu>
|
|
6
6
|
Project-URL: Homepage, https://github.com/NCAR/rda-python-setuid
|
|
@@ -27,8 +27,10 @@ Two modes are supported:
|
|
|
27
27
|
|
|
28
28
|
- **Mode 1 (CommonUser program):** a symlink `dsarch -> pywrapper` runs `setuid_dsarch`
|
|
29
29
|
as the common user.
|
|
30
|
-
- **Mode 2 (pgstart specialist):** a copy `pgstart_zji`
|
|
31
|
-
|
|
30
|
+
- **Mode 2 (pgstart specialist):** a copy `pgstart_<loginname>` (e.g. `pgstart_zji`)
|
|
31
|
+
runs any command as `<loginname>` via `pgstart.py`. `<loginname>` can be any
|
|
32
|
+
user that belongs to the same group as `PGLOG['COMMONUSER']`. Execution is
|
|
33
|
+
restricted to authorized callers (see `pgstart.py` below).
|
|
32
34
|
|
|
33
35
|
Two Python entry points are packaged alongside the C wrapper:
|
|
34
36
|
|
|
@@ -41,10 +43,12 @@ Two Python entry points are packaged alongside the C wrapper:
|
|
|
41
43
|
`sys.path`, and `PGLOG` dictionary respectively — handy for verifying the
|
|
42
44
|
setuid environment before wiring up a real program.
|
|
43
45
|
|
|
44
|
-
- **`pgstart.py`** — the Mode 2 launcher invoked through a `pgstart_<
|
|
46
|
+
- **`pgstart.py`** — the Mode 2 launcher invoked through a `pgstart_<loginname>`
|
|
45
47
|
copy of `pywrapper`. Reads the real/effective UIDs from `PGLOG`, then
|
|
46
|
-
permits execution only if the real user
|
|
47
|
-
|
|
48
|
+
permits execution only if the real user is in
|
|
49
|
+
`[PGLOG['ADMINUSER'], euser, PGLOG['COMMONUSER']]`
|
|
50
|
+
(i.e. the admin specialist `PGLOG['ADMINUSER']` — default `zji` — the
|
|
51
|
+
effective user themselves, or the shared common user); unauthorized callers receive
|
|
48
52
|
an informational message and exit. After authorization it parses leading
|
|
49
53
|
flag tokens — `-bg` (background via `subprocess.Popen`), `-fg` (explicit
|
|
50
54
|
foreground, default), `-cwd <dir>` (chdir before exec), and the same
|
|
@@ -78,30 +82,84 @@ automatically. `pywrapper-install -l/--link` creates the symlink
|
|
|
78
82
|
`dsarch -> pywrapper`; running `dsarch` goes through the setuid wrapper, which
|
|
79
83
|
execs `setuid_dsarch` as CommonUser.
|
|
80
84
|
|
|
85
|
+
The `main()` of each wrapped program (e.g. `rda_python_dsarch/dsarch.py`) must
|
|
86
|
+
also call `show_setup_guide()` at the top of `main()`, passing an instance of
|
|
87
|
+
the program's class along with the package name and list of setuid program
|
|
88
|
+
names:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
def main():
|
|
92
|
+
from rda_python_setuid.setup_guide import show_setup_guide
|
|
93
|
+
object = DsArch()
|
|
94
|
+
show_setup_guide(object, 'rda_python_dsarch', ['dsarch'])
|
|
95
|
+
...
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
When `setuid_dsarch` is invoked directly (before pywrapper symlinks are set
|
|
99
|
+
up, so euid ≠ CommonUser), `show_setup_guide()` prints the shared setuid setup
|
|
100
|
+
guide and exits. When invoked via the `dsarch -> pywrapper` symlink (euid =
|
|
101
|
+
CommonUser), `get_command()` strips the `setuid_` prefix, the check inside
|
|
102
|
+
`show_setup_guide()` fails, and the program runs normally.
|
|
103
|
+
|
|
81
104
|
## Environment setup
|
|
82
105
|
|
|
106
|
+
Create a Python environment first; package installs in the next section run
|
|
107
|
+
inside whichever environment you activate here.
|
|
108
|
+
|
|
83
109
|
### Option A — Python venv (DECS machines)
|
|
84
110
|
|
|
85
111
|
```bash
|
|
86
112
|
python3 -m venv $ENVHOME # e.g. /glade/u/home/gdexdata/gdexmsenv
|
|
87
113
|
source $ENVHOME/bin/activate
|
|
88
|
-
pip install rda_python_setuid rda_python_dsarch ...
|
|
89
114
|
```
|
|
90
115
|
|
|
91
116
|
### Option B — Conda (DAV/Casper)
|
|
92
117
|
|
|
93
118
|
```bash
|
|
94
|
-
conda create
|
|
95
|
-
conda activate
|
|
96
|
-
pip install rda_python_setuid rda_python_dsarch ...
|
|
119
|
+
conda create --prefix $ENVHOME python=3.12 # e.g. /glade/work/gdexdata/conda-envs/pg-gdex
|
|
120
|
+
conda activate $ENVHOME
|
|
97
121
|
```
|
|
98
122
|
|
|
99
|
-
|
|
123
|
+
## Installing rda-python-setuid
|
|
100
124
|
|
|
101
|
-
|
|
125
|
+
Pick whichever install mode fits your workflow. All four pull in the
|
|
126
|
+
transitive dependency (`rda_python_common`) automatically. Once installed,
|
|
127
|
+
the `pywrapper-install` CLI is available for the setuid wiring steps below.
|
|
102
128
|
|
|
103
|
-
|
|
104
|
-
|
|
129
|
+
For local development, clone this repo alongside your project and install it
|
|
130
|
+
in editable mode so that changes are picked up without re-installing:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
git clone https://github.com/NCAR/rda-python-setuid.git
|
|
134
|
+
cd rda-python-setuid
|
|
135
|
+
pip install -e .
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
To test a specific branch (e.g. an in-progress feature or fix branch), pass
|
|
139
|
+
`-b/--branch` to `git clone`:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
git clone -b <branch-name> https://github.com/NCAR/rda-python-setuid.git
|
|
143
|
+
cd rda-python-setuid
|
|
144
|
+
pip install -e .
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
For a regular (non-editable) install from a checkout:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
pip install /path/to/rda-python-setuid
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
For a production install on a system that uses the published distribution:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
pip install rda_python_setuid
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Setuid wrapper setup
|
|
160
|
+
|
|
161
|
+
With `rda_python_setuid` installed in the active environment, run
|
|
162
|
+
`pywrapper-install` with no arguments to display the full user guide:
|
|
105
163
|
|
|
106
164
|
```bash
|
|
107
165
|
pywrapper-install
|
|
@@ -116,11 +174,32 @@ pip install rda_python_dsarch
|
|
|
116
174
|
# 2. Compile pywrapper C binary (once per environment):
|
|
117
175
|
pywrapper-install -c|--compile
|
|
118
176
|
|
|
119
|
-
# 3. Wire up each program as a setuid entry:
|
|
177
|
+
# 3. Wire up each program as a setuid entry (specify name or use 'all'):
|
|
120
178
|
pywrapper-install -l|--link dsarch
|
|
179
|
+
pywrapper-install -l|--link all # auto-link every setuid_* entry not yet linked
|
|
180
|
+
|
|
181
|
+
# 4. Optionally, install a pgstart_<loginname> binary so <loginname> (any user
|
|
182
|
+
# in the same group as PGLOG['COMMONUSER']) can run commands as themselves
|
|
183
|
+
# via the setuid wrapper. Same command in both cases — only the invoker
|
|
184
|
+
# differs:
|
|
185
|
+
#
|
|
186
|
+
# 4a. If PGLOG['ADMINUSER'] (default zji) can `sudo -u <loginname>`, the
|
|
187
|
+
# admin sets it up on the user's behalf:
|
|
188
|
+
pywrapper-install -p|--pgstart -n|--username <loginname>
|
|
189
|
+
#
|
|
190
|
+
# 4b. Otherwise <loginname> runs the same command themselves (no sudo
|
|
191
|
+
# from ADMINUSER required, since they already are <loginname>):
|
|
192
|
+
pywrapper-install -p|--pgstart -n|--username <loginname>
|
|
193
|
+
```
|
|
121
194
|
|
|
122
|
-
|
|
123
|
-
|
|
195
|
+
### Update an existing installation (no sudo required)
|
|
196
|
+
|
|
197
|
+
When the package is upgraded and a new `pywrapper.c` is bundled, use `-u/--update`
|
|
198
|
+
to recompile and reinstall all setuid binaries without needing `sudo`. The existing
|
|
199
|
+
`pgstart_*` binaries in `bin/` are used to perform the privileged operations:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
pywrapper-install -u|--update [-n|--username gdexdata] [-e|--envhome $ENVHOME]
|
|
124
203
|
```
|
|
125
204
|
|
|
126
205
|
### Simple install (no sudo required, runs as current user)
|
|
@@ -131,6 +210,7 @@ direct symlink from `dsarch` to `setuid_dsarch`:
|
|
|
131
210
|
```bash
|
|
132
211
|
pip install rda_python_dsarch
|
|
133
212
|
pywrapper-install -l|--link dsarch -s|--simple
|
|
213
|
+
pywrapper-install -l|--link all -s|--simple # or link all setuid_* entries at once
|
|
134
214
|
```
|
|
135
215
|
|
|
136
216
|
## Runtime flow
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
rda_python_setuid/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
2
|
+
rda_python_setuid/install.py,sha256=IguehVEMgY08-mNZcDPL4sMX5_XF5sb71qwkzE-wTqU,12422
|
|
3
|
+
rda_python_setuid/install.usg,sha256=CEeRuUO4_3dzwlIk0uEbDl-6HtYNGm16pltBvNfJqkw,7397
|
|
4
|
+
rda_python_setuid/pgstart.py,sha256=tYCOTJedqE5Lc6hUD2tyQparsOK9VGJ2alHHLNWqF20,3968
|
|
5
|
+
rda_python_setuid/pywrapper.c,sha256=Dp9EOFU9IShduQ4WMeFRiiIJLQvgZHsiQlc89vJgtII,4190
|
|
6
|
+
rda_python_setuid/pywrapper.py,sha256=7RHpWeyHGLP_7Vx3KJ_yy-3qYObYg_ar6meuworA0MA,3023
|
|
7
|
+
rda_python_setuid/setuid_setup.usg,sha256=nJaS08GtlCQmXiy-udp6cxCFgZZJuBaZ36DicUn5Q7I,2915
|
|
8
|
+
rda_python_setuid/setup_guide.py,sha256=thdTlYJwhID63G7G9NWte0qeXwH6ytQPLFbjesWodNQ,2158
|
|
9
|
+
rda_python_setuid-3.0.0.dist-info/licenses/LICENSE,sha256=1dck4EAQwv8QweDWCXDx-4Or0S8YwiCstaso_H57Pno,1097
|
|
10
|
+
rda_python_setuid-3.0.0.dist-info/METADATA,sha256=JTvNrLDE7D3GZV1-Rr2ElyksAB8Bv0FmSVgsazKwnR0,8184
|
|
11
|
+
rda_python_setuid-3.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
12
|
+
rda_python_setuid-3.0.0.dist-info/entry_points.txt,sha256=mWZUCa2KYzGsYVa33M7W03CY9SMsJYsywlIDF-4iMaQ,165
|
|
13
|
+
rda_python_setuid-3.0.0.dist-info/top_level.txt,sha256=ONMhKLagyTBktuz5dTyipSnRC0YhomTMs8eFRjM9kHQ,18
|
|
14
|
+
rda_python_setuid-3.0.0.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
rda_python_setuid/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
2
|
-
rda_python_setuid/install.py,sha256=0tY0zo7WRyQMm0iUi1_XYyjR8SDVU17R729ehATZNKw,6071
|
|
3
|
-
rda_python_setuid/install.usg,sha256=m0tgMS_Jh9-1wxCkbbgDNVJb3MGlMSp0Ecnwc9UeQcI,5582
|
|
4
|
-
rda_python_setuid/pgstart.py,sha256=qM-ntQbYfEK1VYigXbNFBDOfW9nyaezjsjyomauMTbw,3945
|
|
5
|
-
rda_python_setuid/pywrapper.c,sha256=Dp9EOFU9IShduQ4WMeFRiiIJLQvgZHsiQlc89vJgtII,4190
|
|
6
|
-
rda_python_setuid/pywrapper.py,sha256=pyQZfITTpxQ_lVkWc_0ZJRF_sZnq8NQdOvR5lObu6mw,2898
|
|
7
|
-
rda_python_setuid-1.0.9.dist-info/licenses/LICENSE,sha256=1dck4EAQwv8QweDWCXDx-4Or0S8YwiCstaso_H57Pno,1097
|
|
8
|
-
rda_python_setuid-1.0.9.dist-info/METADATA,sha256=jFM7488gCNataDKePCLkILCVPD1xZ0i_2iAOSx46piA,5027
|
|
9
|
-
rda_python_setuid-1.0.9.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
10
|
-
rda_python_setuid-1.0.9.dist-info/entry_points.txt,sha256=mWZUCa2KYzGsYVa33M7W03CY9SMsJYsywlIDF-4iMaQ,165
|
|
11
|
-
rda_python_setuid-1.0.9.dist-info/top_level.txt,sha256=ONMhKLagyTBktuz5dTyipSnRC0YhomTMs8eFRjM9kHQ,18
|
|
12
|
-
rda_python_setuid-1.0.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|