1/*
2 * tclXunixId.c --
3 *
4 * Tcl commands to access getuid, setuid, getgid, setgid and friends on Unix.
5 *-----------------------------------------------------------------------------
6 * Copyright 1991-1999 Karl Lehenbauer and Mark Diekhans.
7 *
8 * Permission to use, copy, modify, and distribute this software and its
9 * documentation for any purpose and without fee is hereby granted, provided
10 * that the above copyright notice appear in all copies.  Karl Lehenbauer and
11 * Mark Diekhans make no representations about the suitability of this
12 * software for any purpose.  It is provided "as is" without express or
13 * implied warranty.
14 *-----------------------------------------------------------------------------
15 * $Id: tclXunixId.c,v 8.1 2001/10/24 23:31:50 hobbs Exp $
16 *-----------------------------------------------------------------------------
17 */
18
19#include "tclExtdInt.h"
20
21/*
22 * Actually configured number of groups (from sysconf if we have it).
23 */
24#ifndef NO_SYSCONF
25static int confNGroups = -1;
26#else
27#ifndef NGROUPS
28#   ifdef NGROUPS_MAX
29#       define NGROUPS NGROUPS_MAX
30#   else
31#       define NGROUPS 32
32#   endif
33#endif
34static int confNGroups = NGROUPS;
35#endif
36
37/*
38 * Prototypes of internal functions.
39 */
40static int
41UseridToUsernameResult _ANSI_ARGS_((Tcl_Interp *interp,
42                                    int         userId));
43
44static int
45UsernameToUseridResult _ANSI_ARGS_((Tcl_Interp *interp,
46                                    char       *userName));
47
48static int
49GroupidToGroupnameResult _ANSI_ARGS_((Tcl_Interp *interp,
50                                      int         groupId));
51
52static int
53GroupnameToGroupidResult _ANSI_ARGS_((Tcl_Interp *interp,
54                                      char       *groupName));
55
56static int
57IdConvert _ANSI_ARGS_((Tcl_Interp *interp,
58                       int         objc,
59                       Tcl_Obj   *CONST objv[]));
60
61static int
62IdEffective  _ANSI_ARGS_((Tcl_Interp  *interp,
63                          int          objc,
64                          Tcl_Obj      *CONST objv[]));
65
66static int
67IdProcess  _ANSI_ARGS_((Tcl_Interp    *interp,
68                        int            objc,
69                        Tcl_Obj      *CONST objv[]));
70
71static int
72IdGroupids  _ANSI_ARGS_((Tcl_Interp    *interp,
73                         int            objc,
74                         Tcl_Obj      *CONST objv[],
75                         int         symbolic));
76
77static int
78IdHost _ANSI_ARGS_((Tcl_Interp    *interp,
79                    int            objc,
80                    Tcl_Obj      *CONST objv[]));
81
82static int
83GetSetWrongArgs _ANSI_ARGS_((Tcl_Interp    *interp,
84                             Tcl_Obj      *CONST objv[]));
85
86static int
87IdUser _ANSI_ARGS_((Tcl_Interp    *interp,
88                    int            objc,
89                    Tcl_Obj      *CONST objv[]));
90
91static int
92IdUserId _ANSI_ARGS_((Tcl_Interp    *interp,
93                      int            objc,
94                      Tcl_Obj      *CONST objv[]));
95
96static int
97IdGroup _ANSI_ARGS_((Tcl_Interp    *interp,
98                     int            objc,
99                     Tcl_Obj      *CONST objv[]));
100
101static int
102IdGroupId _ANSI_ARGS_((Tcl_Interp    *interp,
103                       int            objc,
104                       Tcl_Obj      *CONST objv[]));
105
106static int
107TclX_IdObjCmd _ANSI_ARGS_((ClientData clientData,
108                           Tcl_Interp *interp,
109                           int objc,
110                           Tcl_Obj *CONST objv[]));
111
112/*-----------------------------------------------------------------------------
113 * TclX_IdObjCmd --
114 *     Implements the TclX id command on Unix.
115 *
116 *        id user ?name?
117 *        id convert user <name>
118 *
119 *        id userid ?uid?
120 *        id convert userid <uid>
121 *
122 *        id group ?name?
123 *        id convert group <name>
124 *
125 *        id groupid ?gid?
126 *        id convert groupid <gid>
127 *
128 *        id groupids
129 *
130 *        id host
131 *
132 *        id process
133 *        id process parent
134 *        id process group
135 *        id process group set
136 *
137 *        id effective user
138 *        id effective userid
139 *
140 *        id effective group
141 *        id effective groupid
142 *
143 * Results:
144 *  Standard TCL results, may return the UNIX system error message.
145 *
146 *-----------------------------------------------------------------------------
147 */
148
149static int
150UseridToUsernameResult (interp, userId)
151    Tcl_Interp *interp;
152    int         userId;
153{
154    uid_t          uid = (uid_t) userId;
155    struct passwd *pw = getpwuid (userId);
156    Tcl_Obj       *resultObj = Tcl_GetObjResult (interp);
157    char          userIdString[16];
158
159    if ((pw == NULL) || ((int) uid != userId)) {
160	sprintf (userIdString, "%d", uid);
161	Tcl_AppendStringsToObj (resultObj,
162	    "unknown user id: ",
163	    userIdString,
164	    NULL);
165        endpwent ();
166        return TCL_ERROR;
167    }
168    Tcl_AppendToObj (resultObj, pw->pw_name, -1);
169    endpwent ();
170    return TCL_OK;
171}
172
173static int
174UsernameToUseridResult (interp, userName)
175    Tcl_Interp *interp;
176    char       *userName;
177{
178    struct passwd *pw = getpwnam (userName);
179    Tcl_Obj       *resultObj = Tcl_GetObjResult (interp);
180
181    if (pw == NULL) {
182	Tcl_AppendStringsToObj (resultObj,
183				"unknown user id: ",
184				userName,
185				 (char *) NULL);
186        endpwent ();
187        return TCL_ERROR;
188    }
189    Tcl_SetObjResult (interp, Tcl_NewIntObj (pw->pw_uid));
190    endpwent ();
191    return TCL_OK;
192}
193
194static int
195GroupidToGroupnameResult (interp, groupId)
196    Tcl_Interp *interp;
197    int         groupId;
198{
199    gid_t          gid = (gid_t) groupId;
200    struct group  *grp = getgrgid (groupId);
201    Tcl_Obj       *resultObj = Tcl_GetObjResult (interp);
202    char          groupIdString[16];
203
204    sprintf (groupIdString, "%d", gid);
205
206    if ((grp == NULL) || ((int) gid != groupId)) {
207	Tcl_AppendStringsToObj (resultObj,
208				"unknown group id: ",
209				groupIdString,
210				(char *)NULL);
211        endgrent ();
212        return TCL_ERROR;
213    }
214    Tcl_AppendToObj (resultObj, grp->gr_name, -1);
215    endgrent ();
216    return TCL_OK;
217}
218
219static int
220GroupnameToGroupidResult (interp, groupName)
221    Tcl_Interp *interp;
222    char       *groupName;
223{
224    struct group  *grp = getgrnam (groupName);
225    Tcl_Obj       *resultObj = Tcl_GetObjResult (interp);
226    if (grp == NULL) {
227        Tcl_AppendStringsToObj (resultObj,
228				"unknown group id: ",
229				groupName,
230				(char *) NULL);
231        return TCL_ERROR;
232    }
233    Tcl_SetIntObj (resultObj, grp->gr_gid);
234    return TCL_OK;
235}
236
237/*
238 * id convert type value
239 */
240static int
241IdConvert (interp, objc, objv)
242    Tcl_Interp    *interp;
243    int            objc;
244    Tcl_Obj      *CONST objv[];
245{
246    long           uid;
247    long           gid;
248    char          *subCommand;
249    char          *valueString;
250
251    if (objc != 4)
252        return TclX_WrongArgs (interp, objv [0], "convert type value");
253
254    subCommand = Tcl_GetStringFromObj (objv[2], NULL);
255    valueString = Tcl_GetStringFromObj (objv[3], NULL);
256
257    if (STREQU (subCommand, "user"))
258        return UsernameToUseridResult (interp, valueString);
259
260    if (STREQU (subCommand, "userid")) {
261        if (Tcl_GetLongFromObj (interp, objv[3], &uid) != TCL_OK)
262            return TCL_ERROR;
263        return UseridToUsernameResult (interp, uid);
264    }
265
266    if (STREQU (subCommand, "group"))
267        return GroupnameToGroupidResult (interp, valueString);
268
269    if (STREQU (subCommand, "groupid")) {
270        if (Tcl_GetLongFromObj (interp, objv[3], &gid) != TCL_OK)
271            return TCL_ERROR;
272        return GroupidToGroupnameResult (interp, gid);
273
274    }
275    TclX_AppendObjResult (interp, "third arg must be \"user\", \"userid\", ",
276                          "\"group\" or \"groupid\", got \"", subCommand, "\"",
277                          (char *) NULL);
278    return TCL_ERROR;
279}
280
281/*
282 * id effective type
283 */
284static int
285IdEffective (interp, objc, objv)
286    Tcl_Interp    *interp;
287    int            objc;
288    Tcl_Obj      *CONST objv[];
289{
290    char          *subCommand;
291
292    if (objc != 3)
293        return TclX_WrongArgs (interp, objv [0], "effective type");
294
295    subCommand = Tcl_GetStringFromObj (objv[2], NULL);
296
297    if (STREQU (subCommand, "user"))
298        return UseridToUsernameResult (interp, geteuid ());
299
300    if (STREQU (subCommand, "userid")) {
301	Tcl_SetObjResult (interp, Tcl_NewIntObj (geteuid ()));
302        return TCL_OK;
303    }
304
305    if (STREQU (subCommand, "group"))
306        return GroupidToGroupnameResult (interp, getegid ());
307
308    if (STREQU (subCommand, "groupid")) {
309	Tcl_SetObjResult (interp, Tcl_NewIntObj (getegid ()));
310        return TCL_OK;
311    }
312
313    TclX_AppendObjResult (interp, "third arg must be \"user\", \"userid\", ",
314                          "\"group\" or \"groupid\", got \"",
315                          subCommand, "\"", (char *) NULL);
316    return TCL_ERROR;
317}
318
319/*
320 * id process ?parent|group? ?set?
321 */
322static int
323IdProcess (interp, objc, objv)
324    Tcl_Interp    *interp;
325    int            objc;
326    Tcl_Obj      *CONST objv[];
327{
328    pid_t          pid;
329    char          *subCommand;
330    char          *trailerCommand;
331
332    if (objc > 4)
333        return TclX_WrongArgs (interp,
334			       objv [0],
335			       "process ?parent|group? ?set?");
336
337    if (objc == 2) {
338	Tcl_SetObjResult (interp, Tcl_NewIntObj (getpid ()));
339        return TCL_OK;
340    }
341
342    subCommand = Tcl_GetStringFromObj (objv[2], NULL);
343
344    if (STREQU (subCommand, "parent")) {
345        if (objc != 3)
346            return TclX_WrongArgs (interp, objv [0],
347                              " process parent");
348
349	Tcl_SetObjResult (interp, Tcl_NewIntObj (getppid ()));
350        return TCL_OK;
351    }
352    if (STREQU (subCommand, "group")) {
353        if (objc == 3) {
354	    Tcl_SetObjResult (interp, Tcl_NewIntObj (getpgrp ()));
355            return TCL_OK;
356        }
357	trailerCommand = Tcl_GetStringFromObj (objv[3], NULL);
358        if ((objc != 4) || !STREQU (trailerCommand, "set"))
359            return TclX_WrongArgs (interp, objv [0],
360                              " process group ?set?");
361
362        if (Tcl_IsSafe (interp)) {
363            TclX_AppendObjResult (interp,  "can't set process group from a ",
364                                  "safe interpeter", (char *) NULL);
365            return TCL_ERROR;
366        }
367
368#ifndef NO_SETPGID
369        pid = getpid ();
370        setpgid (pid, pid);
371#else
372        setpgrp ();
373#endif
374        return TCL_OK;
375    }
376
377    TclX_AppendObjResult (interp, "expected one of \"parent\" or \"group\" ",
378                          "got\"", subCommand, "\"", (char *) NULL);
379    return TCL_ERROR;
380}
381
382/*
383 * id groupids
384 * id groups
385 */
386static int
387IdGroupids (interp, objc, objv, symbolic)
388    Tcl_Interp    *interp;
389    int            objc;
390    Tcl_Obj      *CONST objv[];
391    int            symbolic;
392{
393#ifndef NO_GETGROUPS
394    gid_t         *groups;
395    int            nGroups, groupIndex;
396    struct group  *grp;
397    Tcl_Obj       *resultObj = Tcl_GetObjResult (interp);
398    Tcl_Obj	  *newObj;
399
400    if (objc != 2)
401        return TclX_WrongArgs (interp, objv [0], "arg");
402
403#ifndef NO_SYSCONF
404    if (confNGroups < 0)
405        confNGroups = sysconf (_SC_NGROUPS_MAX);
406#endif
407    groups = (gid_t *) ckalloc (confNGroups * sizeof (gid_t));
408
409
410    nGroups = getgroups (confNGroups, groups);
411    if (nGroups < 0) {
412        Tcl_AppendStringsToObj (Tcl_GetObjResult (interp),
413                                Tcl_PosixError (interp), (char *) NULL);
414        ckfree ((char *) groups);
415        return TCL_ERROR;
416    }
417
418    for (groupIndex = 0; groupIndex < nGroups; groupIndex++) {
419        if (symbolic) {
420	    int    groupId = groups [groupIndex];
421            grp = getgrgid (groupId);
422            if (grp == NULL) {
423		char    groupIdString[16];
424
425		sprintf (groupIdString, "%d", groupId);
426		Tcl_AppendStringsToObj (resultObj,
427		    "unknown group id: ",
428		    groupIdString,
429		    (char *)NULL);
430                endgrent ();
431                return TCL_ERROR;
432            }
433	    newObj = Tcl_NewStringObj (grp->gr_name, -1);
434            Tcl_ListObjAppendElement (interp,
435				      resultObj,
436				      newObj);
437        } else {
438	    newObj = Tcl_NewIntObj(groups[groupIndex]);
439            Tcl_ListObjAppendElement (interp,
440				      resultObj,
441				      newObj);
442        }
443    }
444    if (symbolic)
445        endgrent ();
446    ckfree ((char *) groups);
447    return TCL_OK;
448#else
449    TclX_AppendObjResult (interp, "group id lists unavailable on this system ",
450                          "(no getgroups function)", (char *) NULL);
451    return TCL_ERROR;
452#endif
453}
454
455/*
456 * id host
457 */
458static int
459IdHost (interp, objc, objv)
460    Tcl_Interp    *interp;
461    int            objc;
462    Tcl_Obj      *CONST objv[];
463{
464#ifndef NO_GETHOSTNAME
465#ifndef MAXHOSTNAMELEN
466#  define MAXHOSTNAMELEN 256
467#endif
468    char hostNameBuf[MAXHOSTNAMELEN];
469
470    if (objc != 2)
471        return TclX_WrongArgs (interp, objv [0], "host");
472
473	if (gethostname (hostNameBuf, MAXHOSTNAMELEN) < 0) {
474            TclX_AppendObjResult (interp, Tcl_PosixError (interp),
475                                  (char *) NULL);
476	    return TCL_ERROR;
477	}
478	hostNameBuf[MAXHOSTNAMELEN-1] = '\0';
479	Tcl_SetObjResult (interp, Tcl_NewStringObj (hostNameBuf, -1));
480	return TCL_OK;
481#else
482        TclX_AppendObjResult (interp, "host name unavailable on this system ",
483                              "(no gethostname function)", (char *) NULL);
484        return TCL_ERROR;
485#endif
486}
487
488/*
489 * Return error when a get set function has too many args (2 or 3 expected).
490 */
491static int
492GetSetWrongArgs (interp, objv)
493    Tcl_Interp    *interp;
494    Tcl_Obj      *CONST objv[];
495{
496    return TclX_WrongArgs (interp, objv [0], "arg ?value?");
497}
498
499/*
500 * id user
501 */
502static int
503IdUser (interp, objc, objv)
504    Tcl_Interp *interp;
505    int            objc;
506    Tcl_Obj      *CONST objv[];
507{
508    struct passwd *pw;
509    char          *user;
510
511    if (objc > 3)
512        return GetSetWrongArgs (interp, objv);
513
514    if (objc == 2) {
515        return UseridToUsernameResult (interp, getuid ());
516    }
517
518    user = Tcl_GetStringFromObj (objv[2], NULL);
519
520    pw = getpwnam (user);
521    if (pw == NULL) {
522        TclX_AppendObjResult (interp, "user \"",user, "\" does not exist",
523                              (char *) NULL);
524        goto errorExit;
525    }
526    if (setuid (pw->pw_uid) < 0) {
527        TclX_AppendObjResult (interp, Tcl_PosixError (interp), (char *) NULL);
528        goto errorExit;
529    }
530    endpwent ();
531    return TCL_OK;
532
533  errorExit:
534    endpwent ();
535    return TCL_ERROR;
536}
537
538/*
539 * id userid
540 */
541static int
542IdUserId (interp, objc, objv)
543    Tcl_Interp    *interp;
544    int            objc;
545    Tcl_Obj      *CONST objv[];
546{
547    int uid;
548
549    if (objc > 3)
550        return GetSetWrongArgs (interp, objv);
551
552    if (objc == 2) {
553	Tcl_SetObjResult (interp, Tcl_NewIntObj (getuid()));
554        return TCL_OK;
555    }
556
557    if (Tcl_GetIntFromObj (interp, objv[2], &uid) != TCL_OK)
558        return TCL_ERROR;
559
560    if (setuid ((uid_t) uid) < 0) {
561        TclX_AppendObjResult (interp, Tcl_PosixError (interp), (char *) NULL);
562        return TCL_ERROR;
563    }
564
565    return TCL_OK;
566}
567
568/*
569 * id group
570 */
571static int
572IdGroup (interp, objc, objv)
573    Tcl_Interp    *interp;
574    int            objc;
575    Tcl_Obj      *CONST objv[];
576{
577    struct group *grp;
578    char         *groupName;
579
580    if (objc > 3)
581        return GetSetWrongArgs (interp, objv);
582
583    if (objc == 2) {
584        return GroupidToGroupnameResult (interp, getgid ());
585    }
586
587    groupName = Tcl_GetStringFromObj (objv[2], NULL);
588
589    grp = getgrnam (groupName);
590    if (grp == NULL) {
591        TclX_AppendObjResult (interp, "group \"", groupName,
592                              "\" does not exist", (char *) NULL);
593        goto errorExit;
594    }
595    if (setgid (grp->gr_gid) < 0) {
596        TclX_AppendObjResult (interp, Tcl_PosixError (interp), (char *) NULL);
597        goto errorExit;
598    }
599    endgrent ();
600    return TCL_OK;
601
602  errorExit:
603    endgrent ();
604    return TCL_ERROR;
605}
606
607/*
608 * id groupid
609 */
610static int
611IdGroupId (interp, objc, objv)
612    Tcl_Interp    *interp;
613    int            objc;
614    Tcl_Obj      *CONST objv[];
615{
616    int gid;
617
618    if (objc > 3)
619        return GetSetWrongArgs (interp, objv);
620
621    if (objc == 2) {
622	Tcl_SetIntObj (Tcl_GetObjResult (interp), getgid());
623        return TCL_OK;
624    }
625
626    if (Tcl_GetIntFromObj (interp, objv[2], &gid) != TCL_OK)
627        return TCL_ERROR;
628
629    if (setgid ((gid_t) gid) < 0) {
630        TclX_AppendObjResult (interp, Tcl_PosixError (interp), (char *) NULL);
631        return TCL_ERROR;
632    }
633
634    return TCL_OK;
635}
636
637static int
638TclX_IdObjCmd (clientData, interp, objc, objv)
639    ClientData  clientData;
640    Tcl_Interp *interp;
641    int         objc;
642    Tcl_Obj   *CONST objv[];
643{
644    char       *subCommand;
645
646    if (objc < 2)
647	return TclX_WrongArgs (interp, objv [0], "arg ?arg...?");
648
649    subCommand = Tcl_GetStringFromObj (objv [1], NULL);
650    /*
651     * If the first argument is "convert", handle the conversion.
652     */
653    if (STREQU (subCommand, "convert")) {
654        return IdConvert (interp, objc, objv);
655    }
656
657    /*
658     * If the first argument is "effective", return the effective user ID,
659     * name, group ID or name.
660     */
661    if (STREQU (subCommand, "effective")) {
662        return IdEffective (interp, objc, objv);
663    }
664
665    /*
666     * If the first argument is "process", return the process ID, parent's
667     * process ID, process group or set the process group depending on args.
668     */
669    if (STREQU (subCommand, "process")) {
670        return IdProcess (interp, objc, objv);
671    }
672
673    /*
674     * Handle getting list of groups the user is a member of.
675     */
676    if (STREQU (subCommand, "groups")) {
677        return IdGroupids (interp, objc, objv, TRUE);
678    }
679
680    if (STREQU (subCommand, "groupids")) {
681        return IdGroupids (interp, objc, objv, FALSE);
682    }
683
684    /*
685     * Handle returning the host name if its available.
686     */
687    if (STREQU (subCommand, "host")) {
688        return IdHost (interp, objc, objv);
689    }
690
691    /*
692     * Handle setting or returning the user ID or group ID (by name or number).
693     */
694    if (STREQU (subCommand, "user")) {
695        return IdUser (interp, objc, objv);
696    }
697
698    if (STREQU (subCommand, "userid")) {
699        return IdUserId (interp, objc, objv);
700    }
701
702    if (STREQU (subCommand, "group")) {
703        return IdGroup (interp, objc, objv);
704    }
705
706    if (STREQU (subCommand, "groupid")) {
707        return IdGroupId (interp, objc, objv);
708    }
709
710    TclX_AppendObjResult (interp, "second arg must be one of \"convert\", ",
711                          "\"effective\", \"process\", ",
712                          "\"user\", \"userid\", \"group\", \"groupid\", ",
713                          "\"groups\", \"groupids\", ",
714                          "or \"host\"", (char *) NULL);
715    return TCL_ERROR;
716}
717
718
719/*-----------------------------------------------------------------------------
720 * TclX_IdInit --
721 *     Initialize the id command.
722 *-----------------------------------------------------------------------------
723 */
724void
725TclX_IdInit (interp)
726    Tcl_Interp *interp;
727{
728    Tcl_CreateObjCommand (interp,
729			  "id",
730			  TclX_IdObjCmd,
731                          (ClientData) NULL,
732			  (Tcl_CmdDeleteProc*) NULL);
733}
734