1/************************************************
2
3  etc.c -
4
5  $Author: nagachika $
6  created at: Tue Mar 22 18:39:19 JST 1994
7
8************************************************/
9
10#include "ruby.h"
11#include "ruby/encoding.h"
12
13#include <sys/types.h>
14#ifdef HAVE_UNISTD_H
15#include <unistd.h>
16#endif
17
18#ifdef HAVE_GETPWENT
19#include <pwd.h>
20#endif
21
22#ifdef HAVE_GETGRENT
23#include <grp.h>
24#endif
25
26static VALUE sPasswd;
27#ifdef HAVE_GETGRENT
28static VALUE sGroup;
29#endif
30
31#ifdef _WIN32
32#include <shlobj.h>
33#ifndef CSIDL_COMMON_APPDATA
34#define CSIDL_COMMON_APPDATA 35
35#endif
36#endif
37
38#ifndef _WIN32
39char *getenv();
40#endif
41char *getlogin();
42
43/* call-seq:
44 *	getlogin	->  String
45 *
46 * Returns the short user name of the currently logged in user.
47 * Unfortunately, it is often rather easy to fool ::getlogin.
48 *
49 * Avoid ::getlogin for security-related purposes.
50 *
51 * If ::getlogin fails, try ::getpwuid.
52 *
53 * See the unix manpage for <code>getpwuid(3)</code> for more detail.
54 *
55 * e.g.
56 *   Etc.getlogin -> 'guest'
57 */
58static VALUE
59etc_getlogin(VALUE obj)
60{
61    char *login;
62
63    rb_secure(4);
64#ifdef HAVE_GETLOGIN
65    login = getlogin();
66    if (!login) login = getenv("USER");
67#else
68    login = getenv("USER");
69#endif
70
71    if (login)
72	return rb_tainted_str_new2(login);
73    return Qnil;
74}
75
76#if defined(HAVE_GETPWENT) || defined(HAVE_GETGRENT)
77static VALUE
78safe_setup_str(const char *str)
79{
80    if (str == 0) str = "";
81    return rb_tainted_str_new2(str);
82}
83#endif
84
85#ifdef HAVE_GETPWENT
86static VALUE
87setup_passwd(struct passwd *pwd)
88{
89    if (pwd == 0) rb_sys_fail("/etc/passwd");
90    return rb_struct_new(sPasswd,
91			 safe_setup_str(pwd->pw_name),
92#ifdef HAVE_ST_PW_PASSWD
93			 safe_setup_str(pwd->pw_passwd),
94#endif
95			 UIDT2NUM(pwd->pw_uid),
96			 GIDT2NUM(pwd->pw_gid),
97#ifdef HAVE_ST_PW_GECOS
98			 safe_setup_str(pwd->pw_gecos),
99#endif
100			 safe_setup_str(pwd->pw_dir),
101			 safe_setup_str(pwd->pw_shell),
102#ifdef HAVE_ST_PW_CHANGE
103			 INT2NUM(pwd->pw_change),
104#endif
105#ifdef HAVE_ST_PW_QUOTA
106			 INT2NUM(pwd->pw_quota),
107#endif
108#ifdef HAVE_ST_PW_AGE
109			 PW_AGE2VAL(pwd->pw_age),
110#endif
111#ifdef HAVE_ST_PW_CLASS
112			 safe_setup_str(pwd->pw_class),
113#endif
114#ifdef HAVE_ST_PW_COMMENT
115			 safe_setup_str(pwd->pw_comment),
116#endif
117#ifdef HAVE_ST_PW_EXPIRE
118			 INT2NUM(pwd->pw_expire),
119#endif
120			 0		/*dummy*/
121	);
122}
123#endif
124
125/* call-seq:
126 *	getpwuid(uid)	->  Passwd
127 *
128 * Returns the /etc/passwd information for the user with the given integer +uid+.
129 *
130 * The information is returned as a Passwd struct.
131 *
132 * If +uid+ is omitted, the value from <code>Passwd[:uid]</code> is returned
133 * instead.
134 *
135 * See the unix manpage for <code>getpwuid(3)</code> for more detail.
136 *
137 * === Example:
138 *
139 *	Etc.getpwuid(0)
140 *	#=> #<struct Struct::Passwd name="root", passwd="x", uid=0, gid=0, gecos="root",dir="/root", shell="/bin/bash">
141 */
142static VALUE
143etc_getpwuid(int argc, VALUE *argv, VALUE obj)
144{
145#if defined(HAVE_GETPWENT)
146    VALUE id;
147    rb_uid_t uid;
148    struct passwd *pwd;
149
150    rb_secure(4);
151    if (rb_scan_args(argc, argv, "01", &id) == 1) {
152	uid = NUM2UIDT(id);
153    }
154    else {
155	uid = getuid();
156    }
157    pwd = getpwuid(uid);
158    if (pwd == 0) rb_raise(rb_eArgError, "can't find user for %d", (int)uid);
159    return setup_passwd(pwd);
160#else
161    return Qnil;
162#endif
163}
164
165/* call-seq:
166 *	getpwnam(name)	->  Passwd
167 *
168 * Returns the /etc/passwd information for the user with specified login
169 * +name+.
170 *
171 * The information is returned as a Passwd struct.
172 *
173 * See the unix manpage for <code>getpwnam(3)</code> for more detail.
174 *
175 * === Example:
176 *
177 *	Etc.getpwnam('root')
178 *	#=> #<struct Struct::Passwd name="root", passwd="x", uid=0, gid=0, gecos="root",dir="/root", shell="/bin/bash">
179 */
180static VALUE
181etc_getpwnam(VALUE obj, VALUE nam)
182{
183#ifdef HAVE_GETPWENT
184    struct passwd *pwd;
185
186    SafeStringValue(nam);
187    pwd = getpwnam(RSTRING_PTR(nam));
188    if (pwd == 0) rb_raise(rb_eArgError, "can't find user for %"PRIsVALUE, nam);
189    return setup_passwd(pwd);
190#else
191    return Qnil;
192#endif
193}
194
195#ifdef HAVE_GETPWENT
196static int passwd_blocking = 0;
197static VALUE
198passwd_ensure(void)
199{
200    endpwent();
201    passwd_blocking = (int)Qfalse;
202    return Qnil;
203}
204
205static VALUE
206passwd_iterate(void)
207{
208    struct passwd *pw;
209
210    setpwent();
211    while (pw = getpwent()) {
212	rb_yield(setup_passwd(pw));
213    }
214    return Qnil;
215}
216
217static void
218each_passwd(void)
219{
220    if (passwd_blocking) {
221	rb_raise(rb_eRuntimeError, "parallel passwd iteration");
222    }
223    passwd_blocking = (int)Qtrue;
224    rb_ensure(passwd_iterate, 0, passwd_ensure, 0);
225}
226#endif
227
228/* call-seq:
229 *	Etc.passwd { |struct| block }	->  Passwd
230 *	Etc.passwd			->  Passwd
231 *
232 * Provides a convenient Ruby iterator which executes a block for each entry
233 * in the /etc/passwd file.
234 *
235 * The code block is passed an Passwd struct.
236 *
237 * See ::getpwent above for details.
238 *
239 * Example:
240 *
241 *     require 'etc'
242 *
243 *     Etc.passwd {|u|
244 *       puts u.name + " = " + u.gecos
245 *     }
246 *
247 */
248static VALUE
249etc_passwd(VALUE obj)
250{
251#ifdef HAVE_GETPWENT
252    struct passwd *pw;
253
254    rb_secure(4);
255    if (rb_block_given_p()) {
256	each_passwd();
257    }
258    else if (pw = getpwent()) {
259	return setup_passwd(pw);
260    }
261#endif
262    return Qnil;
263}
264
265/* call-seq:
266 *	Etc::Passwd.each { |struct| block }	->  Passwd
267 *	Etc::Passwd.each			->  Enumerator
268 *
269 * Iterates for each entry in the /etc/passwd file if a block is given.
270 *
271 * If no block is given, returns the Enumerator.
272 *
273 * The code block is passed an Passwd struct.
274 *
275 * See ::getpwent above for details.
276 *
277 * Example:
278 *
279 *     require 'etc'
280 *
281 *     Etc::Passwd.each {|u|
282 *       puts u.name + " = " + u.gecos
283 *     }
284 *
285 *     Etc::Passwd.collect {|u| u.gecos}
286 *     Etc::Passwd.collect {|u| u.gecos}
287 *
288 */
289static VALUE
290etc_each_passwd(VALUE obj)
291{
292#ifdef HAVE_GETPWENT
293    RETURN_ENUMERATOR(obj, 0, 0);
294    each_passwd();
295#endif
296    return obj;
297}
298
299/* Resets the process of reading the /etc/passwd file, so that the next call
300 * to ::getpwent will return the first entry again.
301 */
302static VALUE
303etc_setpwent(VALUE obj)
304{
305#ifdef HAVE_GETPWENT
306    setpwent();
307#endif
308    return Qnil;
309}
310
311/* Ends the process of scanning through the /etc/passwd file begun with
312 * ::getpwent, and closes the file.
313 */
314static VALUE
315etc_endpwent(VALUE obj)
316{
317#ifdef HAVE_GETPWENT
318    endpwent();
319#endif
320    return Qnil;
321}
322
323/* Returns an entry from the /etc/passwd file.
324 *
325 * The first time it is called it opens the file and returns the first entry;
326 * each successive call returns the next entry, or +nil+ if the end of the file
327 * has been reached.
328 *
329 * To close the file when processing is complete, call ::endpwent.
330 *
331 * Each entry is returned as a Passwd struct.
332 *
333 */
334static VALUE
335etc_getpwent(VALUE obj)
336{
337#ifdef HAVE_GETPWENT
338    struct passwd *pw;
339
340    if (pw = getpwent()) {
341	return setup_passwd(pw);
342    }
343#endif
344    return Qnil;
345}
346
347#ifdef HAVE_GETGRENT
348static VALUE
349setup_group(struct group *grp)
350{
351    VALUE mem;
352    char **tbl;
353
354    mem = rb_ary_new();
355    tbl = grp->gr_mem;
356    while (*tbl) {
357	rb_ary_push(mem, safe_setup_str(*tbl));
358	tbl++;
359    }
360    return rb_struct_new(sGroup,
361			 safe_setup_str(grp->gr_name),
362#ifdef HAVE_ST_GR_PASSWD
363			 safe_setup_str(grp->gr_passwd),
364#endif
365			 GIDT2NUM(grp->gr_gid),
366			 mem);
367}
368#endif
369
370/* call-seq:
371 *	getgrgid(group_id)  ->	Group
372 *
373 * Returns information about the group with specified integer +group_id+,
374 * as found in /etc/group.
375 *
376 * The information is returned as a Group struct.
377 *
378 * See the unix manpage for <code>getgrgid(3)</code> for more detail.
379 *
380 * === Example:
381 *
382 *	Etc.getgrgid(100)
383 *	#=> #<struct Struct::Group name="users", passwd="x", gid=100, mem=["meta", "root"]>
384 *
385 */
386static VALUE
387etc_getgrgid(int argc, VALUE *argv, VALUE obj)
388{
389#ifdef HAVE_GETGRENT
390    VALUE id;
391    gid_t gid;
392    struct group *grp;
393
394    rb_secure(4);
395    if (rb_scan_args(argc, argv, "01", &id) == 1) {
396	gid = NUM2GIDT(id);
397    }
398    else {
399	gid = getgid();
400    }
401    grp = getgrgid(gid);
402    if (grp == 0) rb_raise(rb_eArgError, "can't find group for %d", (int)gid);
403    return setup_group(grp);
404#else
405    return Qnil;
406#endif
407}
408
409/* call-seq:
410 *	getgrnam(name)	->  Group
411 *
412 * Returns information about the group with specified +name+, as found in
413 * /etc/group.
414 *
415 * The information is returned as a Group struct.
416 *
417 * See the unix manpage for <code>getgrnam(3)</code> for more detail.
418 *
419 * === Example:
420 *
421 *	Etc.getgrnam('users')
422 *	#=> #<struct Struct::Group name="users", passwd="x", gid=100, mem=["meta", "root"]>
423 *
424 */
425static VALUE
426etc_getgrnam(VALUE obj, VALUE nam)
427{
428#ifdef HAVE_GETGRENT
429    struct group *grp;
430
431    rb_secure(4);
432    SafeStringValue(nam);
433    grp = getgrnam(RSTRING_PTR(nam));
434    if (grp == 0) rb_raise(rb_eArgError, "can't find group for %"PRIsVALUE, nam);
435    return setup_group(grp);
436#else
437    return Qnil;
438#endif
439}
440
441#ifdef HAVE_GETGRENT
442static int group_blocking = 0;
443static VALUE
444group_ensure(void)
445{
446    endgrent();
447    group_blocking = (int)Qfalse;
448    return Qnil;
449}
450
451
452static VALUE
453group_iterate(void)
454{
455    struct group *pw;
456
457    setgrent();
458    while (pw = getgrent()) {
459	rb_yield(setup_group(pw));
460    }
461    return Qnil;
462}
463
464static void
465each_group(void)
466{
467    if (group_blocking) {
468	rb_raise(rb_eRuntimeError, "parallel group iteration");
469    }
470    group_blocking = (int)Qtrue;
471    rb_ensure(group_iterate, 0, group_ensure, 0);
472}
473#endif
474
475/* Provides a convenient Ruby iterator which executes a block for each entry
476 * in the /etc/group file.
477 *
478 * The code block is passed an Group struct.
479 *
480 * See ::getgrent above for details.
481 *
482 * Example:
483 *
484 *     require 'etc'
485 *
486 *     Etc.group {|g|
487 *       puts g.name + ": " + g.mem.join(', ')
488 *     }
489 *
490 */
491static VALUE
492etc_group(VALUE obj)
493{
494#ifdef HAVE_GETGRENT
495    struct group *grp;
496
497    rb_secure(4);
498    if (rb_block_given_p()) {
499	each_group();
500    }
501    else if (grp = getgrent()) {
502	return setup_group(grp);
503    }
504#endif
505    return Qnil;
506}
507
508#ifdef HAVE_GETGRENT
509/* call-seq:
510 *	Etc::Group.each { |group| block }   ->	obj
511 *	Etc::Group.each			    ->	Enumerator
512 *
513 * Iterates for each entry in the /etc/group file if a block is given.
514 *
515 * If no block is given, returns the Enumerator.
516 *
517 * The code block is passed a Group struct.
518 *
519 * Example:
520 *
521 *     require 'etc'
522 *
523 *     Etc::Group.each {|g|
524 *       puts g.name + ": " + g.mem.join(', ')
525 *     }
526 *
527 *     Etc::Group.collect {|g| g.name}
528 *     Etc::Group.select {|g| !g.mem.empty?}
529 *
530 */
531static VALUE
532etc_each_group(VALUE obj)
533{
534    RETURN_ENUMERATOR(obj, 0, 0);
535    each_group();
536    return obj;
537}
538#endif
539
540/* Resets the process of reading the /etc/group file, so that the next call
541 * to ::getgrent will return the first entry again.
542 */
543static VALUE
544etc_setgrent(VALUE obj)
545{
546#ifdef HAVE_GETGRENT
547    setgrent();
548#endif
549    return Qnil;
550}
551
552/* Ends the process of scanning through the /etc/group file begun by
553 * ::getgrent, and closes the file.
554 */
555static VALUE
556etc_endgrent(VALUE obj)
557{
558#ifdef HAVE_GETGRENT
559    endgrent();
560#endif
561    return Qnil;
562}
563
564/* Returns an entry from the /etc/group file.
565 *
566 * The first time it is called it opens the file and returns the first entry;
567 * each successive call returns the next entry, or +nil+ if the end of the file
568 * has been reached.
569 *
570 * To close the file when processing is complete, call ::endgrent.
571 *
572 * Each entry is returned as a Group struct
573 */
574static VALUE
575etc_getgrent(VALUE obj)
576{
577#ifdef HAVE_GETGRENT
578    struct group *gr;
579
580    if (gr = getgrent()) {
581	return setup_group(gr);
582    }
583#endif
584    return Qnil;
585}
586
587#define numberof(array) (sizeof(array) / sizeof(*(array)))
588
589#ifdef _WIN32
590VALUE rb_w32_special_folder(int type);
591UINT rb_w32_system_tmpdir(WCHAR *path, UINT len);
592VALUE rb_w32_conv_from_wchar(const WCHAR *wstr, rb_encoding *enc);
593#endif
594
595/*
596 * Returns system configuration directory.
597 *
598 * This is typically "/etc", but is modified by the prefix used when Ruby was
599 * compiled. For example, if Ruby is built and installed in /usr/local, returns
600 * "/usr/local/etc".
601 */
602static VALUE
603etc_sysconfdir(VALUE obj)
604{
605#ifdef _WIN32
606    return rb_w32_special_folder(CSIDL_COMMON_APPDATA);
607#else
608    return rb_filesystem_str_new_cstr(SYSCONFDIR);
609#endif
610}
611
612/*
613 * Returns system temporary directory; typically "/tmp".
614 */
615static VALUE
616etc_systmpdir(void)
617{
618    VALUE tmpdir;
619#ifdef _WIN32
620    WCHAR path[_MAX_PATH];
621    UINT len = rb_w32_system_tmpdir(path, numberof(path));
622    if (!len) return Qnil;
623    tmpdir = rb_w32_conv_from_wchar(path, rb_filesystem_encoding());
624#else
625    #ifndef MAXPATHLEN
626    #define MAXPATHLEN 1024
627    #endif
628    char path[MAXPATHLEN];
629    size_t len = 0;
630
631    len = confstr(_CS_DARWIN_USER_TEMP_DIR, path, sizeof(path));
632    if (len > 0) {
633	    tmpdir = rb_filesystem_str_new_cstr(path);
634    } else {
635	    tmpdir = rb_filesystem_str_new_cstr("/tmp");
636    }
637#endif
638    FL_UNSET(tmpdir, FL_TAINT|FL_UNTRUSTED);
639    return tmpdir;
640}
641
642/*
643 * The Etc module provides access to information typically stored in
644 * files in the /etc directory on Unix systems.
645 *
646 * The information accessible consists of the information found in the
647 * /etc/passwd and /etc/group files, plus information about the system's
648 * temporary directory (/tmp) and configuration directory (/etc).
649 *
650 * The Etc module provides a more reliable way to access information about
651 * the logged in user than environment variables such as +$USER+.
652 *
653 * == Example:
654 *
655 *     require 'etc'
656 *
657 *     login = Etc.getlogin
658 *     info = Etc.getpwnam(login)
659 *     username = info.gecos.split(/,/).first
660 *     puts "Hello #{username}, I see your login name is #{login}"
661 *
662 * Note that the methods provided by this module are not always secure.
663 * It should be used for informational purposes, and not for security.
664 *
665 * All operations defined in this module are class methods, so that you can
666 * include the Etc module into your class.
667 */
668void
669Init_etc(void)
670{
671    VALUE mEtc;
672
673    mEtc = rb_define_module("Etc");
674    rb_define_module_function(mEtc, "getlogin", etc_getlogin, 0);
675
676    rb_define_module_function(mEtc, "getpwuid", etc_getpwuid, -1);
677    rb_define_module_function(mEtc, "getpwnam", etc_getpwnam, 1);
678    rb_define_module_function(mEtc, "setpwent", etc_setpwent, 0);
679    rb_define_module_function(mEtc, "endpwent", etc_endpwent, 0);
680    rb_define_module_function(mEtc, "getpwent", etc_getpwent, 0);
681    rb_define_module_function(mEtc, "passwd", etc_passwd, 0);
682
683    rb_define_module_function(mEtc, "getgrgid", etc_getgrgid, -1);
684    rb_define_module_function(mEtc, "getgrnam", etc_getgrnam, 1);
685    rb_define_module_function(mEtc, "group", etc_group, 0);
686    rb_define_module_function(mEtc, "setgrent", etc_setgrent, 0);
687    rb_define_module_function(mEtc, "endgrent", etc_endgrent, 0);
688    rb_define_module_function(mEtc, "getgrent", etc_getgrent, 0);
689    rb_define_module_function(mEtc, "sysconfdir", etc_sysconfdir, 0);
690    rb_define_module_function(mEtc, "systmpdir", etc_systmpdir, 0);
691
692    sPasswd =  rb_struct_define("Passwd",
693				"name", "passwd", "uid", "gid",
694#ifdef HAVE_ST_PW_GECOS
695				"gecos",
696#endif
697				"dir", "shell",
698#ifdef HAVE_ST_PW_CHANGE
699				"change",
700#endif
701#ifdef HAVE_ST_PW_QUOTA
702				"quota",
703#endif
704#ifdef HAVE_ST_PW_AGE
705				"age",
706#endif
707#ifdef HAVE_ST_PW_CLASS
708				"uclass",
709#endif
710#ifdef HAVE_ST_PW_COMMENT
711				"comment",
712#endif
713#ifdef HAVE_ST_PW_EXPIRE
714				"expire",
715#endif
716				NULL);
717    /* Define-const: Passwd
718     *
719     * Passwd is a Struct that contains the following members:
720     *
721     * name::
722     *	    contains the short login name of the user as a String.
723     * passwd::
724     *	    contains the encrypted password of the user as a String.
725     *	    an 'x' is returned if shadow passwords are in use. An '*' is returned
726     *      if the user cannot log in using a password.
727     * uid::
728     *	    contains the integer user ID (uid) of the user.
729     * gid::
730     *	    contains the integer group ID (gid) of the user's primary group.
731     * dir::
732     *	    contains the path to the home directory of the user as a String.
733     * shell::
734     *	    contains the path to the login shell of the user as a String.
735     *
736     * === The following members below are optional, and must be compiled with special flags:
737     *
738     * gecos::
739     *     contains a longer String description of the user, such as
740     *	   a full name. Some Unix systems provide structured information in the
741     *     gecos field, but this is system-dependent.
742     *     must be compiled with +HAVE_ST_PW_GECOS+
743     * change::
744     *     password change time(integer) must be compiled with +HAVE_ST_PW_CHANGE+
745     * quota::
746     *     quota value(integer) must be compiled with +HAVE_ST_PW_QUOTA+
747     * age::
748     *     password age(integer) must be compiled with +HAVE_ST_PW_AGE+
749     * class::
750     *     user access class(string) must be compiled with +HAVE_ST_PW_CLASS+
751     * comment::
752     *     comment(string) must be compiled with +HAVE_ST_PW_COMMENT+
753     * expire::
754     *	    account expiration time(integer) must be compiled with +HAVE_ST_PW_EXPIRE+
755     */
756    rb_define_const(mEtc, "Passwd", sPasswd);
757    rb_extend_object(sPasswd, rb_mEnumerable);
758    rb_define_singleton_method(sPasswd, "each", etc_each_passwd, 0);
759
760#ifdef HAVE_GETGRENT
761    sGroup = rb_struct_define("Group", "name",
762#ifdef HAVE_ST_GR_PASSWD
763			      "passwd",
764#endif
765			      "gid", "mem", NULL);
766
767    /* Define-const: Group
768     *
769     * Group is a Struct that is only available when compiled with +HAVE_GETGRENT+.
770     *
771     * The struct contains the following members:
772     *
773     * name::
774     *	    contains the name of the group as a String.
775     * passwd::
776     *	    contains the encrypted password as a String. An 'x' is
777     *	    returned if password access to the group is not available; an empty
778     *	    string is returned if no password is needed to obtain membership of
779     *	    the group.
780     *
781     *	    Must be compiled with +HAVE_ST_GR_PASSWD+.
782     * gid::
783     *	    contains the group's numeric ID as an integer.
784     * mem::
785     *	    is an Array of Strings containing the short login names of the
786     *	    members of the group.
787     */
788    rb_define_const(mEtc, "Group", sGroup);
789    rb_extend_object(sGroup, rb_mEnumerable);
790    rb_define_singleton_method(sGroup, "each", etc_each_group, 0);
791#endif
792}
793