永井@知能.九工大です.

ruby-dev ML で議論された話題ではありますが,
より広く意見を求めるため,ruby-list ML にも流します.
少々長文になりますが,ご了承下さい.

今から 2 年ほど前に議論された一つの話題があります.
緒事情あってその後長期間放置されたままだったのですが,
まつもとさんからのお声掛かりもあり,
きちんと決着をつけるために復活しました.(^_^)

そのテーマは,簡単に言うと

「ポータビリティの高いユーザ ID/グループ ID の操作メソッドとは?」

というものです.

問題の発端は,プロセスのユーザ ID/グループ ID を操作するための
メソッドとしては,現在の Process.uid=, Process.euid= などには
非常に問題があるのではないかということでした.
これらのメソッドはシステムコールを利用して実ユーザ ID 等を
操作するものではあるのですが,その際に用いられるシステムコールが
OS 依存であり,しかもどのシステムコールが用いられているかを
確認することができないため,非常に使いづらいものになっています.

ソースを見てもらうとわかりますが,例えば Process.uid= 一つ取っても
実ユーザ ID が変化するだけの環境もあれば,実/実行/保存のすべての
ユーザ ID が変化してしまう環境もあります.
また,root で起動した後に特定のユーザ権限に落とすために 
Process.uid= と Process.euid= とを使っても,環境によっては
保存ユーザ ID が変更されず,完全な権限放棄が実現できないという
環境もあります.

こうした状況では,権限をコントロールするようなちょっとした
スクリプトを書こうとしても,適切に書けないか,特定の環境に
依存したポータビリティに書けるものにしかなりません.
さらには,setuid や setreuid などのシステムコールに
直接対応するメソッドも用意されていません.
ただし,システムコールに直接対応するメソッドを用意したとしても,
同じ名前のシステムコールであっても微妙に仕様が違ったり,
保存ユーザ ID を持つ環境と持たない環境とでは
目的実現のために用いるシステムコールの集合が違ったりなど,
スクリプトのポータビリティを高めるには OS 環境に対する知識と
努力とが必要になります.

そこで,システムコール対応メソッドを用意するのはともかくとして,
ポータビリティが高いメソッド集合はどういうものかという議論が
行われたわけです.
結果として提案自体はなされましたが,それで良いかどうかの結論が
きちんと出されないままに長期間の放置となっていました.

そこで皆様にお願いです.ぜひ意見をください.
もちろん,「これこれの環境ではこうした方がいい」といった
提案などは特に嬉しいのですが,そのように細に入ったものでなくとも
「ここが気に入った」だとか「ここが気に入らない」だとか,
「よくわからないけど,まぁ,いいんじゃないの」だとか,
「そもそもそんなもの不要」だとかといった感想レベルでも構いません.
どうにも反応がなくて先に進めずにいるのです.

# もし皆の納得が得られるなら,今月中に予定されている 1.8.0 に
# 含めることができるといいなとは考えていますので,
# 急ぎ意見をいただけますと嬉しく思います.

メール末尾に現状での案における仕様と patch を添えておきますが,
その前に簡単に方針に関する注意事項に触れておきます.

OS がサポートするシステムコールの集合と仕様の違いにより,
あらゆる目的に対してポータビリティを維持するメソッド集合を
構成することはほとんど不可能であると思われます.
そのため,メソッド集合を考えるに際して,目的範囲を制限しています.
ユーザ ID/グループ ID を操作する目的として想定したのは次のものです.

 (1) ある状態の権限を完全に放棄する.
 (2) 二つの権限の間を移行して処理を行う.
 (3) (2) が可能な状態を作り出すための操作.

上記 (3) は (2) の補助ですので,本来の目的は (1) or (2) でしょう.
上記 (2) の操作は,保存ユーザ ID を持つ環境と持たない環境とで違います.
保存ユーザ ID を持たない環境では,実ユーザ ID と実効ユーザ ID との
交換によって実現するしかありませんが,保存ユーザ ID を持つ環境では
実ユーザ ID を変更しなくても実効ユーザ ID の指定だけで
保存ユーザ ID を利用した権限切り替えを行うことができます.
ただ,いずれの環境であったとしても,(2) の操作の実現には
「実ユーザ ID と実効ユーザ ID とが異なる」という状況を
作り出せればいいということになります.
それさえできれば,処理中に実ユーザ ID が変化するかどうかという
違いはあるものの,「切り替え」という目的を一つのメソッドで
表現することができるはずです.

「実ユーザ ID と実効ユーザ ID とが異なる」という状況を
作り出すことについても,例えば root で起動して実ユーザ ID が A ,
実効ユーザ ID が B (ただし A != B && A, B != root) とできるかは,
詳しいことは省きますが,実ユーザ ID と実効ユーザ ID とを
交換できる (交換する手段がある) 環境であるかで判断することができます 
(と考えています).
そのため,この目的,つまり上記 (3) のためのメソッドとして,
「実効ユーザ ID を設定する」,「実/実効ユーザ ID の交換が可能か?」,
「実/実効ユーザ ID を交換する」の 3 個を用意しています.

ここで提案しているメソッド集合は,上記のように整理していくことで
できるだけ高いポータビリティを実現するための最小限の集合となることを
目指したものです.環境の違いに基づいた使用するシステムコールの選定は
すべてメソッド実装の内部に封じ込めていますから,環境の差異について
ほとんど気にすることなくスクリプトを書けるはずです.

patch で行っている条件判断を見ていただければわかると思いますが,
システムコールを直接駆使してここで提案していると同様の
ポータビリティを持つスクリプトを書くのはかなりの手間になるはずです.

なお,この設計の過程において,一部環境では実現可能となっている
「実/実効/保存ユーザ ID がすべて異なる状態」というものは
サポートをあきらめています.
そうした状態がどうしても必要という状況は思いつかないのですが,
もし本当に必要であれば,システムコールに直接対応したメソッドを
使っていただかなければなりません.

では現在の仕様と patch とを示しておきます.
皆様のご意見をお待ちしています.

=*==*==*==*==*==*==*==*==*==*==*==*==*==*==*==*==*==*==*==*==*=

[ruby-dev:16609] の定義に基づく [ruby-dev:16883] の patch を
CVS head に適用したもの (メソッド名は短く変更).
getgroups/setgroups は Process.group/group= で実装済なのでそのまま.
initgroups を Process.initgroups として追加

module Process::ID_Syscall   単なるシステムコールの直接実装
    単純な操作がどうしても欲しい人のためのサービス提供
    ポータビリティは期待できない
  getuid
  geteuid
  getgid
  getegid
  setuid
  setgid
  setruid
  setrgid
  seteuid
  setegid
  setreuid
  setregid
  setresuid
  setresgid

module Process::UserID   ユーザ ID 操作
  rid                実 ID の獲得
  eid                実効 ID の獲得
  to(id)             指定 ID への完全移行
  eid=(id)           実効 ID の設定
  re_exchange        実/実効 ID の交換
  re_exchangeable?   実/実効 ID の交換可否
  has_sid?           保存 ID の有無
  switch             権限の一時的な変更

module Process::GroupID   グループ ID 操作
  メソッド集合は module Process::UserID と同じ

============================================================

Process::UserID.rid [Process::GroupID.rid]

  仕様 : 現在の実ユーザ ID を得る

============================================================

Process::UserID.eid [Process::GroupID.eid]

  仕様 : 現在の実効ユーザ ID を得る

============================================================

Process::UserID.to(id) [Process::GroupID.to(id)]

  仕様 : 実/実効/保存ユーザ ID のすべてを変更して,
         他のユーザとしての権限を放棄する.
         成功時は id を返す.
         完全放棄できない場合(一部ユーザ ID の変更ができない場合)は
         例外を発生する.
         例外の発生時に,このメソッドを呼び出す前の各ユーザ ID の値が
         保存されているかどうかは保証されない.

  注意 : 以前の Process.uid= とは互換性がないことに注意.
         setreuid(id,-1) によって実装されていたものを用いていたケースは,
         例えば次のように書き換える必要があるだろう.

         Process::UserID.re_exchange  # (r,e,s)==(u1,u2,??) ==> (u2,u1,??)
         Process::UserID.eid = id     # (u2,u1,??) ==> (u2,id,??)
         Process::UserID.re_exchange  # (u2,id,??) ==> (id,u2,??)

============================================================

Process::UserID.eid=(id) [Process::GroupID.eid=(id)]

  仕様 : 実効ユーザ ID を変更する.
         成功時は id を返す.
         保存ユーザ ID の変化は,Process::UserID.re_exchangeable? が
         true かどうかで決まる.
         true ではない環境では,保存ユーザ ID は変化しない.
         true の環境では,id が実ユーザ ID と異なる値に設定された場合,
         保存ユーザ ID は新しい実効ユーザ ID の値に設定される.
         設定できない場合は例外を発生する.

============================================================

Process::UserID.re_exchange [Process::GroupID.re_exchange]

  仕様 : 実ユーザ ID と実効ユーザ ID とを入れ換える.
         保存ユーザ ID は新しい実効ユーザ ID と同じになる.
         環境依存により実装されていない場合は例外を発生する.
         戻り値としては,新しい実効ユーザ ID を返す.

============================================================

Process::UserID.re_exchangeable? [Process::GroupID.re_exchangeable?]

  仕様 : 実ユーザ ID と実効ユーザ ID との入れ換えが可能な環境かどうか
         (Process::UserID.re_exchange が実装されているか) を真偽値で返す.
         入れ換え可能なら true を返す.

============================================================

Process::UserID.has_sid? [Process::GroupID.has_sid?]

  仕様 : 保存ユーザ ID を持つ環境かどうかを真偽値で返す.
         保存ユーザ ID を持つなら true を返す.
         ただし,保存ユーザ ID の有無を明確に判定できない環境では,
         この値は推定値である (誤りの可能性もある) ことに注意が必要.
         現在は,次の条件のいずれかが満足される場合に
         保存ユーザ ID を持つものと判定する.
          ・setresuid を持つ
          ・seteuid を持つ
          ・_POSIX_SAVED_IDS が真として定義されている

============================================================

Process::UserID.switch         [Process::GroupID.switch]
Process::UserID.switch { ... } [Process::GroupID.switch { ... }]

  仕様 : ユーザ権限を(一時的に)変更する.
         ブロックが与えられたならば,権限を移行してそのブロックを実行した後,
         元の権限に復帰する.
         途中で実/実効/保存ユーザ ID を変更しなければ,
         ブロックを与えない呼び出しを二度実行することで,
         権限の一時的変更と元の権限への復帰とが可能となる.
         ブロックが与えられない場合の戻り値は元に戻せる(変更できる) ID 値,
         ブロックが与えられた場合の戻り値はブロックの値を返す.
         実/実効/保存ユーザ ID が二つのユーザ権限を移行できる状態でなければ
         例外を発生する (変更できるのは実ユーザ ID の権限へか,
         保存ユーザ ID の権限へかのいずれかである).
         ブロックを与えた際に,ブロック内でユーザ ID が変更されたなどの理由で
         元の権限に復帰できない場合も例外を発生する.
         保存ユーザ ID を持たない環境では,このメソッドの実行によって
         実ユーザ ID が変化する (ブロックを与えた場合はブロック実行中のみ).

============================================================

=*==*==*==*==*==*==*==*==*==*==*==*==*==*==*==*==*==*==*==*==*=
Index: configure.in
===================================================================
RCS file: /src/ruby/configure.in,v
retrieving revision 1.179
diff -u -r1.179 configure.in
--- configure.in	11 Jul 2003 13:37:22 -0000	1.179
+++ configure.in	20 Jul 2003 15:03:34 -0000
@@ -371,9 +371,9 @@
 	      truncate chsize times utimes fcntl lockf lstat symlink readlink\
 	      setitimer setruid seteuid setreuid setresuid setproctitle\
 	      setrgid setegid setregid setresgid pause lchown lchmod\
-	      getpgrp setpgrp getpgid setpgid getgroups setgroups getpriority getrlimit\
-	      dlopen sigprocmask sigaction _setjmp setsid telldir seekdir fchmod\
-	      mktime timegm cosh sinh tanh)
+	      getpgrp setpgrp getpgid setpgid initgroups getgroups setgroups\
+	      getpriority getrlimit dlopen sigprocmask sigaction _setjmp\
+	      setsid telldir seekdir fchmod mktime timegm cosh sinh tanh)
 AC_STRUCT_TIMEZONE
 AC_CACHE_CHECK(for struct tm.tm_gmtoff, rb_cv_member_struct_tm_tm_gmtoff,
   [AC_TRY_COMPILE([#include <time.h>],
Index: process.c
===================================================================
RCS file: /src/ruby/process.c,v
retrieving revision 1.73
diff -u -r1.73 process.c
--- process.c	15 Jul 2003 07:35:10 -0000	1.73
+++ process.c	20 Jul 2003 15:03:34 -0000
@@ -1158,6 +1158,66 @@
 }
 
 static VALUE
+p_sys_setuid(obj, id)
+    VALUE obj, id;
+{
+#if defined HAVE_SETUID
+    if (setuid(NUM2INT(id)) != 0) rb_sys_fail(0);
+#else
+    rb_notimplement();
+#endif
+    return Qnil;
+}
+
+static VALUE
+p_sys_setruid(obj, id)
+    VALUE obj, id;
+{
+#if defined HAVE_SETRUID
+    if (setruid(NUM2INT(id)) != 0) rb_sys_fail(0);
+#else
+    rb_notimplement();
+#endif
+    return Qnil;
+}
+
+static VALUE
+p_sys_seteuid(obj, id)
+    VALUE obj, id;
+{
+#if defined HAVE_SETEUID
+    if (seteuid(NUM2INT(id)) != 0) rb_sys_fail(0);
+#else
+    rb_notimplement();
+#endif
+    return Qnil;
+}
+
+static VALUE
+p_sys_setreuid(obj, rid, eid)
+    VALUE obj, rid, eid;
+{
+#if defined HAVE_SETREUID
+    if (setreuid(NUM2INT(rid),NUM2INT(eid)) != 0) rb_sys_fail(0);
+#else
+    rb_notimplement();
+#endif
+    return Qnil;
+}
+
+static VALUE
+p_sys_setresuid(obj, rid, eid, sid)
+    VALUE obj, rid, eid, sid;
+{
+#if defined HAVE_SETRESUID
+    if (setresuid(NUM2INT(rid),NUM2INT(eid),NUM2INT(sid)) != 0) rb_sys_fail(0);
+#else
+    rb_notimplement();
+#endif
+    return Qnil;
+}
+
+static VALUE
 proc_getuid(obj)
     VALUE obj;
 {
@@ -1177,7 +1237,7 @@
     if (setreuid(uid, -1) < 0) rb_sys_fail(0);
 #elif defined HAVE_SETRUID
     if (setruid(uid) < 0) rb_sys_fail(0);
-#else
+#elif defined HAVE_SETUID
     {
 	if (geteuid() == uid) {
 	    if (setuid(uid) < 0) rb_sys_fail(0);
@@ -1186,10 +1246,203 @@
 	    rb_notimplement();
 	}
     }
+#else
+    rb_notimplement();
 #endif
     return INT2FIX(uid);
 }
 
+static int SAVED_USER_ID;
+
+static VALUE
+p_uid_set_to(obj, id)
+    VALUE obj, id;
+{
+    extern int errno;
+    int uid;
+
+    uid = NUM2INT(id);
+
+    if (geteuid() == 0) { /* root-user */
+#if defined(HAVE_SETRESUID)
+      if (setresuid(uid, uid, uid) < 0) rb_sys_fail(0);
+      SAVED_USER_ID = uid;
+#elif defined HAVE_SETUID
+      if (setuid(uid) < 0) rb_sys_fail(0);
+      SAVED_USER_ID = uid;
+#elif defined HAVE_SETREUID
+      if (getuid() == uid) {
+	if (SAVED_USER_ID == uid) {
+	  if (setreuid(-1, uid) < 0) rb_sys_fail(0);
+	} else {
+	  if (uid == 0) { /* (r,e,s) == (root, root, x) */
+	    if (setreuid(-1, SAVED_USER_ID) < 0) rb_sys_fail(0);
+	    if (setreuid(SAVED_USER_ID, 0) < 0) rb_sys_fail(0);
+	    SAVED_USER_ID = 0; /* (r,e,s) == (x, root, root) */
+	    if (setreuid(uid, uid) < 0) rb_sys_fail(0);
+	    SAVED_USER_ID = uid;
+	  } else {
+	    if (setreuid(0, -1) < 0) rb_sys_fail(0);
+	    SAVED_USER_ID = 0;
+	    if (setreuid(uid, uid) < 0) rb_sys_fail(0);
+	    SAVED_USER_ID = uid;
+	  }
+	}
+      } else {
+	if (setreuid(uid, uid) < 0) rb_sys_fail(0);
+	SAVED_USER_ID = uid;
+      }
+#elif defined HAVE_SETRUID && defined HAVE_SETEUID
+      if (getuid() == uid) {
+	if (SAVED_USER_ID == uid) {
+	  if (seteuid(uid) < 0) rb_sys_fail(0);
+	} else {
+	  if (uid == 0) {
+	    if (setruid(SAVED_USER_ID) < 0) rb_sys_fail(0);
+	    SAVED_USER_ID = 0;
+	    if (setruid(0) < 0) rb_sys_fail(0);
+	  } else {
+	    if (setruid(0) < 0) rb_sys_fail(0);
+	    SAVED_USER_ID = 0;
+	    if (seteuid(uid) < 0) rb_sys_fail(0);
+	    if (setruid(uid) < 0) rb_sys_fail(0);
+	    SAVED_USER_ID = uid;
+	  }
+	}
+      } else {
+	if (seteuid(uid) < 0) rb_sys_fail(0);
+	if (setruid(uid) < 0) rb_sys_fail(0);
+	SAVED_USER_ID = uid;
+      }
+#else
+      rb_notimplement();
+#endif
+    } else { /* unprivileged user */
+#if defined(HAVE_SETRESUID)
+      if (setresuid((getuid() == uid)? -1: uid, 
+		    (geteuid() == uid)? -1: uid, 
+		    (SAVED_USER_ID == uid)? -1: uid) < 0) rb_sys_fail(0);
+      SAVED_USER_ID = uid;
+#elif defined HAVE_SETREUID
+      if (SAVED_USER_ID == uid) {
+	if (setreuid((getuid() == uid)? -1: uid, 
+		     (geteuid() == uid)? -1: uid) < 0) rb_sys_fail(0);
+      } else if (getuid() != uid) {
+	if (setreuid(uid, (geteuid() == uid)? -1: uid) < 0) rb_sys_fail(0);
+	SAVED_USER_ID = uid;
+      } else if (/* getuid() == uid && */ geteuid() != uid) {
+	if (setreuid(geteuid(), uid) < 0) rb_sys_fail(0);
+	SAVED_USER_ID = uid;
+	if (setreuid(uid, -1) < 0) rb_sys_fail(0);
+      } else { /* getuid() == uid && geteuid() == uid */
+	if (setreuid(-1, SAVED_USER_ID) < 0) rb_sys_fail(0);
+	if (setreuid(SAVED_USER_ID, uid) < 0) rb_sys_fail(0);
+	SAVED_USER_ID = uid;
+	if (setreuid(uid, -1) < 0) rb_sys_fail(0);
+      }
+#elif defined HAVE_SETRUID && defined HAVE_SETEUID
+      if (SAVED_USER_ID == uid) {
+	if (geteuid() != uid && seteuid(uid) < 0) rb_sys_fail(0);
+	if (getuid() != uid && setruid(uid) < 0) rb_sys_fail(0);
+      } else if (/* SAVED_USER_ID != uid && */ geteuid() == uid) {
+	if (getuid() != uid) {
+	  if (setruid(uid) < 0) rb_sys_fail(0);
+	  SAVED_USER_ID = uid;
+	} else {
+	  if (setruid(SAVED_USER_ID) < 0) rb_sys_fail(0);
+	  SAVED_USER_ID = uid;
+	  if (setruid(uid) < 0) rb_sys_fail(0);
+	}
+      } else if (/* geteuid() != uid && */ getuid() == uid) {
+	if (seteuid(uid) < 0) rb_sys_fail(0);
+	if (setruid(SAVED_USER_ID) < 0) rb_sys_fail(0);
+	SAVED_USER_ID = uid;
+	if (setruid(uid) < 0) rb_sys_fail(0);
+      } else {
+	errno = EPERM;
+	rb_sys_fail(0);
+      }
+#elif defined HAVE_SETEUID
+      if (getuid() == uid && SAVED_USER_ID == uid) {
+	if (seteuid(uid) < 0) rb_sys_fail(0);
+      } else {
+	errno = EPERM;
+	rb_sys_fail(0);
+      }
+#elif defined HAVE_SETUID
+      if (getuid() == uid && SAVED_USER_ID == uid) {
+	if (setuid(uid) < 0) rb_sys_fail(0);
+      } else {
+	errno = EPERM;
+	rb_sys_fail(0);
+      }
+#else
+      rb_notimplement();
+#endif
+    }
+    return INT2FIX(uid);
+}
+
+static VALUE
+p_sys_setgid(obj, id)
+    VALUE obj, id;
+{
+#if defined HAVE_SETGID
+    if (setgid(NUM2INT(id)) != 0) rb_sys_fail(0);
+#else
+    rb_notimplement();
+#endif
+    return Qnil;
+}
+
+static VALUE
+p_sys_setrgid(obj, id)
+    VALUE obj, id;
+{
+#if defined HAVE_SETRGID
+    if (setrgid(NUM2INT(id)) != 0) rb_sys_fail(0);
+#else
+    rb_notimplement();
+#endif
+    return Qnil;
+}
+
+static VALUE
+p_sys_setegid(obj, id)
+    VALUE obj, id;
+{
+#if defined HAVE_SETEGID
+    if (setegid(NUM2INT(id)) != 0) rb_sys_fail(0);
+#else
+    rb_notimplement();
+#endif
+    return Qnil;
+}
+
+static VALUE
+p_sys_setregid(obj, rid, eid)
+    VALUE obj, rid, eid;
+{
+#if defined HAVE_SETREGID
+    if (setregid(NUM2INT(rid),NUM2INT(eid)) != 0) rb_sys_fail(0);
+#else
+    rb_notimplement();
+#endif
+    return Qnil;
+}
+
+static VALUE
+p_sys_setresgid(obj, rid, eid, sid)
+    VALUE obj, rid, eid, sid;
+{
+#if defined HAVE_SETRESGID
+    if (setresgid(NUM2INT(rid),NUM2INT(eid),NUM2INT(sid)) != 0) rb_sys_fail(0);
+#else
+    rb_notimplement();
+#endif
+    return Qnil;
+}
+
 static VALUE
 proc_getgid(obj)
     VALUE obj;
@@ -1208,9 +1461,9 @@
     if (setresgid(gid, -1, -1) < 0) rb_sys_fail(0);
 #elif defined HAVE_SETREGID
     if (setregid(gid, -1) < 0) rb_sys_fail(0);
-#elif defined HAS_SETRGID
+#elif defined HAVE_SETRGID
     if (setrgid((GIDTYPE)gid) < 0) rb_sys_fail(0);
-#else
+#elif defined HAVE_SETGID
     {
 	if (getegid() == gid) {
 	    if (setgid(gid) < 0) rb_sys_fail(0);
@@ -1219,6 +1472,8 @@
 	    rb_notimplement();
 	}
     }
+#else
+    rb_notimplement();
 #endif
     return INT2FIX(gid);
 }
@@ -1302,6 +1557,21 @@
 }
 
 static VALUE
+proc_initgroups(obj, uname, base_grp)
+     VALUE obj, uname, base_grp;
+{
+#ifdef HAVE_INITGROUPS
+    if (initgroups(StringValuePtr(uname), (gid_t)NUM2INT(base_grp)) != 0) {
+	rb_sys_fail(0);
+    }
+    return proc_getgroups(obj);
+#else
+    rb_notimplement();
+    return Qnil;
+#endif
+}
+
+static VALUE
 proc_getmaxgroups(obj)
     VALUE obj;
 {
@@ -1322,6 +1592,138 @@
     return INT2FIX(maxgroups);
 }
 
+static int SAVED_GROUP_ID;
+
+static VALUE
+p_gid_set_to(obj, id)
+    VALUE obj, id;
+{
+    extern int errno;
+    int gid;
+
+    gid = NUM2INT(id);
+
+    if (geteuid() == 0) { /* root-user */
+#if defined(HAVE_SETRESGID)
+      if (setresgid(gid, gid, gid) < 0) rb_sys_fail(0);
+      SAVED_GROUP_ID = gid;
+#elif defined HAVE_SETGID
+      if (setgid(gid) < 0) rb_sys_fail(0);
+      SAVED_GROUP_ID = gid;
+#elif defined HAVE_SETREGID
+      if (getgid() == gid) {
+	if (SAVED_GROUP_ID == gid) {
+	  if (setregid(-1, gid) < 0) rb_sys_fail(0);
+	} else {
+	  if (gid == 0) { /* (r,e,s) == (root, y, x) */
+	    if (setregid(-1, SAVED_GROUP_ID) < 0) rb_sys_fail(0);
+	    if (setregid(SAVED_GROUP_ID, 0) < 0) rb_sys_fail(0);
+	    SAVED_GROUP_ID = 0; /* (r,e,s) == (x, root, root) */
+	    if (setregid(gid, gid) < 0) rb_sys_fail(0);
+	    SAVED_GROUP_ID = gid;
+	  } else { /* (r,e,s) == (z, y, x) */
+	    if (setregid(0, 0) < 0) rb_sys_fail(0);
+	    SAVED_GROUP_ID = 0;
+	    if (setregid(gid, gid) < 0) rb_sys_fail(0);
+	    SAVED_GROUP_ID = gid;
+	  }
+	}
+      } else {
+	if (setregid(gid, gid) < 0) rb_sys_fail(0);
+	SAVED_GROUP_ID = gid;
+      }
+#elif defined HAVE_SETRGID && defined HAVE_SETEGID
+      if (getgid() == gid) {
+	if (SAVED_GROUP_ID == gid) {
+	  if (setegid(gid) < 0) rb_sys_fail(0);
+	} else {
+	  if (gid == 0) {
+	    if (setegid(gid) < 0) rb_sys_fail(0);
+	    if (setrgid(SAVED_GROUP_ID) < 0) rb_sys_fail(0);
+	    SAVED_GROUP_ID = 0;
+	    if (setrgid(0) < 0) rb_sys_fail(0);
+	  } else {
+	    if (setrgid(0) < 0) rb_sys_fail(0);
+	    SAVED_GROUP_ID = 0;
+	    if (setegid(gid) < 0) rb_sys_fail(0);
+	    if (setrgid(gid) < 0) rb_sys_fail(0);
+	    SAVED_GROUP_ID = gid;
+	  }
+	}
+      } else {
+	if (setegid(gid) < 0) rb_sys_fail(0);
+	if (setrgid(gid) < 0) rb_sys_fail(0);
+	SAVED_GROUP_ID = gid;
+      }
+#else
+      rb_notimplement();
+#endif
+    } else { /* unprivileged user */
+#if defined(HAVE_SETRESGID)
+      if (setresgid((getgid() == gid)? -1: gid, 
+		    (getegid() == gid)? -1: gid, 
+		    (SAVED_GROUP_ID == gid)? -1: gid) < 0) rb_sys_fail(0);
+      SAVED_GROUP_ID = gid;
+#elif defined HAVE_SETREGID
+      if (SAVED_GROUP_ID == gid) {
+	if (setregid((getgid() == gid)? -1: gid, 
+		     (getegid() == gid)? -1: gid) < 0) rb_sys_fail(0);
+      } else if (getgid() != gid) {
+	if (setregid(gid, (getegid() == gid)? -1: gid) < 0) rb_sys_fail(0);
+	SAVED_GROUP_ID = gid;
+      } else if (/* getgid() == gid && */ getegid() != gid) {
+	if (setregid(getegid(), gid) < 0) rb_sys_fail(0);
+	SAVED_GROUP_ID = gid;
+	if (setregid(gid, -1) < 0) rb_sys_fail(0);
+      } else { /* getgid() == gid && getegid() == gid */
+	if (setregid(-1, SAVED_GROUP_ID) < 0) rb_sys_fail(0);
+	if (setregid(SAVED_GROUP_ID, gid) < 0) rb_sys_fail(0);
+	SAVED_GROUP_ID = gid;
+	if (setregid(gid, -1) < 0) rb_sys_fail(0);
+      }
+#elif defined HAVE_SETRGID && defined HAVE_SETEGID
+      if (SAVED_GROUP_ID == gid) {
+	if (getegid() != gid && setegid(gid) < 0) rb_sys_fail(0);
+	if (getgid() != gid && setrgid(gid) < 0) rb_sys_fail(0);
+      } else if (/* SAVED_GROUP_ID != gid && */ getegid() == gid) {
+	if (getgid() != gid) {
+	  if (setrgid(gid) < 0) rb_sys_fail(0);
+	  SAVED_GROUP_ID = gid;
+	} else {
+	  if (setrgid(SAVED_GROUP_ID) < 0) rb_sys_fail(0);
+	  SAVED_GROUP_ID = gid;
+	  if (setrgid(gid) < 0) rb_sys_fail(0);
+	}
+      } else if (/* getegid() != gid && */ getgid() == gid) {
+	if (setegid(gid) < 0) rb_sys_fail(0);
+	if (setrgid(SAVED_GROUP_ID) < 0) rb_sys_fail(0);
+	SAVED_GROUP_ID = gid;
+	if (setrgid(gid) < 0) rb_sys_fail(0);
+      } else {
+	errno = EPERM;
+	rb_sys_fail(0);
+      }
+#elif defined HAVE_SETEGID
+      if (getgid() == gid && SAVED_GROUP_ID == gid) {
+	if (setegid(gid) < 0) rb_sys_fail(0);
+      } else {
+	errno = EPERM;
+	rb_sys_fail(0);
+      }
+#elif defined HAVE_SETGID
+      if (getgid() == gid && SAVED_GROUP_ID == gid) {
+	if (setgid(gid) < 0) rb_sys_fail(0);
+      } else {
+	errno = EPERM;
+	rb_sys_fail(0);
+      }
+#else
+      rb_notimplement();
+#endif
+    }
+    return INT2FIX(gid);
+}
+
 static VALUE
 proc_geteuid(obj)
     VALUE obj;
@@ -1340,7 +1742,7 @@
     if (setreuid(-1, NUM2INT(euid)) < 0) rb_sys_fail(0);
 #elif defined HAVE_SETEUID
     if (seteuid(NUM2INT(euid)) < 0) rb_sys_fail(0);
-#else
+#elif defined HAVE_SETUID
     euid = NUM2INT(euid);
     if (euid == getuid()) {
 	if (setuid(euid) < 0) rb_sys_fail(0);
@@ -1348,11 +1750,53 @@
     else {
 	rb_notimplement();
     }
+#else
+    rb_notimplement();
 #endif
     return euid;
 }
 
 static VALUE
+rb_seteuid_core(euid)
+    int euid;
+{
+    int uid;
+
+    uid = getuid();
+
+#if defined(HAVE_SETRESUID) && !defined(__CHECKER__)
+    if (uid != euid) {
+      if (setresuid(-1,euid,euid) < 0) rb_sys_fail(0);
+      SAVED_USER_ID = euid;
+    } else {
+      if (setresuid(-1,euid,-1) < 0) rb_sys_fail(0);
+    }
+#elif defined HAVE_SETREUID && !defined(__OpenBSD__)
+    if (setreuid(-1, euid) < 0) rb_sys_fail(0);
+    if (uid != euid) {
+      if (setreuid(euid,uid) < 0) rb_sys_fail(0);
+      if (setreuid(uid,euid) < 0) rb_sys_fail(0);
+      SAVED_USER_ID = euid;
+    }
+#elif defined HAVE_SETEUID
+    if (seteuid(euid) < 0) rb_sys_fail(0);
+#elif defined HAVE_SETUID
+    if (geteuid() == 0) rb_sys_fail(0);
+    if (setuid(euid) < 0) rb_sys_fail(0);
+#else
+    rb_notimplement();
+#endif
+    return INT2FIX(euid);
+}
+
+static VALUE
+p_uid_set_eid(obj, id)
+    VALUE obj, id;
+{
+  return rb_seteuid_core(NUM2INT(id));
+}
+
+static VALUE
 proc_getegid(obj)
     VALUE obj;
 {
@@ -1372,7 +1816,7 @@
     if (setregid(-1, NUM2INT(egid)) < 0) rb_sys_fail(0);
 #elif defined HAVE_SETEGID
     if (setegid(NUM2INT(egid)) < 0) rb_sys_fail(0);
-#else
+#elif defined HAVE_SETGID
     egid = NUM2INT(egid);
     if (egid == getgid()) {
 	if (setgid(egid) < 0) rb_sys_fail(0);
@@ -1380,10 +1824,224 @@
     else {
 	rb_notimplement();
     }
+#else
+    rb_notimplement();
 #endif
     return egid;
 }
 
+static VALUE
+rb_setegid_core(egid)
+    int egid;
+{
+    int gid;
+
+    gid = getgid();
+
+#if defined(HAVE_SETRESGID) && !defined(__CHECKER__)
+    if (gid != egid) {
+      if (setresgid(-1,egid,egid) < 0) rb_sys_fail(0);
+      SAVED_GROUP_ID = egid;
+    } else {
+      if (setresgid(-1,egid,-1) < 0) rb_sys_fail(0);
+    }
+#elif defined HAVE_SETREGID && !defined(__OpenBSD__)
+    if (setregid(-1, egid) < 0) rb_sys_fail(0);
+    if (gid != egid) {
+      if (setregid(egid,gid) < 0) rb_sys_fail(0);
+      if (setregid(gid,egid) < 0) rb_sys_fail(0);
+      SAVED_GROUP_ID = egid;
+    }
+#elif defined HAVE_SETEGID
+    if (setegid(egid) < 0) rb_sys_fail(0);
+#elif defined HAVE_SETGID
+    if (getegid() == 0) rb_sys_fail(0);
+    if (setgid(egid) < 0) rb_sys_fail(0);
+#else
+    rb_notimplement();
+#endif
+    return INT2FIX(egid);
+}
+
+static VALUE
+p_gid_set_eid(obj, id)
+    VALUE obj, id;
+{
+  return rb_setegid_core(NUM2INT(id));
+}
+
+static VALUE
+p_uid_exchangeable()
+{
+#if defined(HAVE_SETRESUID) &&  !defined(__CHECKER__)
+  return Qtrue;
+#elif defined HAVE_SETREUID && !defined(__OpenBSD__)
+  return Qtrue;
+#else
+  return Qfalse;
+#endif
+}
+
+static VALUE
+p_uid_exchange(obj)
+    VALUE obj;
+{
+    int uid, euid;
+
+    uid = getuid();
+    euid = geteuid();
+
+#if defined(HAVE_SETRESUID) &&  !defined(__CHECKER__)
+    if (setresuid(euid, uid, uid) < 0) rb_sys_fail(0);
+    SAVED_USER_ID = uid;
+#elif defined HAVE_SETREUID && !defined(__OpenBSD__)
+    if (setreuid(euid,uid) < 0) rb_sys_fail(0);
+    SAVED_USER_ID = uid;
+#else
+    rb_notimplement();
+#endif
+    return INT2FIX(uid);
+}
+
+static VALUE
+p_gid_exchangeable()
+{
+#if defined(HAVE_SETRESGID) &&  !defined(__CHECKER__)
+  return Qtrue;
+#elif defined HAVE_SETREGID && !defined(__OpenBSD__)
+  return Qtrue;
+#else
+  return Qfalse;
+#endif
+}
+
+static VALUE
+p_gid_exchange(obj)
+    VALUE obj;
+{
+    int gid, egid;
+
+    gid = getgid();
+    egid = getegid();
+
+#if defined(HAVE_SETRESGID) &&  !defined(__CHECKER__)
+    if (setresgid(egid, gid, gid) < 0) rb_sys_fail(0);
+    SAVED_GROUP_ID = gid;
+#elif defined HAVE_SETREGID && !defined(__OpenBSD__)
+    if (setregid(egid,gid) < 0) rb_sys_fail(0);
+    SAVED_GROUP_ID = gid;
+#else
+    rb_notimplement();
+#endif
+    return INT2FIX(gid);
+}
+
+static VALUE
+p_uid_has_saved_id()
+{
+#if defined(HAVE_SETRESUID) || defined(HAVE_SETEUID) || _POSIX_SAVED_IDS
+  return Qtrue;
+#else
+  return Qfalse;
+#endif
+}
+
+static VALUE
+p_uid_switch_auth(obj)
+    VALUE obj;
+{
+    extern int errno;
+    int uid, euid;
+
+    uid = getuid();
+    euid = geteuid();
+
+#if defined(HAVE_SETRESUID) || defined(HAVE_SETEUID) || _POSIX_SAVED_IDS
+    if (uid != euid) {
+      proc_seteuid(obj, INT2FIX(uid));
+      if (rb_block_given_p()) {
+	return rb_ensure(rb_yield, Qnil, rb_seteuid_core, SAVED_USER_ID);
+      } else {
+	return INT2FIX(euid);
+      }
+    } else if (euid != SAVED_USER_ID) {
+      proc_seteuid(obj, INT2FIX(SAVED_USER_ID));
+      if (rb_block_given_p()) {
+	return rb_ensure(rb_yield, Qnil, rb_seteuid_core, euid);
+      } else {
+	return INT2FIX(uid);
+      }
+    } else {
+      errno = EPERM;
+      rb_sys_fail(0);
+    }
+#else
+    if (uid == euid) {
+      errno = EPERM;
+      rb_sys_fail(0);
+    }
+    proc_swap_uid(obj);
+    if (rb_block_given_p()) {
+      return rb_ensure(rb_yield, Qnil, proc_swap_uid, obj);
+    } else {
+      return INT2FIX(euid);
+    }
+#endif
+}
+
+static VALUE
+p_gid_has_saved_id()
+{
+#if defined(HAVE_SETRESGID) || defined(HAVE_SETEGID) || _POSIX_SAVED_IDS
+  return Qtrue;
+#else
+  return Qfalse;
+#endif
+}
+
+static VALUE
+p_gid_switch_auth(obj)
+    VALUE obj;
+{
+    extern int errno;
+    int gid, egid;
+
+    gid = getgid();
+    egid = getegid();
+
+#if defined(HAVE_SETRESGID) || defined(HAVE_SETEGID) || _POSIX_SAVED_IDS
+    if (gid != egid) {
+      proc_setegid(obj, INT2FIX(gid));
+      if (rb_block_given_p()) {
+	return rb_ensure(rb_yield, Qnil, proc_setegid, INT2FIX(SAVED_GROUP_ID));
+      } else {
+	return INT2FIX(egid);
+      }
+    } else if (egid != SAVED_GROUP_ID) {
+      proc_setegid(obj, INT2FIX(SAVED_GROUP_ID));
+      if (rb_block_given_p()) {
+	return rb_ensure(rb_yield, Qnil, proc_setegid, INT2FIX(egid));
+      } else {
+	return INT2FIX(gid);
+      }
+    } else {
+      errno = EPERM;
+      rb_sys_fail(0);
+    }
+#else
+    if (gid == egid) {
+      errno = EPERM;
+      rb_sys_fail(0);
+    }
+    proc_swap_gid(obj);
+    if (rb_block_given_p()) {
+      return rb_ensure(rb_yield, Qnil, proc_swap_gid, obj);
+    } else {
+      return INT2FIX(egid);
+    }
+#endif
+}
+
 VALUE
 rb_proc_times(obj)
     VALUE obj;
@@ -1411,6 +2069,9 @@
 }
 
 VALUE rb_mProcess;
+VALUE rb_mProcUID;
+VALUE rb_mProcGID;
+VALUE rb_mProcID_Syscall;
 
 void
 Init_process()
@@ -1499,6 +2160,7 @@
     rb_define_module_function(rb_mProcess, "euid=", proc_seteuid, 1);
     rb_define_module_function(rb_mProcess, "egid", proc_getegid, 0);
     rb_define_module_function(rb_mProcess, "egid=", proc_setegid, 1);
+    rb_define_module_function(rb_mProcess, "initgroups", proc_initgroups, 2);
     rb_define_module_function(rb_mProcess, "groups", proc_getgroups, 0);
     rb_define_module_function(rb_mProcess, "groups=", proc_setgroups, 1);
     rb_define_module_function(rb_mProcess, "maxgroups", proc_getmaxgroups, 0);
@@ -1509,4 +2171,55 @@
 #if defined(HAVE_TIMES) || defined(_WIN32)
     S_Tms = rb_struct_define("Tms", "utime", "stime", "cutime", "cstime", 0);
 #endif
+
+    SAVED_USER_ID = geteuid();
+    SAVED_GROUP_ID = getegid();
+
+    rb_mProcUID = rb_define_module_under(rb_mProcess, "UserID");
+    rb_mProcGID = rb_define_module_under(rb_mProcess, "GroupID");
+
+    rb_define_module_function(rb_mProcUID, "rid", proc_getuid, 0);
+    rb_define_module_function(rb_mProcGID, "rid", proc_getgid, 0);
+    rb_define_module_function(rb_mProcUID, "eid", proc_geteuid, 0);
+    rb_define_module_function(rb_mProcGID, "eid", proc_getegid, 0);
+    rb_define_module_function(rb_mProcUID, "to", p_uid_set_to, 1);
+    rb_define_module_function(rb_mProcGID, "to", p_gid_set_to, 1);
+    rb_define_module_function(rb_mProcUID, "eid=", p_uid_set_eid, 1);
+    rb_define_module_function(rb_mProcGID, "eid=", p_gid_set_eid, 1);
+    rb_define_module_function(rb_mProcUID, "re_exchange", p_uid_exchange, 0);
+    rb_define_module_function(rb_mProcGID, "re_exchange", p_gid_exchange, 0);
+    rb_define_module_function(rb_mProcUID, "re_exchangeable?", 
+			      p_uid_exchangeable, 0);
+    rb_define_module_function(rb_mProcGID, "re_exchangeable?", 
+			      p_gid_exchangeable, 0);
+    rb_define_module_function(rb_mProcUID, "has_sid?", p_uid_has_saved_id, 0);
+    rb_define_module_function(rb_mProcGID, "has_sid?", p_gid_has_saved_id, 0);
+    rb_define_module_function(rb_mProcUID, "switch", p_uid_switch_auth, 0);
+    rb_define_module_function(rb_mProcGID, "switch", p_uid_switch_auth, 0);
+
+    rb_mProcID_Syscall = rb_define_module_under(rb_mProcess, "ID_Syscall");
+
+    rb_define_module_function(rb_mProcID_Syscall, "getuid", proc_getuid, 0);
+    rb_define_module_function(rb_mProcID_Syscall, "geteuid", proc_geteuid, 0);
+    rb_define_module_function(rb_mProcID_Syscall, "getgid", proc_getgid, 0);
+    rb_define_module_function(rb_mProcID_Syscall, "getegid", proc_getegid, 0);
+
+    rb_define_module_function(rb_mProcID_Syscall, "setuid", p_sys_setuid, 1);
+    rb_define_module_function(rb_mProcID_Syscall, "setgid", p_sys_setgid, 1);
+
+    rb_define_module_function(rb_mProcID_Syscall, "setruid", p_sys_setruid, 1);
+    rb_define_module_function(rb_mProcID_Syscall, "setrgid", p_sys_setrgid, 1);
+
+    rb_define_module_function(rb_mProcID_Syscall, "seteuid", p_sys_seteuid, 1);
+    rb_define_module_function(rb_mProcID_Syscall, "setegid", p_sys_setegid, 1);
+
+    rb_define_module_function(rb_mProcID_Syscall, "setreuid", 
+			      p_sys_setreuid, 2);
+    rb_define_module_function(rb_mProcID_Syscall, "setregid", 
+			      p_sys_setregid, 2);
+
+    rb_define_module_function(rb_mProcID_Syscall, "setresuid", 
+			      p_sys_setresuid, 3);
+    rb_define_module_function(rb_mProcID_Syscall, "setresgid", 
+			      p_sys_setresgid, 3);
 }

=*==*==*==*==*==*==*==*==*==*==*==*==*==*==*==*==*==*==*==*==*=
-- 
                                         永井 秀利 (九工大 知能情報)
                                             nagai / ai.kyutech.ac.jp