source: trunk/base/src/pextlib1.0/Pextlib.c @ 51522

Last change on this file since 51522 was 51522, checked in by pguyot@…, 9 years ago

Escalation & dropping privileges: properly get the current gid (fixes #19131)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 29.8 KB
Line 
1/*
2 * Pextlib.c
3 * $Id: Pextlib.c 51522 2009-05-27 10:30:00Z pguyot@kallisys.net $
4 *
5 * Copyright (c) 2002 - 2003 Apple Computer, Inc.
6 * Copyright (c) 2004 - 2005 Paul Guyot <pguyot@kallisys.net>
7 * Copyright (c) 2004 Landon Fuller <landonf@macports.org>
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of Apple Computer, Inc. nor the names of its contributors
19 *    may be used to endorse or promote products derived from this software
20 *    without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 * POSSIBILITY OF SUCH DAMAGE.
33 */
34
35#if HAVE_CONFIG_H
36#include <config.h>
37#endif
38
39#include <ctype.h>
40#include <errno.h>
41#include <grp.h>
42#include <stdint.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46
47#if HAVE_STRINGS_H
48#include <strings.h>
49#endif
50
51#if HAVE_DIRENT_H
52#include <dirent.h>
53#endif
54
55#if HAVE_LIMITS_H
56#include <limits.h>
57#endif
58
59#if HAVE_PATHS_H
60#include <paths.h>
61#endif
62
63#ifndef _PATH_DEVNULL
64#define _PATH_DEVNULL   "/dev/null"
65#endif
66
67#include <pwd.h>
68
69#if HAVE_SYS_FILE_H
70#include <sys/file.h>
71#endif
72
73#if HAVE_SYS_TYPES_H
74#include <sys/types.h>
75#endif
76
77#if HAVE_SYS_FCNTL_H
78#include <sys/fcntl.h>
79#endif
80
81#if HAVE_FCNTL_H
82#include <fcntl.h>
83#endif
84
85#if HAVE_SYS_WAIT_H
86#include <sys/wait.h>
87#endif
88
89#if HAVE_UNISTD_H
90#include <unistd.h>
91#endif
92
93#if HAVE_SYS_SOCKET_H
94#include <sys/socket.h>
95#endif
96
97#if HAVE_SYS_STAT_H
98#include <sys/stat.h>
99#endif
100
101#include <tcl.h>
102
103#include "md5cmd.h"
104#include "sha1cmd.h"
105#include "rmd160cmd.h"
106#include "fs-traverse.h"
107#include "filemap.h"
108#include "curl.h"
109#include "xinstall.h"
110#include "vercomp.h"
111#include "readline.h"
112#include "uid.h"
113#include "tracelib.h"
114#include "tty.h"
115#include "get_systemconfiguration_proxies.h"
116
117#if HAVE_CRT_EXTERNS_H
118#include <crt_externs.h>
119#define environ (*_NSGetEnviron())
120#else
121extern char **environ;
122#endif
123
124#if !HAVE_BZERO
125#if HAVE_MEMSET
126#define bzero(b, len) (void)memset(b, 0x00, len)
127#endif
128#endif
129
130#if !HAVE_FGETLN
131char *fgetln(FILE *stream, size_t *len);
132#endif
133
134#define CBUFSIZ 30
135
136static char *
137ui_escape(const char *source)
138{
139        char *d, *dest;
140        const char *s;
141        int slen, dlen;
142
143        s = source;
144        slen = dlen = strlen(source) * 2 + 1;
145        d = dest = malloc(dlen);
146        if (dest == NULL) {
147                return NULL;
148        }
149        while(*s != '\0') {
150                switch(*s) {
151                        case '\\':
152                        case '}':
153                        case '{':
154                                *d = '\\';
155                                d++;
156                                *d = *s;
157                                d++;
158                                s++;
159                                break;
160                        case '\n':
161                                s++;
162                                break;
163                        default:
164                                *d = *s;
165                                d++;
166                                s++;
167                                break;
168                }
169        }
170        *d = '\0';
171        return dest;
172}
173
174int
175ui_info(Tcl_Interp *interp, char *mesg)
176{
177        const char ui_proc_start[] = "ui_info [subst -nocommands -novariables {";
178        const char ui_proc_end[] = "}]";
179        char *script, *string, *p;
180        int scriptlen, len, rval;
181
182        string = ui_escape(mesg);
183        if (string == NULL)
184                return TCL_ERROR;
185
186        len = strlen(string);
187        scriptlen = sizeof(ui_proc_start) + len + sizeof(ui_proc_end) - 1;
188        script = malloc(scriptlen);
189        if (script == NULL)
190                return TCL_ERROR;
191        else
192                p = script;
193
194        memcpy(script, ui_proc_start, sizeof(ui_proc_start));
195        strcat(script, string);
196        strcat(script, ui_proc_end);
197        free(string);
198        rval = Tcl_EvalEx(interp, script, scriptlen - 1, 0);
199        free(script);
200        return rval;
201}
202
203struct linebuf {
204        size_t len;
205        char *line;
206};
207
208int SystemCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
209{
210        char *buf;
211        struct linebuf circbuf[CBUFSIZ];
212        size_t linelen;
213        char *args[4];
214        char *cmdstring;
215        FILE *pdes;
216        int fdset[2], nullfd;
217        int fline, pos, ret;
218        int osetsid = 0;
219        pid_t pid;
220        Tcl_Obj *errbuf;
221        Tcl_Obj *tcl_result;
222        int read_failed, status;
223
224        /* usage: system [-notty] command */
225        if (objc == 2) {
226                cmdstring = Tcl_GetString(objv[1]);
227        } else if (objc == 3) {
228                char *arg = Tcl_GetString(objv[1]);
229                cmdstring = Tcl_GetString(objv[2]);
230
231                if (strcmp(arg, "-notty") == 0) {
232                        osetsid = 1;
233                } else {
234                        tcl_result = Tcl_NewStringObj("bad option ", -1);
235                        Tcl_AppendObjToObj(tcl_result, Tcl_NewStringObj(arg, -1));
236                        Tcl_SetObjResult(interp, tcl_result);
237                        return TCL_ERROR;
238                }
239        } else {
240                Tcl_WrongNumArgs(interp, 1, objv, "command");
241                return TCL_ERROR;
242        }
243
244        /*
245         * Fork a child to run the command, in a popen() like fashion -
246         * popen() itself is not used because stderr is also desired.
247         */
248        if (pipe(fdset) != 0) {
249                return TCL_ERROR;
250        }
251
252        pid = fork();
253        switch (pid) {
254        case -1: /* error */
255                return TCL_ERROR;
256                break;
257        case 0: /* child */
258                close(fdset[0]);
259
260                if ((nullfd = open(_PATH_DEVNULL, O_RDONLY)) == -1)
261                        _exit(1);
262                dup2(nullfd, STDIN_FILENO);
263                dup2(fdset[1], STDOUT_FILENO);
264                dup2(fdset[1], STDERR_FILENO);
265                /* drop the controlling terminal if requested */
266                if (osetsid) {
267                        if (setsid() == -1)
268                                _exit(1);
269                }
270                /* XXX ugly string constants */
271                args[0] = "sh";
272                args[1] = "-c";
273                args[2] = cmdstring;
274                args[3] = NULL;
275                execve("/bin/sh", args, environ);
276                _exit(1);
277                break;
278        default: /* parent */
279                break;
280        }
281
282        close(fdset[1]);
283
284        /* read from simulated popen() pipe */
285        read_failed = 0;
286        pos = 0;
287        bzero(circbuf, sizeof(circbuf));
288        pdes = fdopen(fdset[0], "r");
289        while ((buf = fgetln(pdes, &linelen)) != NULL) {
290                char *sbuf;
291                int slen;
292
293                /*
294                 * Allocate enough space to insert a terminating
295                 * '\0' if the line is not terminated with a '\n'
296                 */
297                if (buf[linelen - 1] == '\n')
298                        slen = linelen;
299                else
300                        slen = linelen + 1;
301
302                if (circbuf[pos].len == 0)
303                        sbuf = malloc(slen);
304                else {
305                        sbuf = realloc(circbuf[pos].line, slen);
306                }
307
308                if (sbuf == NULL) {
309                        read_failed = 1;
310                        break;
311                }
312
313                memcpy(sbuf, buf, linelen);
314                /* terminate line with '\0',replacing '\n' if it exists */
315                sbuf[slen - 1] = '\0';
316
317                circbuf[pos].line = sbuf;
318                circbuf[pos].len = slen;
319
320                if (pos++ == CBUFSIZ - 1) {
321                        pos = 0;
322                }
323
324                if (ui_info(interp, sbuf) != TCL_OK) {
325                        read_failed = 1;
326                        break;
327                }
328        }
329        fclose(pdes);
330
331        status = TCL_ERROR;
332
333        if (wait(&ret) == pid && WIFEXITED(ret) && !read_failed) {
334                /* Normal exit, and reading from the pipe didn't fail. */
335                if (WEXITSTATUS(ret) == 0) {
336                        status = TCL_OK;
337                } else {
338                        /* Copy the contents of the circular buffer to errbuf */
339                        Tcl_Obj* errorCode;
340                        errbuf = Tcl_NewStringObj(NULL, 0);
341                        for (fline = pos; pos < fline + CBUFSIZ; pos++) {
342                                if (circbuf[pos % CBUFSIZ].len == 0)
343                                continue; /* skip empty lines */
344
345                                /* Append line, minus trailing NULL */
346                                Tcl_AppendToObj(errbuf, circbuf[pos % CBUFSIZ].line,
347                                                circbuf[pos % CBUFSIZ].len - 1);
348
349                                /* Re-add previously stripped newline */
350                                Tcl_AppendToObj(errbuf, "\n", 1);
351                        }
352
353                        /* set errorCode [list CHILDSTATUS <pid> <code>] */
354                        errorCode = Tcl_NewListObj(0, NULL);
355                        Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewStringObj("CHILDSTATUS", -1));
356                        Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewIntObj(pid));
357                        Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewIntObj(WEXITSTATUS(ret)));
358                        Tcl_SetObjErrorCode(interp, errorCode);
359
360                        /* set result */
361                        tcl_result = Tcl_NewStringObj("shell command \"", -1);
362                        Tcl_AppendToObj(tcl_result, cmdstring, -1);
363                        Tcl_AppendToObj(tcl_result, "\" returned error ", -1);
364                        Tcl_AppendObjToObj(tcl_result, Tcl_NewIntObj(WEXITSTATUS(ret)));
365                        Tcl_AppendToObj(tcl_result, "\nCommand output: ", -1);
366                        Tcl_AppendObjToObj(tcl_result, errbuf);
367                        Tcl_SetObjResult(interp, tcl_result);
368                }
369        }
370
371        /* Cleanup. */
372        close(fdset[0]);
373        for (fline = 0; fline < CBUFSIZ; fline++) {
374                if (circbuf[fline].len != 0) {
375                        free(circbuf[fline].line);
376                }
377        }
378
379        return status;
380}
381
382int SudoCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
383{
384        char *buf;
385        struct linebuf circbuf[CBUFSIZ];
386        size_t linelen;
387        char *args[4];
388        char *cmdstring, *passwd;
389        FILE *pdes;
390        int fdset[2];
391        int fline, pos, ret;
392        pid_t pid;
393        Tcl_Obj *errbuf;
394        Tcl_Obj *tcl_result;
395
396        if (objc != 3) {
397                Tcl_WrongNumArgs(interp, 1, objv, "password command");
398                return TCL_ERROR;
399        }
400        passwd = Tcl_GetString(objv[1]);
401        cmdstring = Tcl_GetString(objv[2]);
402
403        if (pipe(fdset) == -1)
404                return TCL_ERROR;
405
406        /*
407         * Fork a child to run the command, in a popen() like fashion -
408         * popen() itself is not used because stderr is also desired.
409         */
410        pid = fork();
411        if (pid == -1)
412                return TCL_ERROR;
413        if (pid == 0) {
414                dup2(fdset[0], STDIN_FILENO);
415                dup2(fdset[1], STDOUT_FILENO);
416                dup2(fdset[1], STDERR_FILENO);
417                args[0] = "sudo";
418                args[1] = "-S";
419                args[2] = cmdstring;
420                args[3] = NULL;
421                execve("/usr/bin/sudo", args, environ);
422                /* Now throw away the privs we just acquired */
423                args[1] = "-k";
424                args[2] = NULL;
425                execve("/usr/bin/sudo", args, environ);
426                _exit(1);
427        } else {
428                write(fdset[1], passwd, strlen(passwd));
429                write(fdset[1], "\n", 1);
430                close(fdset[1]);
431        }
432        pdes = fdopen(fdset[0], "r");
433
434        /* read from simulated popen() pipe */
435        pos = 0;
436        bzero(circbuf, sizeof(circbuf));
437        while ((buf = fgetln(pdes, &linelen)) != NULL) {
438                char *sbuf;
439                int slen;
440
441                /*
442                 * Allocate enough space to insert a terminating
443                 * '\0' if the line is not terminated with a '\n'
444                 */
445                if (buf[linelen - 1] == '\n')
446                        slen = linelen;
447                else
448                        slen = linelen + 1;
449
450                if (circbuf[pos].len == 0)
451                        sbuf = malloc(slen);
452                else {
453                        sbuf = realloc(circbuf[pos].line, slen);
454                }
455
456                if (sbuf == NULL) {
457                        for (fline = pos; pos < fline + CBUFSIZ; pos++) {
458                                if (circbuf[pos % CBUFSIZ].len != 0)
459                                        free(circbuf[pos % CBUFSIZ].line);
460                        }
461                        return TCL_ERROR;
462                }
463
464                memcpy(sbuf, buf, linelen);
465                /* terminate line with '\0',replacing '\n' if it exists */
466                sbuf[slen - 1] = '\0';
467
468                circbuf[pos].line = sbuf;
469                circbuf[pos].len = slen;
470
471                if (pos++ == CBUFSIZ - 1)
472                        pos = 0;
473                ret = ui_info(interp, sbuf);
474                if (ret != TCL_OK) {
475                        for (fline = pos; pos < fline + CBUFSIZ; pos++) {
476                                if (circbuf[pos % CBUFSIZ].len != 0)
477                                        free(circbuf[pos % CBUFSIZ].line);
478                        }
479                        return ret;
480                }
481        }
482        fclose(pdes);
483
484        if (wait(&ret) != pid)
485                return TCL_ERROR;
486        if (WIFEXITED(ret)) {
487                if (WEXITSTATUS(ret) == 0)
488                        return TCL_OK;
489                else {
490                        /* Copy the contents of the circular buffer to errbuf */
491                        Tcl_Obj* errorCode;
492                        errbuf = Tcl_NewStringObj(NULL, 0);
493                        for (fline = pos; pos < fline + CBUFSIZ; pos++) {
494                                if (circbuf[pos % CBUFSIZ].len == 0)
495                                continue; /* skip empty lines */
496
497                                /* Append line, minus trailing NULL */
498                                Tcl_AppendToObj(errbuf, circbuf[pos % CBUFSIZ].line,
499                                                circbuf[pos % CBUFSIZ].len - 1);
500
501                                /* Re-add previously stripped newline */
502                                Tcl_AppendToObj(errbuf, "\n", 1);
503                                free(circbuf[pos % CBUFSIZ].line);
504                        }
505
506                        /* set errorCode [list CHILDSTATUS <pid> <code>] */
507                        errorCode = Tcl_NewListObj(0, NULL);
508                        Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewStringObj("CHILDSTATUS", -1));
509                        Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewIntObj(pid));
510                        Tcl_ListObjAppendElement(interp, errorCode, Tcl_NewIntObj(WEXITSTATUS(ret)));
511                        Tcl_SetObjErrorCode(interp, errorCode);
512
513                        /* set result */
514                        tcl_result = Tcl_NewStringObj("sudo command \"", -1);
515                        Tcl_AppendToObj(tcl_result, cmdstring, -1);
516                        Tcl_AppendToObj(tcl_result, "\" returned error ", -1);
517                        Tcl_AppendObjToObj(tcl_result, Tcl_NewIntObj(WEXITSTATUS(ret)));
518                        Tcl_AppendToObj(tcl_result, "\nCommand output: ", -1);
519                        Tcl_AppendObjToObj(tcl_result, errbuf);
520                        Tcl_SetObjResult(interp, tcl_result);
521                        return TCL_ERROR;
522                }
523        } else
524                return TCL_ERROR;
525}
526
527int FlockCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
528{
529        static const char errorstr[] = "use one of \"-shared\", \"-exclusive\", or \"-unlock\", and optionally \"-noblock\"";
530        int operation = 0, fd, i, ret;
531        int errnoval = 0;
532        int oshared = 0, oexclusive = 0, ounlock = 0, onoblock = 0;
533#if defined(HAVE_LOCKF) && !defined(HAVE_FLOCK)
534        off_t curpos;
535#endif
536        char *res;
537        Tcl_Channel channel;
538        ClientData handle;
539
540        if (objc < 3 || objc > 4) {
541                Tcl_WrongNumArgs(interp, 1, objv, "channelId switches");
542                return TCL_ERROR;
543        }
544
545        if ((channel = Tcl_GetChannel(interp, Tcl_GetString(objv[1]), NULL)) == NULL)
546                return TCL_ERROR;
547
548        if (Tcl_GetChannelHandle(channel, TCL_READABLE|TCL_WRITABLE, &handle) != TCL_OK) {
549                Tcl_SetResult(interp, "error getting channel handle", TCL_STATIC);
550                return TCL_ERROR;
551        }
552        fd = (int)(intptr_t)handle;
553
554        for (i = 2; i < objc; i++) {
555                char *arg = Tcl_GetString(objv[i]);
556                if (!strcmp(arg, "-shared")) {
557                  oshared = 1;
558                } else if (!strcmp(arg, "-exclusive")) {
559                  oexclusive = 1;
560                } else if (!strcmp(arg, "-unlock")) {
561                  ounlock = 1;
562                } else if (!strcmp(arg, "-noblock")) {
563                  onoblock = 1;
564                }
565        }
566
567        /* verify the arguments */
568
569        if((oshared + oexclusive + ounlock) != 1) {
570          /* only one of the options should have been specified */
571          Tcl_SetResult(interp, (void *) &errorstr, TCL_STATIC);
572          return TCL_ERROR;
573        }
574
575        if(onoblock && ounlock) {
576          /* should not be specified together */
577          Tcl_SetResult(interp, "-noblock can not be used with -unlock", TCL_STATIC);
578          return TCL_ERROR;
579        }
580         
581#if HAVE_FLOCK
582        /* prefer flock if present */
583        if(oshared) operation |= LOCK_SH;
584
585        if(oexclusive) operation |= LOCK_EX;
586
587        if(ounlock) operation |= LOCK_UN;
588
589        if(onoblock) operation |= LOCK_NB;
590
591        ret = flock(fd, operation);
592        if(ret == -1) {
593          errnoval = errno;
594        }
595#else
596#if HAVE_LOCKF
597        if(ounlock) operation = F_ULOCK;
598
599        /* lockf semantics don't map to shared locks. */
600        if(oshared || oexclusive) {
601          if(onoblock) {
602            operation = F_TLOCK;
603          } else {
604            operation = F_LOCK;
605          }
606        }
607
608        curpos = lseek(fd, 0, SEEK_CUR);
609        if(curpos == -1) {
610                Tcl_SetResult(interp, (void *) "Seek error", TCL_STATIC);
611                return TCL_ERROR;
612        }
613
614        ret = lockf(fd, operation, 0); /* lock entire file */
615
616        curpos = lseek(fd, curpos, SEEK_SET);
617        if(curpos == -1) {
618                Tcl_SetResult(interp, (void *) "Seek error", TCL_STATIC);
619                return TCL_ERROR;
620        }
621
622        if(ret == -1) {
623          errnoval = errno;
624          if((oshared || oexclusive)) {
625            /* map the errno val to what we would expect for flock */
626            if(onoblock && errnoval == EAGAIN) {
627              /* on some systems, EAGAIN=EWOULDBLOCK, but lets be safe */
628              errnoval = EWOULDBLOCK;
629            } else if(errnoval == EINVAL) {
630              errnoval = EOPNOTSUPP;
631            }
632          }
633        }
634#else
635#error no available locking implementation
636#endif /* HAVE_LOCKF */
637#endif /* HAVE_FLOCK */
638
639        if (ret != 0)
640        {
641                switch(errnoval) {
642                        case EAGAIN:
643                                res = "EAGAIN";
644                                break;
645                        case EBADF:
646                                res = "EBADF";
647                                break;
648                        case EINVAL:
649                                res = "EINVAL";
650                                break;
651                        case EOPNOTSUPP:
652                                res = "EOPNOTSUPP";
653                                break;
654                        default:
655                                res = strerror(errno);
656                                break;
657                }
658                Tcl_SetResult(interp, (void *) res, TCL_STATIC);
659                return TCL_ERROR;
660        }
661        return TCL_OK;
662}
663
664/**
665 *
666 * Return the list of elements in a directory.
667 * Since 1.60.4.2, the list doesn't include . and ..
668 *
669 * Synopsis: readdir directory
670 */
671int ReaddirCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
672{
673        DIR *dirp;
674        struct dirent *mp;
675        Tcl_Obj *tcl_result;
676        char *path;
677
678        if (objc != 2) {
679                Tcl_WrongNumArgs(interp, 1, objv, "directory");
680                return TCL_ERROR;
681        }
682
683        path = Tcl_GetString(objv[1]);
684        dirp = opendir(path);
685        if (!dirp) {
686                Tcl_SetResult(interp, "Cannot read directory", TCL_STATIC);
687                return TCL_ERROR;
688        }
689        tcl_result = Tcl_NewListObj(0, NULL);
690        while ((mp = readdir(dirp))) {
691                /* Skip . and .. */
692                if ((mp->d_name[0] != '.') ||
693                        ((mp->d_name[1] != 0)   /* "." */
694                                &&
695                        ((mp->d_name[1] != '.') || (mp->d_name[2] != 0)))) /* ".." */ {
696                        Tcl_ListObjAppendElement(interp, tcl_result, Tcl_NewStringObj(mp->d_name, -1));
697                }
698        }
699        closedir(dirp);
700        Tcl_SetObjResult(interp, tcl_result);
701       
702        return TCL_OK;
703}
704
705int StrsedCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
706{
707        char *pattern, *string, *res;
708        int range[2];
709        extern char *strsed(char *str, char *pat, int *range);
710        Tcl_Obj *tcl_result;
711
712        if (objc != 3) {
713                Tcl_WrongNumArgs(interp, 1, objv, "string pattern");
714                return TCL_ERROR;
715        }
716
717        string = Tcl_GetString(objv[1]);
718        pattern = Tcl_GetString(objv[2]);
719        res = strsed(string, pattern, range);
720        if (!res) {
721                Tcl_SetResult(interp, "strsed failed", TCL_STATIC);
722                return TCL_ERROR;
723        }
724        tcl_result = Tcl_NewStringObj(res, -1);
725        Tcl_SetObjResult(interp, tcl_result);
726        free(res);
727        return TCL_OK;
728}
729
730int MkdtempCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
731{
732        char *template, *sp;
733        Tcl_Obj *tcl_result;
734
735        if (objc != 2) {
736                Tcl_WrongNumArgs(interp, 1, objv, "template");
737                return TCL_ERROR;
738        }
739
740        template = strdup(Tcl_GetString(objv[1]));
741        if (template == NULL)
742                return TCL_ERROR;
743
744        if ((sp = mkdtemp(template)) == NULL) {
745                Tcl_AppendResult(interp, "mkdtemp failed: ", strerror(errno), NULL);
746                free(template);
747                return TCL_ERROR;
748        }
749
750        tcl_result = Tcl_NewStringObj(sp, -1);
751        Tcl_SetObjResult(interp, tcl_result);
752        free(template);
753        return TCL_OK;
754}
755
756int MktempCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
757{
758        char *template, *sp;
759        Tcl_Obj *tcl_result;
760
761        if (objc != 2) {
762                Tcl_WrongNumArgs(interp, 1, objv, "template");
763                return TCL_ERROR;
764        }
765
766        template = strdup(Tcl_GetString(objv[1]));
767        if (template == NULL)
768                return TCL_ERROR;
769
770        if ((sp = mktemp(template)) == NULL) {
771                Tcl_AppendResult(interp, "mktemp failed: ", strerror(errno), NULL);
772                free(template);
773                return TCL_ERROR;
774        }
775
776        tcl_result = Tcl_NewStringObj(sp, -1);
777        Tcl_SetObjResult(interp, tcl_result);
778        free(template);
779        return TCL_OK;
780}
781
782int MkstempCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
783{
784        Tcl_Channel channel;
785        char *template, *channelname;
786        int fd;
787
788        if (objc != 2) {
789                Tcl_WrongNumArgs(interp, 1, objv, "template");
790                return TCL_ERROR;
791        }
792
793        template = strdup(Tcl_GetString(objv[1]));
794        if (template == NULL)
795                return TCL_ERROR;
796
797        if ((fd = mkstemp(template)) < 0) {
798                Tcl_AppendResult(interp, "mkstemp failed: ", strerror(errno), NULL);
799                free(template);
800                return TCL_ERROR;
801        }
802
803        channel = Tcl_MakeFileChannel((ClientData)(intptr_t)fd, TCL_READABLE|TCL_WRITABLE);
804        Tcl_RegisterChannel(interp, channel);
805        channelname = (char *)Tcl_GetChannelName(channel);
806        Tcl_AppendResult(interp, channelname, " ", template, NULL);
807        free(template);
808        return TCL_OK;
809}
810
811int ExistsuserCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
812{
813        Tcl_Obj *tcl_result;
814        struct passwd *pwent;
815        char *user;
816
817        if (objc != 2) {
818                Tcl_WrongNumArgs(interp, 1, objv, "user");
819                return TCL_ERROR;
820        }
821
822        user = strdup(Tcl_GetString(objv[1]));
823        if (isdigit(*(user)))
824                pwent = getpwuid(strtol(user, 0, 0));
825        else
826                pwent = getpwnam(user);
827        free(user);
828
829        if (pwent == NULL)
830                tcl_result = Tcl_NewIntObj(0);
831        else
832                tcl_result = Tcl_NewIntObj(pwent->pw_uid);
833
834        Tcl_SetObjResult(interp, tcl_result);
835        return TCL_OK;
836}
837
838int ExistsgroupCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
839{
840        Tcl_Obj *tcl_result;
841        struct group *grent;
842        char *group;
843
844        if (objc != 2) {
845                Tcl_WrongNumArgs(interp, 1, objv, "groupname");
846                return TCL_ERROR;
847        }
848
849        group = strdup(Tcl_GetString(objv[1]));
850        if (isdigit(*(group)))
851                grent = getgrgid(strtol(group, 0, 0));
852        else
853                grent = getgrnam(group);
854        free(group);
855
856        if (grent == NULL)
857                tcl_result = Tcl_NewIntObj(0);
858        else
859                tcl_result = Tcl_NewIntObj(grent->gr_gid);
860
861        Tcl_SetObjResult(interp, tcl_result);
862        return TCL_OK;
863}
864
865/* Find the first unused UID > 100
866   previously this would find the highest used UID and add 1
867   but UIDs > 500 are visible on the login screen of OS X */
868int NextuidCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc UNUSED, Tcl_Obj *CONST objv[] UNUSED)
869{
870        Tcl_Obj *tcl_result;
871        int cur;
872
873        cur = MIN_USABLE_UID;
874       
875        while (getpwuid(cur) != NULL) {
876                cur++;
877        }
878       
879        tcl_result = Tcl_NewIntObj(cur);
880        Tcl_SetObjResult(interp, tcl_result);
881        return TCL_OK;
882}
883
884/* Just as with NextuidCmd, return the first unused gid > 100
885   groups aren't visible on the login screen, but I see no reason
886   to create group 502 when I can create group 100 */
887int NextgidCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc UNUSED, Tcl_Obj *CONST objv[] UNUSED)
888{
889        Tcl_Obj *tcl_result;
890        int cur;
891
892        cur = MIN_USABLE_GID;
893
894        while (getgrgid(cur) != NULL) {
895                cur++;
896        }
897       
898        tcl_result = Tcl_NewIntObj(cur);
899        Tcl_SetObjResult(interp, tcl_result);
900        return TCL_OK;
901}
902
903int UmaskCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc UNUSED, Tcl_Obj *CONST objv[] UNUSED)
904{
905        Tcl_Obj *tcl_result;
906        char *tcl_mask, *p;
907        const size_t stringlen = 4; /* 3 digits & \0 */
908        int i;
909        mode_t *set;
910        mode_t newmode;
911        mode_t oldmode;
912
913        if (objc != 2) {
914                Tcl_WrongNumArgs(interp, 1, objv, "mode");
915                return TCL_ERROR;
916        }
917
918        tcl_mask = Tcl_GetString(objv[1]);
919        if ((set = setmode(tcl_mask)) == NULL) {
920                Tcl_SetResult(interp, "Invalid umask mode", TCL_STATIC);
921                return TCL_ERROR;
922        }
923
924        newmode = getmode(set, 0);
925        free(set);
926
927        oldmode = umask(newmode);
928
929        tcl_mask = malloc(stringlen); /* 3 digits & \0 */
930        if (!tcl_mask) {
931                return TCL_ERROR;
932        }
933
934        /* Totally gross and cool */
935        p = tcl_mask + stringlen;
936        *p = '\0';
937        for (i = stringlen - 1; i > 0; i--) {
938                p--;
939                *p = (oldmode & 7) + '0';
940                oldmode >>= 3;
941        }
942        if (*p != '0') {
943                p--;
944                *p = '0';
945        }
946
947        tcl_result = Tcl_NewStringObj(p, -1);
948        free(tcl_mask);
949
950        Tcl_SetObjResult(interp, tcl_result);
951        return TCL_OK;
952}
953
954/**
955 * Call pipe(2) to create a pipe.
956 * Syntax is:
957 * pipe
958 *
959 * Generate a Tcl error if something goes wrong.
960 * Return a list with the file descriptors of the pipe. The first item is the
961 * readable fd.
962 */
963int PipeCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
964{
965        Tcl_Obj* result;
966        int fildes[2];
967
968        if (objc != 1) {
969                Tcl_WrongNumArgs(interp, 1, objv, NULL);
970                return TCL_ERROR;
971        }
972       
973        if (pipe(fildes) < 0) {
974                Tcl_AppendResult(interp, "pipe failed: ", strerror(errno), NULL);
975                return TCL_ERROR;
976        }
977       
978        /* build a list out of the couple */
979        result = Tcl_NewListObj(0, NULL);
980        Tcl_ListObjAppendElement(interp, result, Tcl_NewIntObj(fildes[0]));
981        Tcl_ListObjAppendElement(interp, result, Tcl_NewIntObj(fildes[1]));
982        Tcl_SetObjResult(interp, result);
983
984        return TCL_OK;
985}
986
987/**
988 * symlink value target
989 * Create a symbolic link at target pointing to value
990 * See symlink(2) for possible errors
991 */
992int CreateSymlinkCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
993{
994    char *value, *target;
995   
996    if (objc != 3) {
997        Tcl_WrongNumArgs(interp, 1, objv, "value target");
998        return TCL_ERROR;
999    }
1000   
1001    value = Tcl_GetString(objv[1]);
1002    target = Tcl_GetString(objv[2]);
1003   
1004    if (symlink(value, target) != 0) {
1005        Tcl_SetResult(interp, (char *)Tcl_PosixError(interp), TCL_STATIC);
1006        return TCL_ERROR;
1007    }
1008    return TCL_OK;
1009}
1010
1011/**
1012 * deletes environment variable
1013 *
1014 * Syntax is:
1015 * unsetenv name (* for all)
1016 */
1017int UnsetEnvCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
1018{
1019    char *name;
1020    char **envp;
1021    char *equals;
1022    size_t len;
1023    Tcl_Obj *tclList;
1024    int listLength;
1025    Tcl_Obj **listArray;
1026    int loopCounter;
1027   
1028    if (objc != 2) {
1029        Tcl_WrongNumArgs(interp, 1, objv, "name");
1030        return TCL_ERROR;
1031    }
1032
1033    name = Tcl_GetString(objv[1]);
1034    if (strchr(name, '=') != NULL) {
1035        Tcl_SetResult(interp, "only the name should be given", TCL_STATIC);
1036        return TCL_ERROR;
1037    }
1038
1039    if (strcmp(name, "*") == 0) {
1040#ifndef HAVE_CLEARENV
1041        /* unset all current environment variables; it'd be best to use
1042           clearenv() but that is not yet standardized, instead use Tcl's
1043           list capability to easily build an array of strings for each
1044           env name, then loop through that list to unsetenv() each one */
1045        tclList = Tcl_NewListObj( 0, NULL );
1046        Tcl_IncrRefCount( tclList );
1047        /* unset all current environment variables */
1048        for (envp = environ; *envp != NULL; envp++) {
1049            equals = strchr(*envp, '=');
1050            if (equals != NULL) {
1051                len = equals - *envp;
1052                Tcl_ListObjAppendElement(interp, tclList, Tcl_NewStringObj(*envp, len));
1053            }
1054        }
1055        Tcl_ListObjGetElements(interp, tclList, &listLength, &listArray);
1056        for (loopCounter = 0; loopCounter < listLength; loopCounter++) {
1057            unsetenv(Tcl_GetString(listArray[loopCounter]));
1058        }
1059        Tcl_DecrRefCount( tclList );
1060#else
1061        clearenv();
1062#endif
1063    } else {
1064        (void) unsetenv(name);
1065    }
1066    /* Tcl appears to become out of sync with the environment when we
1067       unset things, eg, 'info exists env(CC)' will succeed where
1068       'puts $env(CC)' will fail since it doesn't actually exist after
1069       being unset here.  This forces Tcl to resync to the current state
1070       (don't care about the actual result, so reset it) */
1071    Tcl_Eval(interp, "array get env");
1072    Tcl_ResetResult(interp);
1073
1074    return TCL_OK;
1075}
1076
1077/**
1078 *
1079 * Tcl wrapper around lchown() to allow changing ownership of symlinks
1080 * ('file attributes' follows the symlink).
1081 *
1082 * Synopsis: lchown filename user ?group?
1083 */
1084int lchownCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
1085{
1086    CONST char *path;
1087    long user;
1088    long group = -1;
1089
1090    if (objc != 3 && objc != 4) {
1091        Tcl_WrongNumArgs(interp, 1, objv, "filename user ?group?");
1092        return TCL_ERROR;
1093    }
1094
1095    path = Tcl_GetString(objv[1]);
1096    if (Tcl_GetLongFromObj(NULL, objv[2], &user) != TCL_OK) {
1097        CONST char *userString = Tcl_GetString(objv[2]);
1098        struct passwd *pwent = getpwnam(userString);
1099        if (pwent == NULL) {
1100            Tcl_SetResult(interp, "Unknown user given", TCL_STATIC);
1101            return TCL_ERROR;
1102        }
1103        user = pwent->pw_uid;
1104    }
1105    if (objc == 4) {
1106        if (Tcl_GetLongFromObj(NULL, objv[3], &group) != TCL_OK) {
1107           CONST char *groupString = Tcl_GetString(objv[3]);
1108           struct group *grent = getgrnam(groupString);
1109           if (grent == NULL) {
1110               Tcl_SetResult(interp, "Unknown group given", TCL_STATIC);
1111               return TCL_ERROR;
1112           }
1113           group = grent->gr_gid;
1114        }
1115    }
1116    if (lchown(path, (uid_t) user, (gid_t) group) != 0) {
1117        Tcl_SetResult(interp, (char *)Tcl_PosixError(interp), TCL_STATIC);
1118        return TCL_ERROR;
1119    }
1120
1121    return TCL_OK;
1122}
1123
1124int Pextlib_Init(Tcl_Interp *interp)
1125{
1126        if (Tcl_InitStubs(interp, "8.4", 0) == NULL)
1127                return TCL_ERROR;
1128
1129        Tcl_CreateObjCommand(interp, "system", SystemCmd, NULL, NULL);
1130        Tcl_CreateObjCommand(interp, "flock", FlockCmd, NULL, NULL);
1131        Tcl_CreateObjCommand(interp, "readdir", ReaddirCmd, NULL, NULL);
1132        Tcl_CreateObjCommand(interp, "strsed", StrsedCmd, NULL, NULL);
1133        Tcl_CreateObjCommand(interp, "mkstemp", MkstempCmd, NULL, NULL);
1134        Tcl_CreateObjCommand(interp, "mktemp", MktempCmd, NULL, NULL);
1135        Tcl_CreateObjCommand(interp, "mkdtemp", MkdtempCmd, NULL, NULL);
1136        Tcl_CreateObjCommand(interp, "existsuser", ExistsuserCmd, NULL, NULL);
1137        Tcl_CreateObjCommand(interp, "existsgroup", ExistsgroupCmd, NULL, NULL);
1138        Tcl_CreateObjCommand(interp, "nextuid", NextuidCmd, NULL, NULL);
1139        Tcl_CreateObjCommand(interp, "nextgid", NextgidCmd, NULL, NULL);
1140        Tcl_CreateObjCommand(interp, "md5", MD5Cmd, NULL, NULL);
1141        Tcl_CreateObjCommand(interp, "xinstall", InstallCmd, NULL, NULL);
1142        Tcl_CreateObjCommand(interp, "fs-traverse", FsTraverseCmd, NULL, NULL);
1143        Tcl_CreateObjCommand(interp, "filemap", FilemapCmd, NULL, NULL);
1144        Tcl_CreateObjCommand(interp, "rpm-vercomp", RPMVercompCmd, NULL, NULL);
1145        Tcl_CreateObjCommand(interp, "rmd160", RMD160Cmd, NULL, NULL);
1146        Tcl_CreateObjCommand(interp, "sha1", SHA1Cmd, NULL, NULL);
1147        Tcl_CreateObjCommand(interp, "umask", UmaskCmd, NULL, NULL);
1148        Tcl_CreateObjCommand(interp, "sudo", SudoCmd, NULL, NULL);
1149        Tcl_CreateObjCommand(interp, "pipe", PipeCmd, NULL, NULL);
1150        Tcl_CreateObjCommand(interp, "curl", CurlCmd, NULL, NULL);
1151        Tcl_CreateObjCommand(interp, "symlink", CreateSymlinkCmd, NULL, NULL);
1152        Tcl_CreateObjCommand(interp, "unsetenv", UnsetEnvCmd, NULL, NULL);
1153        Tcl_CreateObjCommand(interp, "lchown", lchownCmd, NULL, NULL);
1154       
1155        Tcl_CreateObjCommand(interp, "readline", ReadlineCmd, NULL, NULL);
1156        Tcl_CreateObjCommand(interp, "rl_history", RLHistoryCmd, NULL, NULL);
1157       
1158        Tcl_CreateObjCommand(interp, "getuid", getuidCmd, NULL, NULL);
1159        Tcl_CreateObjCommand(interp, "geteuid", geteuidCmd, NULL, NULL);
1160        Tcl_CreateObjCommand(interp, "getgid", getgidCmd, NULL, NULL);
1161        Tcl_CreateObjCommand(interp, "getegid", getegidCmd, NULL, NULL);
1162        Tcl_CreateObjCommand(interp, "setuid", setuidCmd, NULL, NULL);
1163        Tcl_CreateObjCommand(interp, "seteuid", seteuidCmd, NULL, NULL);
1164        Tcl_CreateObjCommand(interp, "setgid", setgidCmd, NULL, NULL);
1165        Tcl_CreateObjCommand(interp, "setegid", setegidCmd, NULL, NULL);
1166        Tcl_CreateObjCommand(interp, "name_to_uid", name_to_uidCmd, NULL, NULL);
1167        Tcl_CreateObjCommand(interp, "uid_to_name", uid_to_nameCmd, NULL, NULL);
1168        Tcl_CreateObjCommand(interp, "uname_to_gid", uname_to_gidCmd, NULL, NULL);
1169        Tcl_CreateObjCommand(interp, "name_to_gid", name_to_gidCmd, NULL, NULL);
1170        Tcl_CreateObjCommand(interp, "gid_to_name", gid_to_nameCmd, NULL, NULL);
1171       
1172        Tcl_CreateObjCommand(interp, "tracelib", TracelibCmd, NULL, NULL);
1173        Tcl_CreateObjCommand(interp, "isatty", IsattyCmd, NULL, NULL);
1174        Tcl_CreateObjCommand(interp, "term_get_size", TermGetSizeCmd, NULL, NULL);
1175        Tcl_CreateObjCommand(interp, "get_systemconfiguration_proxies", GetSystemConfigurationProxiesCmd, NULL, NULL);
1176
1177        if (Tcl_PkgProvide(interp, "Pextlib", "1.0") != TCL_OK)
1178                return TCL_ERROR;
1179
1180        /* init libcurl */
1181        CurlInit(interp);
1182
1183        return TCL_OK;
1184}
Note: See TracBrowser for help on using the repository browser.