rda-python-setuid 1.0.8__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.
@@ -15,24 +15,31 @@
15
15
  # pywrapper-install
16
16
  #
17
17
  # # 1. Compile and install pywrapper (run once per environment):
18
- # pywrapper-install -c [-u gdexdata] [-e $ENVHOME]
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 [-u zji] [-e $ENVHOME]
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 [-u gdexdata] [-e $ENVHOME]
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]
32
38
  # "setuid_dsarch" = "rda_python_dsarch.dsarch:main"
33
39
  # pip install places setuid_dsarch in the bin dir automatically.
34
- # pywrapper-install --link dsarch will chown setuid_dsarch to CommonUser and
35
- # chmod 700, so users cannot run it directly and must go through the setuid wrapper.
40
+ # pywrapper-install --link dsarch creates the symlink dsarch -> pywrapper, so
41
+ # users invoking dsarch go through the setuid wrapper which execs setuid_dsarch
42
+ # as CommonUser.
36
43
  # pywrapper-install --link dsarch --simple creates dsarch -> setuid_dsarch directly,
37
44
  # skipping setuid; the program runs as the current user.
38
45
  #
@@ -76,7 +83,7 @@ def main():
76
83
  help="Path to the venv root directory containing bin/ (default: parent of the current Python executable's bin/ dir)"
77
84
  )
78
85
  parser.add_argument(
79
- '-u', '--user', default=None,
86
+ '-n', '--username', default=None,
80
87
  help="User name to own the setuid binary (default: current login user for -p/--pgstart, gdexdata otherwise)"
81
88
  )
82
89
  parser.add_argument(
@@ -94,60 +101,105 @@ def main():
94
101
  )
95
102
  group.add_argument(
96
103
  '-l', '--link', metavar='PROGRAM',
97
- 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"
98
109
  )
99
110
  args = parser.parse_args()
100
111
 
101
- if not (args.compile or args.pgstart or args.link):
112
+ if not (args.compile or args.pgstart or args.link or args.update):
102
113
  show_usage()
103
114
 
104
- if args.user is None and not args.simple:
115
+ if args.username is None and not args.simple:
105
116
  import pwd
106
117
  if args.pgstart:
107
- args.user = pwd.getpwuid(os.getuid()).pw_name
118
+ args.username = pwd.getpwuid(os.getuid()).pw_name
108
119
  else:
109
- args.user = 'gdexdata'
120
+ args.username = 'gdexdata'
110
121
 
111
122
  bindir = os.path.join(args.envhome, 'bin') if args.envhome else get_bindir()
112
123
  pywrapper = os.path.join(bindir, 'pywrapper')
113
124
 
114
125
  if args.link:
115
- target = os.path.join(bindir, args.link)
116
- script = os.path.join(bindir, 'setuid_{}'.format(args.link))
117
- if not os.path.exists(script):
118
- print("Error: {} not found. Install the package that provides it first.".format(script))
119
- sys.exit(1)
120
- if args.simple:
121
- # Simple install: symlink PROGRAM -> setuid_PROGRAM, no setuid, runs as current user.
122
- if os.path.lexists(target):
123
- print("Already exists: {}".format(target))
124
- else:
125
- os.symlink(script, target)
126
- print("Created: {} -> setuid_{}".format(target, args.link))
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))
127
159
  else:
128
- # Mode 1: symlink PROGRAM -> pywrapper, then lock down setuid_PROGRAM
129
- # so users cannot bypass the setuid wrapper by running it directly.
130
- if os.path.lexists(target):
131
- print("Already exists: {}".format(target))
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))
132
172
  else:
133
- os.symlink(pywrapper, target)
134
- print("Created: {} -> pywrapper".format(target))
135
- # chown and chmod 700: only CommonUser can execute setuid_PROGRAM directly.
136
- # pywrapper (running as CommonUser via setuid) can still execv it, but any
137
- # direct invocation by other users will get "permission denied".
138
- run(['sudo', '-u', args.user, 'chown', args.user, script])
139
- run(['sudo', '-u', args.user, 'chmod', '700', script])
140
- print("Locked: {} (chmod 700, owned by {})".format(script, args.user))
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))
141
180
 
142
181
  elif args.pgstart:
143
- # Mode 2: copy pywrapper to pgstart_USER with setuid owned by USER
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.
144
186
  if not os.path.exists(pywrapper):
145
- print("Error: {} not found. Run pywrapper-install --user COMMONUSER first.".format(pywrapper))
187
+ print("Error: {} not found. Run pywrapper-install --compile --username COMMONUSER first.".format(pywrapper))
146
188
  sys.exit(1)
147
- target = os.path.join(bindir, 'pgstart_{}'.format(args.user))
148
- run(['sudo', '-u', args.user, 'cp', pywrapper, target])
149
- run(['sudo', '-u', args.user, 'chmod', '4750', target])
150
- print("Installed: {} (setuid, owned by {})".format(target, args.user))
189
+ target = os.path.join(bindir, 'pgstart_{}'.format(args.username))
190
+ import pwd
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))
151
203
 
152
204
  elif args.compile:
153
205
  # Compile pywrapper.c and install pywrapper with setuid
@@ -155,9 +207,80 @@ def main():
155
207
  src_dest = os.path.join(bindir, 'pywrapper.c')
156
208
  shutil.copy(src, src_dest)
157
209
  print("Copied: {}".format(src_dest))
158
- run(['sudo', '-u', args.user, 'gcc', '-o', pywrapper, src_dest])
159
- run(['sudo', '-u', args.user, 'chmod', '4750', pywrapper])
160
- print("Installed: {} (setuid, owned by {})".format(pywrapper, args.user))
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))
161
284
 
162
285
 
163
286
  if __name__ == '__main__': main()
@@ -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 [-u|--user USER] [-e|--envhome ENVHOME]
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, or -l/--link must be given to perform an
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 -u or --user USER
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,19 +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 to run arbitrary commands as themselves via the
35
- setuid wrapper. Cannot be combined with -c/--compile or -l/--link.
36
-
37
- - Option -l or --link PROGRAM
38
- Mode 1: create a symlink PROGRAM -> pywrapper in the bin/ directory, then
39
- lock down setuid_PROGRAM (chmod 700, owned by USER) so users cannot bypass
40
- the setuid wrapper by running setuid_PROGRAM directly. Cannot be combined
41
- with -c/--compile or -p/--pgstart.
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
44
+ Mode 1: create a symlink PROGRAM -> pywrapper in the bin/ directory so
45
+ that running PROGRAM invokes the setuid wrapper, which then execs
46
+ setuid_PROGRAM as CommonUser. setuid_PROGRAM keeps its default
47
+ ownership and permissions. Cannot be combined with -c/--compile or
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).
42
58
 
43
59
  - Option -s or --simple (use with -l/--link)
44
60
  Simple install: create a symlink PROGRAM -> setuid_PROGRAM directly,
45
61
  skipping the setuid mechanism entirely. The program runs as the current
46
- user with no privilege change. -u/--user is not required with this option.
62
+ user with no privilege change. -n/--username is not required with this option.
47
63
  Useful for users who do not need or cannot set up the setuid wrapper.
48
64
 
49
65
  Convention for wrapped programs:
@@ -66,8 +82,8 @@
66
82
  "setuid_dsarch" = "rda_python_dsarch.dsarch:main"
67
83
 
68
84
  pip install then places setuid_dsarch in the bin/ directory automatically.
69
- Running pywrapper-install --link will lock it down (chmod 700) so that only
70
- pywrapper (running as CommonUser via its setuid bit) can execv it.
85
+ Running pywrapper-install --link creates the symlink dsarch -> pywrapper;
86
+ pywrapper (running as CommonUser via its setuid bit) execs setuid_dsarch.
71
87
 
72
88
  Environment Setup:
73
89
 
@@ -110,10 +126,14 @@
110
126
  pywrapper-install -c
111
127
 
112
128
  3. Wire up each program as a setuid entry:
113
- pywrapper-install -l dsarch
129
+ pywrapper-install -l dsarch # one program
130
+ pywrapper-install -l all # or all setuid_* entries at once
114
131
 
115
- 4. Optionally, allow a specialist to run commands as themselves:
116
- pywrapper-install -p
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>
117
137
 
118
138
  Option B - Simple install (no sudo required, runs as current user):
119
139
 
@@ -121,7 +141,8 @@
121
141
  pip install rda_python_dsarch
122
142
 
123
143
  2. Create a direct symlink to the connector script:
124
- 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
125
146
 
126
147
  Examples:
127
148
 
@@ -134,8 +155,18 @@
134
155
  3. Wire up dsarch to run as gdexdata via pywrapper:
135
156
  pywrapper-install -l dsarch
136
157
 
137
- 4. Allow specialist zji to run commands as themselves via pgstart:
138
- pywrapper-install -p -u zji
158
+ 4. Wire up all setuid_* entries at once:
159
+ pywrapper-install -l all
139
160
 
140
- 5. Simple install of dsarch for a user who does not need setuid:
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:
141
169
  pywrapper-install -l dsarch -s
170
+
171
+ 8. Simple install of all setuid_* entries at once:
172
+ pywrapper-install -l all -s
@@ -40,7 +40,7 @@ def main():
40
40
  pglog = PgLOG()
41
41
  permit = False
42
42
  pglog.PGLOG['LOGFILE'] = "pgstart.log"
43
- aname = PgLOG.get_command()
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 == euser or ruser == pglog.PGLOG['GDEXUSER']: permit = True
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['GDEXUSER'], euser))
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
 
@@ -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)
@@ -0,0 +1,227 @@
1
+ Metadata-Version: 2.4
2
+ Name: rda_python_setuid
3
+ Version: 3.0.0
4
+ Summary: RDA Python Package to setuid for program executions as an effective or common user
5
+ Author-email: Zaihua Ji <zji@ucar.edu>
6
+ Project-URL: Homepage, https://github.com/NCAR/rda-python-setuid
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Requires-Python: >=3.7
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: rda_python_common
15
+ Dynamic: license-file
16
+
17
+ RDA Python package, including a C code wrapper, to execute commandline applications
18
+ via setuid for effective and common user names.
19
+
20
+ ## Overview
21
+
22
+ `rda_python_setuid` provides a C binary (`pywrapper`) that acquires a setuid effective
23
+ user, then `execv`s a Python entry point script. This allows Python programs to run
24
+ as a designated common user (e.g. `gdexdata`) without requiring `sudo` access.
25
+
26
+ Two modes are supported:
27
+
28
+ - **Mode 1 (CommonUser program):** a symlink `dsarch -> pywrapper` runs `setuid_dsarch`
29
+ as the common user.
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).
34
+
35
+ Two Python entry points are packaged alongside the C wrapper:
36
+
37
+ - **`pywrapper.py`** — the default fallback target executed when `pywrapper.c`
38
+ cannot resolve a matching `setuid_<program>` entry point. Acquires the
39
+ effective UID via `PgLOG.set_suid()`, prints the caller's real and effective
40
+ user names, and shows the `pyproject.toml` snippet plus the
41
+ `pywrapper-install -l <program>` command needed to wrap a new script.
42
+ Diagnostic flags `-env`, `-inc`, and `-plg` dump the environment variables,
43
+ `sys.path`, and `PGLOG` dictionary respectively — handy for verifying the
44
+ setuid environment before wiring up a real program.
45
+
46
+ - **`pgstart.py`** — the Mode 2 launcher invoked through a `pgstart_<loginname>`
47
+ copy of `pywrapper`. Reads the real/effective UIDs from `PGLOG`, then
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
52
+ an informational message and exit. After authorization it parses leading
53
+ flag tokens — `-bg` (background via `subprocess.Popen`), `-fg` (explicit
54
+ foreground, default), `-cwd <dir>` (chdir before exec), and the same
55
+ `-env`/`-inc`/`-plg` diagnostics as `pywrapper.py` — and then runs the
56
+ remaining arguments as a command (`subprocess.run`/`Popen`) under the
57
+ effective UID, logging a host/program/timestamp/user line to `pgstart.log`.
58
+
59
+ ## Dependency requirement
60
+
61
+ Any Python package whose programs are to be run via the setuid mechanism must declare
62
+ `rda_python_setuid` as a dependency in its `pyproject.toml`:
63
+
64
+ ```toml
65
+ [project]
66
+ dependencies = [
67
+ "rda_python_setuid",
68
+ ...
69
+ ]
70
+ ```
71
+
72
+ It must also register each wrapped program's connector entry point with a `setuid_`
73
+ prefix:
74
+
75
+ ```toml
76
+ [project.scripts]
77
+ "setuid_dsarch" = "rda_python_dsarch.dsarch:main"
78
+ ```
79
+
80
+ `pip install` then places `setuid_dsarch` in the environment's `bin/` directory
81
+ automatically. `pywrapper-install -l/--link` creates the symlink
82
+ `dsarch -> pywrapper`; running `dsarch` goes through the setuid wrapper, which
83
+ execs `setuid_dsarch` as CommonUser.
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
+
104
+ ## Environment setup
105
+
106
+ Create a Python environment first; package installs in the next section run
107
+ inside whichever environment you activate here.
108
+
109
+ ### Option A — Python venv (DECS machines)
110
+
111
+ ```bash
112
+ python3 -m venv $ENVHOME # e.g. /glade/u/home/gdexdata/gdexmsenv
113
+ source $ENVHOME/bin/activate
114
+ ```
115
+
116
+ ### Option B — Conda (DAV/Casper)
117
+
118
+ ```bash
119
+ conda create --prefix $ENVHOME python=3.12 # e.g. /glade/work/gdexdata/conda-envs/pg-gdex
120
+ conda activate $ENVHOME
121
+ ```
122
+
123
+ ## Installing rda-python-setuid
124
+
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.
128
+
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:
163
+
164
+ ```bash
165
+ pywrapper-install
166
+ ```
167
+
168
+ ### Full setuid setup (requires sudo access to CommonUser)
169
+
170
+ ```bash
171
+ # 1. Install the target package (pulls in rda_python_setuid automatically):
172
+ pip install rda_python_dsarch
173
+
174
+ # 2. Compile pywrapper C binary (once per environment):
175
+ pywrapper-install -c|--compile
176
+
177
+ # 3. Wire up each program as a setuid entry (specify name or use 'all'):
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
+ ```
194
+
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]
203
+ ```
204
+
205
+ ### Simple install (no sudo required, runs as current user)
206
+
207
+ Users who do not need the setuid mechanism can skip steps 2–4 and create a
208
+ direct symlink from `dsarch` to `setuid_dsarch`:
209
+
210
+ ```bash
211
+ pip install rda_python_dsarch
212
+ pywrapper-install -l|--link dsarch -s|--simple
213
+ pywrapper-install -l|--link all -s|--simple # or link all setuid_* entries at once
214
+ ```
215
+
216
+ ## Runtime flow
217
+
218
+ ```
219
+ user runs: dsarch [args]
220
+ | (symlink -> pywrapper, setuid bit -> EUID=gdexdata)
221
+ pywrapper.c: execv(bin/setuid_dsarch, args)
222
+ setuid_dsarch: calls dsarch:main() as gdexdata
223
+ ```
224
+
225
+ ## Github
226
+
227
+ <https://github.com/NCAR/rda-python-setuid>
@@ -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,147 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: rda_python_setuid
3
- Version: 1.0.8
4
- Summary: RDA Python Package to setuid for program executions as an effective or common user
5
- Author-email: Zaihua Ji <zji@ucar.edu>
6
- Project-URL: Homepage, https://github.com/NCAR/rda-python-setuid
7
- Classifier: Programming Language :: Python :: 3
8
- Classifier: License :: OSI Approved :: MIT License
9
- Classifier: Operating System :: OS Independent
10
- Classifier: Development Status :: 5 - Production/Stable
11
- Requires-Python: >=3.7
12
- Description-Content-Type: text/markdown
13
- License-File: LICENSE
14
- Requires-Dist: rda_python_common
15
- Dynamic: license-file
16
-
17
- RDA Python package, including a C code wrapper, to execute commandline applications
18
- via setuid for effective and common user names.
19
-
20
- ## Overview
21
-
22
- `rda_python_setuid` provides a C binary (`pywrapper`) that acquires a setuid effective
23
- user, then `execv`s a Python entry point script. This allows Python programs to run
24
- as a designated common user (e.g. `gdexdata`) without requiring `sudo` access.
25
-
26
- Two modes are supported:
27
-
28
- - **Mode 1 (CommonUser program):** a symlink `dsarch -> pywrapper` runs `setuid_dsarch`
29
- as the common user.
30
- - **Mode 2 (pgstart specialist):** a copy `pgstart_zji` runs any command as specialist
31
- `zji` via `pgstart.py`, restricted to authorized users.
32
-
33
- Two Python entry points are packaged alongside the C wrapper:
34
-
35
- - **`pywrapper.py`** — the default fallback target executed when `pywrapper.c`
36
- cannot resolve a matching `setuid_<program>` entry point. Acquires the
37
- effective UID via `PgLOG.set_suid()`, prints the caller's real and effective
38
- user names, and shows the `pyproject.toml` snippet plus the
39
- `pywrapper-install -l <program>` command needed to wrap a new script.
40
- Diagnostic flags `-env`, `-inc`, and `-plg` dump the environment variables,
41
- `sys.path`, and `PGLOG` dictionary respectively — handy for verifying the
42
- setuid environment before wiring up a real program.
43
-
44
- - **`pgstart.py`** — the Mode 2 launcher invoked through a `pgstart_<USER>`
45
- copy of `pywrapper`. Reads the real/effective UIDs from `PGLOG`, then
46
- permits execution only if the real user matches the effective user or the
47
- shared GDEX common user (`PGLOG['GDEXUSER']`); unauthorized callers receive
48
- an informational message and exit. After authorization it parses leading
49
- flag tokens — `-bg` (background via `subprocess.Popen`), `-fg` (explicit
50
- foreground, default), `-cwd <dir>` (chdir before exec), and the same
51
- `-env`/`-inc`/`-plg` diagnostics as `pywrapper.py` — and then runs the
52
- remaining arguments as a command (`subprocess.run`/`Popen`) under the
53
- effective UID, logging a host/program/timestamp/user line to `pgstart.log`.
54
-
55
- ## Dependency requirement
56
-
57
- Any Python package whose programs are to be run via the setuid mechanism must declare
58
- `rda_python_setuid` as a dependency in its `pyproject.toml`:
59
-
60
- ```toml
61
- [project]
62
- dependencies = [
63
- "rda_python_setuid",
64
- ...
65
- ]
66
- ```
67
-
68
- It must also register each wrapped program's connector entry point with a `setuid_`
69
- prefix:
70
-
71
- ```toml
72
- [project.scripts]
73
- "setuid_dsarch" = "rda_python_dsarch.dsarch:main"
74
- ```
75
-
76
- `pip install` then places `setuid_dsarch` in the environment's `bin/` directory
77
- automatically. `pywrapper-install -l/--link` locks it down to `chmod 700` so users
78
- cannot bypass the setuid wrapper by running it directly.
79
-
80
- ## Environment setup
81
-
82
- ### Option A — Python venv (DECS machines)
83
-
84
- ```bash
85
- python3 -m venv $ENVHOME # e.g. /glade/u/home/gdexdata/gdexmsenv
86
- source $ENVHOME/bin/activate
87
- pip install rda_python_setuid rda_python_dsarch ...
88
- ```
89
-
90
- ### Option B — Conda (DAV/Casper)
91
-
92
- ```bash
93
- conda create -n pg-gdex python=3.10
94
- conda activate pg-gdex
95
- pip install rda_python_setuid rda_python_dsarch ...
96
- ```
97
-
98
- The conda environment is typically at `/glade/work/gdexdata/conda-envs/pg-gdex`.
99
-
100
- ## Installation
101
-
102
- After setting up the environment and installing packages, run `pywrapper-install`
103
- with no arguments to display the full user guide:
104
-
105
- ```bash
106
- pywrapper-install
107
- ```
108
-
109
- ### Full setuid setup (requires sudo access to CommonUser)
110
-
111
- ```bash
112
- # 1. Install the target package (pulls in rda_python_setuid automatically):
113
- pip install rda_python_dsarch
114
-
115
- # 2. Compile pywrapper C binary (once per environment):
116
- pywrapper-install -c|--compile
117
-
118
- # 3. Wire up each program as a setuid entry:
119
- pywrapper-install -l|--link dsarch
120
-
121
- # 4. Optionally, allow a specialist to run commands as themselves:
122
- pywrapper-install -p|--pgstart -u|--user zji
123
- ```
124
-
125
- ### Simple install (no sudo required, runs as current user)
126
-
127
- Users who do not need the setuid mechanism can skip steps 2–4 and create a
128
- direct symlink from `dsarch` to `setuid_dsarch`:
129
-
130
- ```bash
131
- pip install rda_python_dsarch
132
- pywrapper-install -l|--link dsarch -s|--simple
133
- ```
134
-
135
- ## Runtime flow
136
-
137
- ```
138
- user runs: dsarch [args]
139
- | (symlink -> pywrapper, setuid bit -> EUID=gdexdata)
140
- pywrapper.c: execv(bin/setuid_dsarch, args)
141
- | (chmod 700, only gdexdata can exec directly)
142
- setuid_dsarch: calls dsarch:main() as gdexdata
143
- ```
144
-
145
- ## Github
146
-
147
- <https://github.com/NCAR/rda-python-setuid>
@@ -1,12 +0,0 @@
1
- rda_python_setuid/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
2
- rda_python_setuid/install.py,sha256=-YoqQ9OTguOwVaWGOkVEQgzZMxfpHIyG4MEM_oRA8oE,6379
3
- rda_python_setuid/install.usg,sha256=V2sbJo5hTB4hWKGZClAHBojm7wg1B1cQRZN7NdvTEHo,5543
4
- rda_python_setuid/pgstart.py,sha256=dzYzuc2ce60RNx7FBxVhUgHYwlVnbiZcCrxVQ6MvZZ4,3909
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.8.dist-info/licenses/LICENSE,sha256=1dck4EAQwv8QweDWCXDx-4Or0S8YwiCstaso_H57Pno,1097
8
- rda_python_setuid-1.0.8.dist-info/METADATA,sha256=sxD3iWI8gQ0iHRxYJdN853asC8dlvKP1yMae1aYOJJQ,5048
9
- rda_python_setuid-1.0.8.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
10
- rda_python_setuid-1.0.8.dist-info/entry_points.txt,sha256=mWZUCa2KYzGsYVa33M7W03CY9SMsJYsywlIDF-4iMaQ,165
11
- rda_python_setuid-1.0.8.dist-info/top_level.txt,sha256=ONMhKLagyTBktuz5dTyipSnRC0YhomTMs8eFRjM9kHQ,18
12
- rda_python_setuid-1.0.8.dist-info/RECORD,,