/************************************************ etc.c - $Author$ created at: Tue Mar 22 18:39:19 JST 1994 ************************************************/ #include "ruby.h" #include "ruby/encoding.h" #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_GETPWENT #include #endif #ifdef HAVE_GETGRENT #include #endif static VALUE sPasswd; #ifdef HAVE_GETGRENT static VALUE sGroup; #endif #ifdef _WIN32 #include #ifndef CSIDL_COMMON_APPDATA #define CSIDL_COMMON_APPDATA 35 #endif #endif #ifndef _WIN32 char *getenv(); #endif char *getlogin(); /* call-seq: * getlogin -> String * * Returns the short user name of the currently logged in user. * Unfortunately, it is often rather easy to fool ::getlogin. * * Avoid ::getlogin for security-related purposes. * * If ::getlogin fails, try ::getpwuid. * * See the unix manpage for getpwuid(3) for more detail. * * e.g. * Etc.getlogin -> 'guest' */ static VALUE etc_getlogin(VALUE obj) { char *login; rb_secure(4); #ifdef HAVE_GETLOGIN login = getlogin(); if (!login) login = getenv("USER"); #else login = getenv("USER"); #endif if (login) return rb_tainted_str_new2(login); return Qnil; } #if defined(HAVE_GETPWENT) || defined(HAVE_GETGRENT) static VALUE safe_setup_str(const char *str) { if (str == 0) str = ""; return rb_tainted_str_new2(str); } #endif #ifdef HAVE_GETPWENT static VALUE setup_passwd(struct passwd *pwd) { if (pwd == 0) rb_sys_fail("/etc/passwd"); return rb_struct_new(sPasswd, safe_setup_str(pwd->pw_name), #ifdef HAVE_ST_PW_PASSWD safe_setup_str(pwd->pw_passwd), #endif UIDT2NUM(pwd->pw_uid), GIDT2NUM(pwd->pw_gid), #ifdef HAVE_ST_PW_GECOS safe_setup_str(pwd->pw_gecos), #endif safe_setup_str(pwd->pw_dir), safe_setup_str(pwd->pw_shell), #ifdef HAVE_ST_PW_CHANGE INT2NUM(pwd->pw_change), #endif #ifdef HAVE_ST_PW_QUOTA INT2NUM(pwd->pw_quota), #endif #ifdef HAVE_ST_PW_AGE PW_AGE2VAL(pwd->pw_age), #endif #ifdef HAVE_ST_PW_CLASS safe_setup_str(pwd->pw_class), #endif #ifdef HAVE_ST_PW_COMMENT safe_setup_str(pwd->pw_comment), #endif #ifdef HAVE_ST_PW_EXPIRE INT2NUM(pwd->pw_expire), #endif 0 /*dummy*/ ); } #endif /* call-seq: * getpwuid(uid) -> Passwd * * Returns the /etc/passwd information for the user with the given integer +uid+. * * The information is returned as a Passwd struct. * * If +uid+ is omitted, the value from Passwd[:uid] is returned * instead. * * See the unix manpage for getpwuid(3) for more detail. * * === Example: * * Etc.getpwuid(0) * #=> # */ static VALUE etc_getpwuid(int argc, VALUE *argv, VALUE obj) { #if defined(HAVE_GETPWENT) VALUE id; rb_uid_t uid; struct passwd *pwd; rb_secure(4); if (rb_scan_args(argc, argv, "01", &id) == 1) { uid = NUM2UIDT(id); } else { uid = getuid(); } pwd = getpwuid(uid); if (pwd == 0) rb_raise(rb_eArgError, "can't find user for %d", (int)uid); return setup_passwd(pwd); #else return Qnil; #endif } /* call-seq: * getpwnam(name) -> Passwd * * Returns the /etc/passwd information for the user with specified login * +name+. * * The information is returned as a Passwd struct. * * See the unix manpage for getpwnam(3) for more detail. * * === Example: * * Etc.getpwnam('root') * #=> # */ static VALUE etc_getpwnam(VALUE obj, VALUE nam) { #ifdef HAVE_GETPWENT struct passwd *pwd; SafeStringValue(nam); pwd = getpwnam(RSTRING_PTR(nam)); if (pwd == 0) rb_raise(rb_eArgError, "can't find user for %s", RSTRING_PTR(nam)); return setup_passwd(pwd); #else return Qnil; #endif } #ifdef HAVE_GETPWENT static int passwd_blocking = 0; static VALUE passwd_ensure(void) { endpwent(); passwd_blocking = (int)Qfalse; return Qnil; } static VALUE passwd_iterate(void) { struct passwd *pw; setpwent(); while (pw = getpwent()) { rb_yield(setup_passwd(pw)); } return Qnil; } static void each_passwd(void) { if (passwd_blocking) { rb_raise(rb_eRuntimeError, "parallel passwd iteration"); } passwd_blocking = (int)Qtrue; rb_ensure(passwd_iterate, 0, passwd_ensure, 0); } #endif /* call-seq: * Etc.passwd { |struct| block } -> Passwd * Etc.passwd -> Passwd * * Provides a convenient Ruby iterator which executes a block for each entry * in the /etc/passwd file. * * The code block is passed an Passwd struct. * * See ::getpwent above for details. * * Example: * * require 'etc' * * Etc.passwd {|u| * puts u.name + " = " + u.gecos * } * */ static VALUE etc_passwd(VALUE obj) { #ifdef HAVE_GETPWENT struct passwd *pw; rb_secure(4); if (rb_block_given_p()) { each_passwd(); } else if (pw = getpwent()) { return setup_passwd(pw); } #endif return Qnil; } /* call-seq: * Etc::Passwd.each { |struct| block } -> Passwd * Etc::Passwd.each -> Enumerator * * Iterates for each entry in the /etc/passwd file if a block is given. * * If no block is given, returns the Enumerator. * * The code block is passed an Passwd struct. * * See ::getpwent above for details. * * Example: * * require 'etc' * * Etc::Passwd.each {|u| * puts u.name + " = " + u.gecos * } * * Etc::Passwd.collect {|u| u.gecos} * Etc::Passwd.collect {|u| u.gecos} * */ static VALUE etc_each_passwd(VALUE obj) { #ifdef HAVE_GETPWENT RETURN_ENUMERATOR(obj, 0, 0); each_passwd(); #endif return obj; } /* Resets the process of reading the /etc/passwd file, so that the next call * to ::getpwent will return the first entry again. */ static VALUE etc_setpwent(VALUE obj) { #ifdef HAVE_GETPWENT setpwent(); #endif return Qnil; } /* Ends the process of scanning through the /etc/passwd file begun with * ::getpwent, and closes the file. */ static VALUE etc_endpwent(VALUE obj) { #ifdef HAVE_GETPWENT endpwent(); #endif return Qnil; } /* Returns an entry from the /etc/passwd file. * * The first time it is called it opens the file and returns the first entry; * each successive call returns the next entry, or +nil+ if the end of the file * has been reached. * * To close the file when processing is complete, call ::endpwent. * * Each entry is returned as a Passwd struct. * */ static VALUE etc_getpwent(VALUE obj) { #ifdef HAVE_GETPWENT struct passwd *pw; if (pw = getpwent()) { return setup_passwd(pw); } #endif return Qnil; } #ifdef HAVE_GETGRENT static VALUE setup_group(struct group *grp) { VALUE mem; char **tbl; mem = rb_ary_new(); tbl = grp->gr_mem; while (*tbl) { rb_ary_push(mem, safe_setup_str(*tbl)); tbl++; } return rb_struct_new(sGroup, safe_setup_str(grp->gr_name), #ifdef HAVE_ST_GR_PASSWD safe_setup_str(grp->gr_passwd), #endif GIDT2NUM(grp->gr_gid), mem); } #endif /* call-seq: * getgrgid(group_id) -> Group * * Returns information about the group with specified integer +group_id+, * as found in /etc/group. * * The information is returned as a Group struct. * * See the unix manpage for getgrgid(3) for more detail. * * === Example: * * Etc.getgrgid(100) * #=> # * */ static VALUE etc_getgrgid(int argc, VALUE *argv, VALUE obj) { #ifdef HAVE_GETGRENT VALUE id; gid_t gid; struct group *grp; rb_secure(4); if (rb_scan_args(argc, argv, "01", &id) == 1) { gid = NUM2GIDT(id); } else { gid = getgid(); } grp = getgrgid(gid); if (grp == 0) rb_raise(rb_eArgError, "can't find group for %d", (int)gid); return setup_group(grp); #else return Qnil; #endif } /* call-seq: * getgrnam(name) -> Group * * Returns information about the group with specified +name+, as found in * /etc/group. * * The information is returned as a Group struct. * * See the unix manpage for getgrnam(3) for more detail. * * === Example: * * Etc.getgrnam('users') * #=> # * */ static VALUE etc_getgrnam(VALUE obj, VALUE nam) { #ifdef HAVE_GETGRENT struct group *grp; rb_secure(4); SafeStringValue(nam); grp = getgrnam(RSTRING_PTR(nam)); if (grp == 0) rb_raise(rb_eArgError, "can't find group for %s", RSTRING_PTR(nam)); return setup_group(grp); #else return Qnil; #endif } #ifdef HAVE_GETGRENT static int group_blocking = 0; static VALUE group_ensure(void) { endgrent(); group_blocking = (int)Qfalse; return Qnil; } static VALUE group_iterate(void) { struct group *pw; setgrent(); while (pw = getgrent()) { rb_yield(setup_group(pw)); } return Qnil; } static void each_group(void) { if (group_blocking) { rb_raise(rb_eRuntimeError, "parallel group iteration"); } group_blocking = (int)Qtrue; rb_ensure(group_iterate, 0, group_ensure, 0); } #endif /* Provides a convenient Ruby iterator which executes a block for each entry * in the /etc/group file. * * The code block is passed an Group struct. * * See ::getgrent above for details. * * Example: * * require 'etc' * * Etc.group {|g| * puts g.name + ": " + g.mem.join(', ') * } * */ static VALUE etc_group(VALUE obj) { #ifdef HAVE_GETGRENT struct group *grp; rb_secure(4); if (rb_block_given_p()) { each_group(); } else if (grp = getgrent()) { return setup_group(grp); } #endif return Qnil; } #ifdef HAVE_GETGRENT /* call-seq: * Etc::Group.each { |group| block } -> obj * Etc::Group.each -> Enumerator * * Iterates for each entry in the /etc/group file if a block is given. * * If no block is given, returns the Enumerator. * * The code block is passed a Group struct. * * Example: * * require 'etc' * * Etc::Group.each {|g| * puts g.name + ": " + g.mem.join(', ') * } * * Etc::Group.collect {|g| g.name} * Etc::Group.select {|g| !g.mem.empty?} * */ static VALUE etc_each_group(VALUE obj) { RETURN_ENUMERATOR(obj, 0, 0); each_group(); return obj; } #endif /* Resets the process of reading the /etc/group file, so that the next call * to ::getgrent will return the first entry again. */ static VALUE etc_setgrent(VALUE obj) { #ifdef HAVE_GETGRENT setgrent(); #endif return Qnil; } /* Ends the process of scanning through the /etc/group file begun by * ::getgrent, and closes the file. */ static VALUE etc_endgrent(VALUE obj) { #ifdef HAVE_GETGRENT endgrent(); #endif return Qnil; } /* Returns an entry from the /etc/group file. * * The first time it is called it opens the file and returns the first entry; * each successive call returns the next entry, or +nil+ if the end of the file * has been reached. * * To close the file when processing is complete, call ::endgrent. * * Each entry is returned as a Group struct */ static VALUE etc_getgrent(VALUE obj) { #ifdef HAVE_GETGRENT struct group *gr; if (gr = getgrent()) { return setup_group(gr); } #endif return Qnil; } #define numberof(array) (sizeof(array) / sizeof(*(array))) #ifdef _WIN32 VALUE rb_w32_special_folder(int type); UINT rb_w32_system_tmpdir(WCHAR *path, UINT len); VALUE rb_w32_conv_from_wchar(const WCHAR *wstr, rb_encoding *enc); #endif /* * Returns system configuration directory. * * This is typically "/etc", but is modified by the prefix used when Ruby was * compiled. For example, if Ruby is built and installed in /usr/local, returns * "/usr/local/etc". */ static VALUE etc_sysconfdir(VALUE obj) { #ifdef _WIN32 return rb_w32_special_folder(CSIDL_COMMON_APPDATA); #else return rb_filesystem_str_new_cstr(SYSCONFDIR); #endif } /* * Returns system temporary directory; typically "/tmp". */ static VALUE etc_systmpdir(void) { VALUE tmpdir; #ifdef _WIN32 WCHAR path[_MAX_PATH]; UINT len = rb_w32_system_tmpdir(path, numberof(path)); if (!len) return Qnil; tmpdir = rb_w32_conv_from_wchar(path, rb_filesystem_encoding()); #else tmpdir = rb_filesystem_str_new_cstr("/tmp"); #endif FL_UNSET(tmpdir, FL_TAINT|FL_UNTRUSTED); return tmpdir; } /* * The Etc module provides access to information typically stored in * files in the /etc directory on Unix systems. * * The information accessible consists of the information found in the * /etc/passwd and /etc/group files, plus information about the system's * temporary directory (/tmp) and configuration directory (/etc). * * The Etc module provides a more reliable way to access information about * the logged in user than environment variables such as +$USER+. * * == Example: * * require 'etc' * * login = Etc.getlogin * info = Etc.getpwnam(login) * username = info.gecos.split(/,/).first * puts "Hello #{username}, I see your login name is #{login}" * * Note that the methods provided by this module are not always secure. * It should be used for informational purposes, and not for security. * * All operations defined in this module are class methods, so that you can * include the Etc module into your class. */ void Init_etc(void) { VALUE mEtc; mEtc = rb_define_module("Etc"); rb_define_module_function(mEtc, "getlogin", etc_getlogin, 0); rb_define_module_function(mEtc, "getpwuid", etc_getpwuid, -1); rb_define_module_function(mEtc, "getpwnam", etc_getpwnam, 1); rb_define_module_function(mEtc, "setpwent", etc_setpwent, 0); rb_define_module_function(mEtc, "endpwent", etc_endpwent, 0); rb_define_module_function(mEtc, "getpwent", etc_getpwent, 0); rb_define_module_function(mEtc, "passwd", etc_passwd, 0); rb_define_module_function(mEtc, "getgrgid", etc_getgrgid, -1); rb_define_module_function(mEtc, "getgrnam", etc_getgrnam, 1); rb_define_module_function(mEtc, "group", etc_group, 0); rb_define_module_function(mEtc, "setgrent", etc_setgrent, 0); rb_define_module_function(mEtc, "endgrent", etc_endgrent, 0); rb_define_module_function(mEtc, "getgrent", etc_getgrent, 0); rb_define_module_function(mEtc, "sysconfdir", etc_sysconfdir, 0); rb_define_module_function(mEtc, "systmpdir", etc_systmpdir, 0); sPasswd = rb_struct_define(NULL, "name", "passwd", "uid", "gid", #ifdef HAVE_ST_PW_GECOS "gecos", #endif "dir", "shell", #ifdef HAVE_ST_PW_CHANGE "change", #endif #ifdef HAVE_ST_PW_QUOTA "quota", #endif #ifdef HAVE_ST_PW_AGE "age", #endif #ifdef HAVE_ST_PW_CLASS "uclass", #endif #ifdef HAVE_ST_PW_COMMENT "comment", #endif #ifdef HAVE_ST_PW_EXPIRE "expire", #endif NULL); /* Define-const: Passwd * * Passwd is a Struct that contains the following members: * * name:: * contains the short login name of the user as a String. * passwd:: * contains the encrypted password of the user as a String. * an 'x' is returned if shadow passwords are in use. An '*' is returned * if the user cannot log in using a password. * uid:: * contains the integer user ID (uid) of the user. * gid:: * contains the integer group ID (gid) of the user's primary group. * dir:: * contains the path to the home directory of the user as a String. * shell:: * contains the path to the login shell of the user as a String. * * === The following members below are optional, and must be compiled with special flags: * * gecos:: * contains a longer String description of the user, such as * a full name. Some Unix systems provide structured information in the * gecos field, but this is system-dependent. * must be compiled with +HAVE_ST_PW_GECOS+ * change:: * password change time(integer) must be compiled with +HAVE_ST_PW_CHANGE+ * quota:: * quota value(integer) must be compiled with +HAVE_ST_PW_QUOTA+ * age:: * password age(integer) must be compiled with +HAVE_ST_PW_AGE+ * class:: * user access class(string) must be compiled with +HAVE_ST_PW_CLASS+ * comment:: * comment(string) must be compiled with +HAVE_ST_PW_COMMENT+ * expire:: * account expiration time(integer) must be compiled with +HAVE_ST_PW_EXPIRE+ */ rb_define_const(mEtc, "Passwd", sPasswd); rb_set_class_path(sPasswd, mEtc, "Passwd"); rb_define_const(rb_cStruct, "Passwd", sPasswd); /* deprecated name */ rb_extend_object(sPasswd, rb_mEnumerable); rb_define_singleton_method(sPasswd, "each", etc_each_passwd, 0); #ifdef HAVE_GETGRENT sGroup = rb_struct_define(NULL, "name", #ifdef HAVE_ST_GR_PASSWD "passwd", #endif "gid", "mem", NULL); /* Define-const: Group * * Group is a Struct that is only available when compiled with +HAVE_GETGRENT+. * * The struct contains the following members: * * name:: * contains the name of the group as a String. * passwd:: * contains the encrypted password as a String. An 'x' is * returned if password access to the group is not available; an empty * string is returned if no password is needed to obtain membership of * the group. * * Must be compiled with +HAVE_ST_GR_PASSWD+. * gid:: * contains the group's numeric ID as an integer. * mem:: * is an Array of Strings containing the short login names of the * members of the group. */ rb_define_const(mEtc, "Group", sGroup); rb_set_class_path(sPasswd, mEtc, "Group"); rb_define_const(rb_cStruct, "Group", sGroup); /* deprecated name */ rb_extend_object(sGroup, rb_mEnumerable); rb_define_singleton_method(sGroup, "each", etc_each_group, 0); #endif }