~sircmpwn/ctools

Implement "id" v2 PROPOSED

Stefan Tatschner: 1
 Implement "id"

 5 files changed, 507 insertions(+), 1 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~sircmpwn/ctools/patches/9663/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH v2] Implement "id" Export this patch

---
v2 -> v1: Added implementation of getgrouplist() to comply to POSIX.

 STATUS          |   2 +-
 doc/id.1.scd    |  50 ++++++
 doc/meson.build |   1 +
 meson.build     |   1 +
 src/id.c        | 454 ++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 507 insertions(+), 1 deletion(-)
 create mode 100644 doc/id.1.scd
 create mode 100644 src/id.c

diff --git a/STATUS b/STATUS
index a4069c3..84fb712 100644
--- a/STATUS
+++ b/STATUS
@@ -63,7 +63,7 @@ T       grep
      N hash
  D     head
    W   iconv
T       id
  D     id
T       ipcrm*
T       ipcs*
    W   jobs
diff --git a/doc/id.1.scd b/doc/id.1.scd
new file mode 100644
index 0000000..069a171
--- /dev/null
+++ b/doc/id.1.scd
@@ -0,0 +1,50 @@
id(1) "ctools"

# NAME

id - return user identity

# SYNOPSIS

*id* [_user_]
*id* −G [−n] [_user_]
*id* −g [−nr] [_user_]
*id* −u [−nr] [_user_]

# DESCRIPTION

If no user operand is provided, the id utility writes the user and
group IDs and the corresponding user and group names of the invoking
process to standard output.

# OPTIONS

*−G*
	Output all different group IDs (effective, real, and
	supplementary) only, using the format "%u\n".  If there is
	more than one distinct group affiliation, output each such
	affiliation, using the format " %u", before the <newline> is
	output.

*−g*
	Output only the effective group ID, using the format "%u\n".

*−n*
	Output the name in the format "%s" instead of the numeric ID
	using the format "%u".

*−r*
	Output the real ID instead of the effective ID.

*−u*
	Output only the effective user ID, using the format "%u\n".

*user*
	The login name for which information is to be written.

# DISCLAIMER

This command is part of ctools and is compatible with POSIX-1.2017, and may
optionally support XSI extensions. This man page is not intended to be a
complete reference, and where it disagrees with the specification, the
specification takes precedence.
diff --git a/doc/meson.build b/doc/meson.build
index 1fc4387..2553a1f 100644
--- a/doc/meson.build
+++ b/doc/meson.build
@@ -18,6 +18,7 @@ man_files = [
	'false.1',
	'fold.1',
	'head.1',
	'id.1',
	'link.1',
	'logname.1',
	'nice.1',
diff --git a/meson.build b/meson.build
index fbf4840..13282a2 100644
--- a/meson.build
+++ b/meson.build
@@ -25,6 +25,7 @@ oneshots = [
	'false',
	'fold',
	'head',
	'id',
	'logname',
	'nice', # Included in base but only effective under XSI
	'nohup',
diff --git a/src/id.c b/src/id.c
new file mode 100644
index 0000000..d087b7b
--- /dev/null
+++ b/src/id.c
@@ -0,0 +1,454 @@
#include <errno.h>
#include <getopt.h>
#include <grp.h>
#include <pwd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

static void
usage(void)
{
	fprintf(stderr, "usage: id [user]\n");
	fprintf(stderr, "       id −G [−n] [user]\n");
	fprintf(stderr, "       id -g [−nr] [user]\n");
	fprintf(stderr, "       id -u [−nr] [user]\n");
}

static char
*user2str(uid_t uid, bool name)
{
	char *str = NULL;
	struct passwd *pwd = NULL;

	if (name) {
		errno = 0;
		pwd = getpwuid(uid);
		if (!pwd) {
			if (errno != 0) {
				perror("getpwuid");
				return NULL;
			}

			fprintf(stderr, "user %u not found\n", uid);
			return NULL;
		}

		str = strdup(pwd->pw_name);
	} else {
		/* This allows large enough user ids… */
		str = calloc(64, sizeof(char));
		if (!str) {
			perror("calloc");
			return NULL;
		}
		snprintf(str, 64, "%u", uid);
	}

	return str;
}

static char
*group2str(gid_t gid, bool name)
{
	char *str = NULL;
	struct group *grp = NULL;

	if (name) {
		errno = 0;
		grp = getgrgid(gid);
		if (!grp) {
			if (errno != 0) {
				perror("getgrgid");
				return NULL;
			}

			fprintf(stderr, "group %u not found\n", gid);
			return NULL;
		}

		str = strdup(grp->gr_name);
	} else {
		/* This allows large enough group ids… */
		str = calloc(64, sizeof(char));
		if (!str) {
			perror("calloc");
			return NULL;
		}
		snprintf(str, 64, "%u", gid);
	}

	return str;
}

static int
user2uid(char *user)
{
	struct passwd *pwd;

	errno = 0;
	pwd = getpwnam(user);
	if (!pwd) {
		if (errno != 0) {
			perror("getpwnam");
			return -1;
		}

		fprintf(stderr, "user %s not found\n", user);
		return -1;
	}

	return pwd->pw_uid;
}

static int
user2gid(char *user)
{
	struct passwd *pwd;

	errno = 0;
	pwd = getpwnam(user);
	if (!pwd) {
		if (errno != 0) {
			perror("getpwnam");
			return -1;
		}

		fprintf(stderr, "user %s not found\n", user);
		return -1;
	}

	return pwd->pw_gid;
}

static int
print_user(uid_t uid)
{
	char *str;

	str = user2str(uid, false);
	if (!str) {
		return 1;
	}

	printf("%s", str);
	free(str);

	str = user2str(uid, true);
	if (!str) {
		return 1;
	}

	printf("(%s)", str);
	free(str);

	return 0;
}

static int
print_group(gid_t gid)
{
	char *str;

	str = group2str(gid, false);
	if (!str) {
		return 1;
	}

	printf("%s", str);
	free(str);

	str = group2str(gid, true);
	if (!str) {
		return 1;
	}

	printf("(%s)", str);
	free(str);

	return 0;
}

static int
getgrouplist(const char *user, gid_t group, gid_t *groups, int *ngroups)
{
	int nmax = *ngroups;
	struct group *grp = getgrent();

	groups[0] = group;
	*ngroups = 1;
	while (grp) {
		for (int i = 0; grp->gr_mem[i]; i++) {
			if (strcmp(grp->gr_mem[i], user) != 0) {
				continue;
			}
			if (i >= nmax) {
				return -1;
			}

			groups[*ngroups] = grp->gr_gid;
			(*ngroups)++;
		}

		grp = getgrent();
	}

	return *ngroups;
}

static int
group_list(uid_t uid, gid_t **groups, int *n)
{
	int r = -1;
	struct passwd *pwd;

	errno = 0;
	pwd = getpwuid(uid);
	if (!pwd) {
		if (errno != 0) {
			perror("getpwuid");
			goto out;
		}

		fprintf(stderr, "user %u not found\n", uid);
		goto out;
	}

	*n = sysconf(_SC_NGROUPS_MAX) + 1;
	*groups = calloc(*n, sizeof(gid_t));
	if (!*groups) {
		perror("calloc");
		goto error;
	}

	r = getgrouplist(pwd->pw_name, pwd->pw_gid, *groups, n);
	if (r < 0) {
		fprintf(stderr, "getgrouplist() failed\n");
		goto error;
	}

	r = 0;
out:
	return r;

error:
	free(*groups);
	return r;
}

static int
print_groups(uid_t uid)
{
	int n, r;
	gid_t *groups;

	r = group_list(uid, &groups, &n);
	if (r != 0) {
		return -1;
	}

	for (int i = 0; i < n; i++) {
		print_group(groups[i]);
		if (i < n-1) {
			printf(",");
		}
	}

	free(groups);
	return 0;
}

static int
print_grouplist(uid_t uid, bool name)
{
	int n, r;
	gid_t *groups;

	r = group_list(uid, &groups, &n);
	if (r != 0) {
		return -1;
	}

	char *str;
	for (int i = 0; i < n; i++) {
		str = group2str(groups[i], name);
		if (!str) {
			free(groups);
			return -1;
		}

		printf("%s", str);
		free(str);

		if (i < n-1) {
			printf(" ");
		}
	}

	free(groups);
	return 0;
}

struct options {
	char *user;
	bool allgroups; /* -G */
	bool grouponly; /* -g */
	bool noid;      /* -n */
	bool realid;    /* -r */
	bool useronly;  /* -u */
};

static int
id(struct options *opts)
{
	int r = -1;
	uid_t uid = getuid();
	gid_t gid = getgid();
	uid_t euid = geteuid();
	gid_t egid = getegid();

	if (opts->user) {
		int _uid;
		_uid = user2uid(opts->user);
		if (_uid == -1) {
			goto out;
		}

		int _gid;
		_gid = user2gid(opts->user);
		if (_gid == -1) {
			goto out;
		}

		/* Be careful about signed vs. unsigned comparison. */
		uid = (uid_t) _uid;
		gid = (gid_t) _gid;
		euid = (uid_t) _uid;
		egid = (gid_t) _gid;
	}

	if (!opts->allgroups && !opts->grouponly && !opts->useronly) {
		printf("uid=");

		r = print_user(uid);
		if (r != 0) {
			goto out;
		}

		printf(" gid=");

		r = print_group(gid);
		if (r != 0) {
			goto out;
		}

		if (uid != euid) {
			printf(" euid=");

			r = print_user(euid);
			if (r != 0) {
				goto out;
			}
		}

		if (gid != egid) {
			printf(" egid=");

			r = print_group(egid);
			if (r != 0) {
				goto out;
			}
		}

		printf(" groups=");

		r = print_groups(uid);
		if (r != 0) {
			goto out;
		}

		printf("\n");
	} else if (opts->allgroups) {
		r = print_grouplist(uid, opts->noid);
		if (r != 0) {
			goto out;
		}
		printf("\n");
	} else if (opts->grouponly) {
		char *str;
		gid_t _gid = opts->realid ? gid : egid;

		str = group2str(_gid, opts->noid);
		if (!str) {
			r = -1;
			goto out;
		}

		printf("%s\n", str);
		free(str);
	} else if (opts->useronly) {
		char *str;
		uid_t _uid = opts->realid ? uid : euid;

		str = user2str(_uid, opts->noid);
		if (!str) {
			r = -1;
			goto out;
		}

		printf("%s\n", str);
		free(str);
	} else {
		fprintf(stderr, "invalid option combination\n");
		usage();
		r = -1;
		goto out;
	}

out:
	return r;
}

int
main(int argc, char *argv[])
{
	char opt;
	struct options opts = {};

	while ((opt = getopt(argc, argv, "Ggunrh")) != -1) {
		switch (opt) {
		case 'G':
			opts.allgroups = true;
			break;
		case 'g':
			opts.grouponly = true;
			break;
		case 'n':
			opts.noid = true;
			break;
		case 'r':
			opts.realid = true;
			break;
		case 'u':
			opts.useronly = true;
			break;
		case 'h':
			usage();
			return 0;
		default:
			usage();
			return 1;
		}
	}

	if (optind < argc) {
		opts.user = argv[optind];
	}

	int r = id(&opts);

	return r != 0 ? 1 : 0;
}
-- 
2.25.0
Don't have time for a detailed review right now, but noticed this:

On Wed Jan 29, 2020 at 12:01 AM, Stefan Tatschner wrote: