summaryrefslogtreecommitdiff
path: root/utils/su.c
blob: 4efc7bd30689309001adfe105cf4536c21ec60b0 (plain)
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
/* `su' for GNU Hurd.
   Copyright (C) 1994 Free Software Foundation
   Written by Roland McGrath.

This file is part of the GNU Hurd.

The GNU Hurd is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

The GNU Hurd is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with the GNU Hurd; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

   Unlike Unix su, this program does not spawn a subshell.  Instead, it
   adds or removes (with the -r flag) auth rights to all the processes in
   the current login.  Note that the addition and removal of rights to a
   particular process is voluntary; the process doesn't get them unless
   it's listening for the right rpc (which the C library normally does),
   and it doesn't have to release any of the rights it has when requested
   (though the C library does this automatically in most programs).

   The -r flag allows you to easily be authorized or not authorized for any
   number of users (uids and gid sets).  This program is not intelligent,
   however.  The -r flag always removes whatever gids the specified user
   has.  If some other user you have obtained authorization for has some of
   the same gids, it will remove them.  The C library could be intelligent
   enough to look up the groups of all the uids it has, and make sure it
   has at least those.  */

#include <stdlib.h>
#include <hurd.h>
#include <pwd.h>
#include <grp.h>
#include <termios.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <hurd/msg.h>
#include <assert.h>

process_t proc;			/* Our proc server port. */

/* Command line flags.  */
int remove_ids = 0;		/* -r: Remove ids instead of adding them.  */

const struct option longopts[] =
  {
    { "remove", 0, 0, 'r' },
    { "uid", 0, 0, 'u' },
    { "gid", 0, 0, 'g' },
    { "loginuser", 0, 0, 'l' },
    { "pid", 0, 0, 'p' },
    { "pgrp", 0, 0, 'G' },
    { "loginid", 0, 0, 'i' },
    { 0, }
  };

struct auth
  {
    unsigned int *ids;
    auth_t authport;
  };

/* These functions return a malloc'd array of ids that can be passed to
   make_auth_handle or used in del_auth messages.  They check authorization
   and return NULL on failure.  */
unsigned int *get_uid (const char *user); /* From a named user or UID.  */
unsigned int *get_gid (const char *user); /* From a named group or GID.  */
unsigned int *get_user (const char *user); /* All ids for a named user.  */

int apply_auth (struct auth *auth, pid_t, int);
int apply_auth_to_pids (struct auth *auth, unsigned int, pid_t *, int);
int apply_auth_to_pgrp (struct auth *auth, pid_t);
int apply_auth_to_loginid (struct auth *auth, int);

int check_password (const char *name, const char *password);


int
main (int argc, char **argv)
{
  int c;
  int status;
  enum { user, group, loginuser, loginid, pid, pgrp } mode = loginuser;
  struct auth auth[argc];
  unsigned int authidx = 0, loginidx = 0, pididx = 0, pgrpidx = 0;
  int loginids[argc];
  pid_t pids[argc], pgrps[argc];
  unsigned int i, j;

  proc = getproc ();		/* Used often.  */

  while ((c = getopt_long (argc, argv, "--rgulpiG", longopts, NULL)) != EOF)
    switch (c)
      {
      case 'r':
	remove_ids = 1;
	break;

      case 'u':
	mode = user;
	break;
      case 'g':
	mode = group;
	break;
      case 'l':
	mode = loginuser;
	break;
      case 'p':
	mode = pid;
	break;
      case 'i':
	mode = loginid;
	break;
      case 'G':
	mode = pgrp;
	break;

      case '\0':		/* Non-option argument.  */
	switch (mode)
	  {
	  case user:
	    auth[authidx++].ids = get_uid (optarg);
	    break;
	  case group:
	    auth[authidx++].ids = get_gid (optarg);
	    break;
	  case loginuser:
	    auth[authidx++].ids = get_user (optarg);
	    break;

	  case pid:
	    pids[pididx++] = atoi (optarg);
	    break;
	  case loginid:
	    loginids[loginidx++] = atoi (optarg);
	    break;
	  case pgrp:
	    pgrps[pgrpidx++] = atoi (optarg);
	    break;
	  }
	break;

      case '?':
      default:
	fprintf (stderr, "Usage: %s [-r|--remove]\n\
	[-u|--uid UID...] [-g|--gid GID...] [-l|--loginuser USER...]\n\
	[-p|--pid PID...] [-i|--loginid ID...] [-G|--pgrp PGRP...]\n",
		 program_invocation_short_name);
	exit (1);
      }

  if (authidx == 0)
    /* No ids specified; default is "su root".  */
    auth[authidx++].ids = get_user ("root");

  if (pididx == 0 && loginidx == 0 && pgrpidx == 0)
    /* No processes specified; default is current login collection.  */
    if (errno = proc_getloginid (proc, getpid (), &loginids[loginidx++]))
      {
	perror ("proc_getloginid");
	return 1;
      }

  status = 0;

  if (! remove_ids)
    for (i = 0; i < authidx; ++i)
      {
	struct auth *a = &auth[i];
	if (errno = auth_makeauth (getauth (),
				   NULL, MACH_MSG_TYPE_COPY_SEND, 0,
				   &a->ids[1], a->ids[0], NULL, 0,
				   &a->ids[1 + a->ids[0] + 1],
				   a->ids[1 + a->ids[0]], NULL, 0,
				   &a->authport))
	  {
	    perror ("auth_makeauth");
	    status = 1;
	  }
      }

  for (j = 0; j < authidx; ++j)
    status |= apply_auth_to_pids (&auth[j], pididx, pids, 0);

  for (i = 0; i < loginidx; ++i)
    {
      for (j = 0; j < authidx; ++j)
	status |= apply_auth_to_loginid (&auth[j], loginids[i]);
    }

  for (i = 0; i < pgrpidx; ++i)
    {
      for (j = 0; j < authidx; ++j)
	status |= apply_auth_to_loginid (&auth[j], pgrps[i]);
    }
		

  return status;
}

/* Turn USER into a list of ids, giving USER's primary uid and gid from the
   passwd file and all the groups that USER appears in in the group file.  */

unsigned int *
get_user (const char *user)
{
  struct passwd *p;
  unsigned int ngids, *ids;

  p = getpwnam (user);
  if (p == NULL)
    {
      fprintf (stderr, "%s: User `%s' not found\n",
	       program_invocation_short_name, user);
      return NULL;
    }

  if (! check_password (user, p->pw_passwd))
    return NULL;

  /* We don't need to change our own gids, but it doesn't really hurt,
     and initgroups does all the work for us.  */
  if (initgroups (user, p->pw_gid) < 0)
    {
      perror ("initgroups");
      return NULL;
    }
  ngids = getgroups (0, NULL);
  if ((int) ngids < 0)
    {
    getgroups_lost:
      perror ("getgroups");
      return NULL;
    }
  assert (sizeof (*ids) == sizeof (gid_t));
  ids = malloc ((3 + ngids) * sizeof (*ids));
  ids[0] = 1;
  ids[1] = p->pw_uid;
  ids[2] = getgroups (ngids, &ids[3]);
  if ((int) ids[2] < 0)
    goto getgroups_lost;

  return ids;
}

/* Return an id list containing just the uid of USER.  */

unsigned int *
get_uid (const char *user)
{
  struct passwd *p;
  unsigned int *ids;
  uid_t uid;
  char *uend;

  uid = strtoul (user, &uend, 10);
  if (uend && *uend == '\0')
    {
      if (remove_ids || getuid () == 0)
	/* No need to verify.  */
	p = NULL;
      else
	{
	  p = getpwuid (uid);
	  if (p == NULL)
	    {
	      fprintf (stderr, "%s: UID %u not found\n",
		       program_invocation_short_name, uid);
	      return NULL;
	    }
	}
    }
  else
    {
      p = getpwnam (user);
      if (p == NULL)
	{
	  fprintf (stderr, "%s: User `%s' not found\n",
		   program_invocation_short_name, user);
	  return NULL;
	}
    }

  if (p != NULL && ! check_password (user, p->pw_passwd))
    return NULL;

  ids = malloc (3 * sizeof (*ids));
  ids[0] = 1;
  ids[1] = p->pw_uid;
  ids[2] = 0;

  return ids;
}

/* Return an id list containing just the gid of GROUP.  */

unsigned int *
get_gid (const char *group)
{
  struct group *g;
  unsigned int *ids;
  gid_t gid;
  char *gend;

  gid = strtoul (group, &gend, 10);
  if (gend && *gend == '\0')
    {
      if (remove_ids || getuid () == 0)
	/* No need to verify.  */
	g = NULL;
      else
	{
	  g = getgrgid (gid);
	  if (g == NULL)
	    {
	      fprintf (stderr, "%s: GID %u not found\n",
		       program_invocation_short_name, gid);
	      return NULL;
	    }
	}
    }
  else
    {
      g = getgrnam (group);
      if (g == NULL)
	{
	  fprintf (stderr, "%s: Group `%s' not found\n",
		   program_invocation_short_name, group);
	  return NULL;
	}
    }

  if (g != NULL && ! check_password (group, g->gr_passwd))
    return NULL;

  ids = malloc (3 * sizeof (*ids));
  ids[0] = 0;
  ids[1] = 1;
  ids[2] = g->gr_gid;

  return ids;
}

/* Add or delete (under -r) the ids indicated by AUTH to/from PID.  If
   IGNORE_BAD_PID is nonzero, return success if PID does not exist.
   Returns zero if successful, nonzero on error (after printing message).  */

int
apply_auth (struct auth *auth, pid_t pid, int ignore_bad_pid)
{
  error_t err;

  if (! auth->ids)
    return 0;

  err = HURD_MSGPORT_RPC (proc_getmsgport (proc, pid, &msgport),
			  proc_pid2task (proc, pid, &refport), 1,
			  remove_ids ?
			  msg_del_auth (msgport, refport,
					&auth->ids[1], auth->ids[0],
					&auth->ids[1 + auth->ids[0] + 1],
					auth->ids[1 + auth->ids[0]]) :
			  msg_add_auth (msgport, auth->authport));
  if (err &&
      (!ignore_bad_pid || (err != ESRCH && err != MIG_SERVER_DIED)))
    {
      fprintf (stderr, "%s: error in %s_auth from PID %d: %s\n",
	       program_invocation_short_name,
	       remove_ids ? "del" : "add", pid, strerror (err));
      return 1;
    }
  else
    return 0;
}

int
apply_auth_to_pids (struct auth *auth, unsigned int npids, pid_t pids[],
		   int ignore_bad_pid)
{
  int status = 0;
  unsigned int i;

  for (i = 0; i < npids; ++i)
    status |= apply_auth (auth, pids[i], ignore_bad_pid);

  return status;
}

int
apply_auth_to_loginid (struct auth *auth, int loginid)
{
  unsigned int npids = 20;
  pid_t pidbuf[20], *pids = pidbuf;
  int status;
  error_t err;

  if (err = proc_getloginpids (proc, loginid, &pids, &npids))
    {
      fprintf (stderr, "%s: proc_getloginpids failed for loginid %d: %s\n",
	       program_invocation_short_name, loginid, strerror (err));
      return 1;
    }

  status = apply_auth_to_pids (auth, npids, pids, 1);

  if (pids != pidbuf)
    vm_deallocate (mach_task_self (),
		   (vm_address_t) pids, npids * sizeof (pid_t));

  return status;
}

int
apply_auth_to_pgrp (struct auth *auth, pid_t pgrp)
{
  unsigned int npids = 20;
  pid_t pidbuf[20], *pids = pidbuf;
  int status;
  error_t err;

  if (err = proc_getpgrppids (proc, pgrp, &pids, &npids))
    {
      fprintf (stderr, "%s: proc_getpgrppids failed for pgrp %d: %s\n",
	       program_invocation_short_name, pgrp, strerror (err));
      return 1;
    }

  status = apply_auth_to_pids (auth, npids, pids, 1);

  if (pids != pidbuf)
    vm_deallocate (mach_task_self (),
		   (vm_address_t) pids, npids * sizeof (pid_t));

  return status;
}

/* Return 1 if the user gives the correct password matching the encrypted
   string PASSWORD, 0 if not.  Return 1 without asking for a password if
   run by uid 0 or if PASSWORD is an empty password, and always under -r.
   Always prints a message before returning 0.  */

int
check_password (const char *name, const char *password)
{
  extern char *crypt (const char salt[2], const char *string);
  char *unencrypted, *encrypted;
  static char *prompt = NULL;

  if (remove_ids || getuid () == 0 || password == NULL || password[0] == '\0')
    return 1;

  asprintf (&prompt, "%s's Password:", name);
  unencrypted = getpass (prompt);
  encrypted = crypt (unencrypted, password);
  memset (unencrypted, 0, strlen (unencrypted)); /* Paranoia may destroya.  */

  if (!strcmp (encrypted, password))
    return 1;

  fprintf (stderr, "%s: Access denied for `%s'\n",
	   program_invocation_short_name, name);
  return 0;
}