ちょっとしたユーティリティ

今から20年以上も前に、仕事でとある米国製ネットワーク管理ツールを扱うことになり、そのソースライセンスを受けたわけです。その設計思想や先進性にそれはそれはとてもシビレたもので、その時の経験が今の自分の礎ともなっているわけです。

で、そのツールをビルドする仕掛けの中に、任意のコマンドを手軽にスーパーユーザー権限で実行するためのユーティリティが含まれていたわけで、これがまた非常に便利だったわけです。

なぜかそのユーティリティはソースが提供されていなかったので実行ファイルだけをあちこちで使い回していたのですが、他のOSでも使いたいなぁと思い、その外部仕様を元に書き起こしたソースがあったはずですが、なぜか行方不明になってしまい見つかりません。仕方がないので2年ほど前にCプログラミングの感覚を思い出しがてらスクラッチから書いたものがこれです。後から思ったのですが、sudoなどを改造する方が良かったかもしれません。

/*
** bang.c - run any command as superuser
**
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

/* userlist must be owned by root:root with 0400 permission */
/* just write username on the list */
#define	USERLIST	"/usr/local/etc/bang.allow"

void usage(char *s)
{
    fprintf(stderr, "usage: %s [args] ...\n", s);
    exit(1);
}

void switch_to_root()
{
    char *me, listuser[80+1];
    FILE *fp;
    uid_t uid, euid;
    gid_t gid, egid;
    struct stat stat_buf;

    if ((me = getlogin()) == NULL) {
	perror("getlogin");
 	exit(1);
    }

    /* return if I'm already super user */
    if ((uid = getuid()) == 0) {
        return;
    }
    euid = geteuid();
    if (setresuid(euid, euid, uid) < 0) {
        perror("setuid");
        exit(1);
    }

    gid = getgid();
    egid = getegid();
    if (setresgid(egid, egid, gid) < 0) {
        perror("setgid");
        exit(1);
    }

    if (stat(USERLIST, &stat_buf) < 0) {
	perror("stat");
	exit(1);
    }
    if (stat_buf.st_uid != 0 || stat_buf.st_gid != 0 ||
	(stat_buf.st_mode & S_IFREG) == 0 ||
	(stat_buf.st_mode & S_IRUSR)!= S_IRUSR ||
	(stat_buf.st_mode & (S_IWUSR | S_IXUSR)) != 0 ||
	(stat_buf.st_mode & (S_IRGRP | S_IWGRP | S_IXGRP)) != 0 ||
	(stat_buf.st_mode & (S_IROTH | S_IWOTH | S_IXOTH)) != 0) {
	/* if file mode is not 0400 root root */
	fprintf(stderr, "userlist may be malformed\n");
	exit(1);
    }
    if ((fp = fopen(USERLIST, "r")) == NULL) {
	fprintf(stderr, "userlist is not available\n");
	exit(1);
    }
    while (fgets(listuser, 80, fp) != NULL) {
	listuser[strlen(listuser) - 1] = '\0';
	if (strcmp(me, listuser) == 0) {
	    fclose(fp);
	    return;
	}
    }
    fprintf(stderr, "not on access list\n");
    fclose(fp);
    exit(1);
}

int main(int argc, char *argv[])
{
    char **newargv;
    char *cmd;
    int i;

    if (argc >= 2) {
	cmd = argv[1];
    }
    else {
	/* original action does not work well on bash environment */
	/* so this version needs any command to be executed */
#if 0
	if ((cmd = getenv("SHELL")) == NULL) {
	    perror("getenv");
	    return 1;
	}
#else
	usage(argv[0]);
#endif
    }
    if ((newargv = (char **)malloc((sizeof (char *)) * argc + 1)) == NULL) {
	perror("malloc");
	return 1;
    }

    for (i = 0; i < argc; i++) {
	newargv[i] = argv[i + 1];
    }

    switch_to_root();
    return execvp(cmd, newargv);
}

本来の仕様では、引数なしで実行すると$SHELLにセットされているものを単に動かしてくれるのですが、bashがセットされてるとなぜかうまく動かないので、このバージョンでは引数必須にしてます。

これを普通にコンパイル・リンクして、このパーミッションで配置すればOK。

pi@raspie ~ $ ls -l /usr/local/bin/! /usr/local/etc/bang.allow 
-r-sr-sr-x 1 root root 7837  1月 19 19:19 /usr/local/bin/!
-r-------- 1 root root   10  1月 19 19:21 /usr/local/etc/bang.allow
pi@raspie ~ $ ! cat /usr/local/etc/bang.allow 
pi
chitto
pi@raspie ~ $

コメントを残す

メールアドレスが公開されることはありません。

空欄に数字または演算子を入れて計算式を完成させてください。