~ubuntu-branches/ubuntu/hoary/postfix/hoary-security

« back to all changes in this revision

Viewing changes to src/util/spawn_command.c

  • Committer: Bazaar Package Importer
  • Author(s): LaMont Jones
  • Date: 2004-10-06 11:50:33 UTC
  • Revision ID: james.westby@ubuntu.com-20041006115033-ooo6yfg6kmoteu04
Tags: upstream-2.1.3
ImportĀ upstreamĀ versionĀ 2.1.3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*++
 
2
/* NAME
 
3
/*      spawn_command 3
 
4
/* SUMMARY
 
5
/*      run external command
 
6
/* SYNOPSIS
 
7
/*      #include <spawn_command.h>
 
8
/*
 
9
/*      WAIT_STATUS_T spawn_command(key, value, ...)
 
10
/*      int     key;
 
11
/* DESCRIPTION
 
12
/*      spawn_command() runs a command in a child process and returns
 
13
/*      the command exit status.
 
14
/*
 
15
/*      Arguments:
 
16
/* .IP key
 
17
/*      Specifies what value will follow. spawn_command() takes a list
 
18
/*      of (key, value) arguments, terminated by SPAWN_CMD_END. The
 
19
/*      following is a listing of key codes together with the expected
 
20
/*      value type.
 
21
/* .RS
 
22
/* .IP "SPAWN_CMD_COMMAND (char *)"
 
23
/*      Specifies the command to execute as a string. The string is
 
24
/*      passed to the shell when it contains shell meta characters
 
25
/*      or when it appears to be a shell built-in command, otherwise
 
26
/*      the command is executed without invoking a shell.
 
27
/*      One of SPAWN_CMD_COMMAND or SPAWN_CMD_ARGV must be specified.
 
28
/*      See also the SPAWN_CMD_SHELL attribute below.
 
29
/* .IP "SPAWN_CMD_ARGV (char **)"
 
30
/*      The command is specified as an argument vector. This vector is
 
31
/*      passed without further inspection to the \fIexecvp\fR() routine.
 
32
/*      One of SPAWN_CMD_COMMAND or SPAWN_CMD_ARGV must be specified.
 
33
/* .IP "SPAWN_CMD_ENV (char **)"
 
34
/*      Additional environment information, in the form of a null-terminated
 
35
/*      list of name, value, name, value, ... elements. By default only the
 
36
/*      command search path is initialized to _PATH_DEFPATH.
 
37
/* .IP "SPAWN_CMD_EXPORT (char **)"
 
38
/*      Null-terminated array of names of environment parameters that can
 
39
/*      be exported. By default, everything is exported.
 
40
/* .IP "SPAWN_CMD_STDIN (int)"
 
41
/* .IP "SPAWN_CMD_STDOUT (int)"
 
42
/* .IP "SPAWN_CMD_STDERR (int)"
 
43
/*      Each of these specifies I/O redirection of one of the standard file
 
44
/*      descriptors for the command.
 
45
/* .IP "SPAWN_CMD_UID (int)"
 
46
/*      The user ID to execute the command as.
 
47
/* .IP "SPAWN_CMD_GID (int)"
 
48
/*      The group ID to execute the command as.
 
49
/* .IP "SPAWN_CMD_TIME_LIMIT (int)"
 
50
/*      The amount of time in seconds the command is allowed to run before
 
51
/*      it is terminated with SIGKILL. The default is no time limit.
 
52
/* .IP "SPAWN_CMD_SHELL (char *)"
 
53
/*      The shell to use when executing the command specified with
 
54
/*      SPAWN_CMD_COMMAND. This shell is invoked regardless of the
 
55
/*      command content.
 
56
/* .RE
 
57
/* DIAGNOSTICS
 
58
/*      Panic: interface violations (for example, a missing command).
 
59
/*
 
60
/*      Fatal error: fork() failure, other system call failures.
 
61
/*
 
62
/*      spawn_command() returns the exit status as defined by wait(2).
 
63
/* LICENSE
 
64
/* .ad
 
65
/* .fi
 
66
/*      The Secure Mailer license must be distributed with this software.
 
67
/* SEE ALSO
 
68
/*      exec_command(3) execute command
 
69
/* AUTHOR(S)
 
70
/*      Wietse Venema
 
71
/*      IBM T.J. Watson Research
 
72
/*      P.O. Box 704
 
73
/*      Yorktown Heights, NY 10598, USA
 
74
/*--*/
 
75
 
 
76
/* System library. */
 
77
 
 
78
#include <sys_defs.h>
 
79
#include <sys/wait.h>
 
80
#include <signal.h>
 
81
#include <unistd.h>
 
82
#include <errno.h>
 
83
#include <stdarg.h>
 
84
#include <stdlib.h>
 
85
#ifdef USE_PATHS_H
 
86
#include <paths.h>
 
87
#endif
 
88
#include <syslog.h>
 
89
 
 
90
/* Utility library. */
 
91
 
 
92
#include <msg.h>
 
93
#include <timed_wait.h>
 
94
#include <set_ugid.h>
 
95
#include <argv.h>
 
96
#include <spawn_command.h>
 
97
#include <exec_command.h>
 
98
#include <clean_env.h>
 
99
 
 
100
/* Application-specific. */
 
101
 
 
102
struct spawn_args {
 
103
    char  **argv;                       /* argument vector */
 
104
    char   *command;                    /* or a plain string */
 
105
    int     stdin_fd;                   /* read stdin here */
 
106
    int     stdout_fd;                  /* write stdout here */
 
107
    int     stderr_fd;                  /* write stderr here */
 
108
    uid_t   uid;                        /* privileges */
 
109
    gid_t   gid;                        /* privileges */
 
110
    char  **env;                        /* extra environment */
 
111
    char  **export;                     /* exportable environment */
 
112
    char   *shell;                      /* command shell */
 
113
    int     time_limit;                 /* command time limit */
 
114
};
 
115
 
 
116
/* get_spawn_args - capture the variadic argument list */
 
117
 
 
118
static void get_spawn_args(struct spawn_args * args, int init_key, va_list ap)
 
119
{
 
120
    char   *myname = "get_spawn_args";
 
121
    int     key;
 
122
 
 
123
    /*
 
124
     * First, set the default values.
 
125
     */
 
126
    args->argv = 0;
 
127
    args->command = 0;
 
128
    args->stdin_fd = -1;
 
129
    args->stdout_fd = -1;
 
130
    args->stderr_fd = -1;
 
131
    args->uid = (uid_t) - 1;
 
132
    args->gid = (gid_t) - 1;
 
133
    args->env = 0;
 
134
    args->export = 0;
 
135
    args->shell = 0;
 
136
    args->time_limit = 0;
 
137
 
 
138
    /*
 
139
     * Then, override the defaults with user-supplied inputs.
 
140
     */
 
141
    for (key = init_key; key != SPAWN_CMD_END; key = va_arg(ap, int)) {
 
142
        switch (key) {
 
143
        case SPAWN_CMD_ARGV:
 
144
            if (args->command)
 
145
                msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND",
 
146
                          myname);
 
147
            args->argv = va_arg(ap, char **);
 
148
            break;
 
149
        case SPAWN_CMD_COMMAND:
 
150
            if (args->argv)
 
151
                msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND",
 
152
                          myname);
 
153
            args->command = va_arg(ap, char *);
 
154
            break;
 
155
        case SPAWN_CMD_STDIN:
 
156
            args->stdin_fd = va_arg(ap, int);
 
157
            break;
 
158
        case SPAWN_CMD_STDOUT:
 
159
            args->stdout_fd = va_arg(ap, int);
 
160
            break;
 
161
        case SPAWN_CMD_STDERR:
 
162
            args->stderr_fd = va_arg(ap, int);
 
163
            break;
 
164
        case SPAWN_CMD_UID:
 
165
            args->uid = va_arg(ap, int);        /* in case uid_t is short */
 
166
            break;
 
167
        case SPAWN_CMD_GID:
 
168
            args->gid = va_arg(ap, int);        /* in case gid_t is short */
 
169
            break;
 
170
        case SPAWN_CMD_TIME_LIMIT:
 
171
            args->time_limit = va_arg(ap, int);
 
172
            break;
 
173
        case SPAWN_CMD_ENV:
 
174
            args->env = va_arg(ap, char **);
 
175
            break;
 
176
        case SPAWN_CMD_EXPORT:
 
177
            args->export = va_arg(ap, char **);
 
178
            break;
 
179
        case SPAWN_CMD_SHELL:
 
180
            args->shell = va_arg(ap, char *);
 
181
            break;
 
182
        default:
 
183
            msg_panic("%s: unknown key: %d", myname, key);
 
184
        }
 
185
    }
 
186
    if (args->command == 0 && args->argv == 0)
 
187
        msg_panic("%s: missing SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND", myname);
 
188
    if (args->command == 0 && args->shell != 0)
 
189
        msg_panic("%s: SPAWN_CMD_ARGV cannot be used with SPAWN_CMD_SHELL",
 
190
                  myname);
 
191
}
 
192
 
 
193
/* spawn_command - execute command with extreme prejudice */
 
194
 
 
195
WAIT_STATUS_T spawn_command(int key,...)
 
196
{
 
197
    char   *myname = "spawn_comand";
 
198
    va_list ap;
 
199
    pid_t   pid;
 
200
    WAIT_STATUS_T wait_status;
 
201
    struct spawn_args args;
 
202
    char  **cpp;
 
203
    ARGV   *argv;
 
204
    int     err;
 
205
 
 
206
    /*
 
207
     * Process the variadic argument list. This also does sanity checks on
 
208
     * what data the caller is passing to us.
 
209
     */
 
210
    va_start(ap, key);
 
211
    get_spawn_args(&args, key, ap);
 
212
    va_end(ap);
 
213
 
 
214
    /*
 
215
     * For convenience...
 
216
     */
 
217
    if (args.command == 0)
 
218
        args.command = args.argv[0];
 
219
 
 
220
    /*
 
221
     * Spawn off a child process and irrevocably change privilege to the
 
222
     * user. This includes revoking all rights on open files (via the close
 
223
     * on exec flag). If we cannot run the command now, try again some time
 
224
     * later.
 
225
     */
 
226
    switch (pid = fork()) {
 
227
 
 
228
        /*
 
229
         * Error. Instead of trying again right now, back off, give the
 
230
         * system a chance to recover, and try again later.
 
231
         */
 
232
    case -1:
 
233
        msg_fatal("fork: %m");
 
234
 
 
235
        /*
 
236
         * Child. Run the child in a separate process group so that the
 
237
         * parent can kill not just the child but also its offspring.
 
238
         */
 
239
    case 0:
 
240
        if (args.uid != (uid_t) - 1 || args.gid != (gid_t) - 1)
 
241
            set_ugid(args.uid, args.gid);
 
242
        setsid();
 
243
 
 
244
        /*
 
245
         * Pipe plumbing.
 
246
         */
 
247
        if ((args.stdin_fd >= 0 && DUP2(args.stdin_fd, STDIN_FILENO) < 0)
 
248
         || (args.stdout_fd >= 0 && DUP2(args.stdout_fd, STDOUT_FILENO) < 0)
 
249
        || (args.stderr_fd >= 0 && DUP2(args.stderr_fd, STDERR_FILENO) < 0))
 
250
            msg_fatal("%s: dup2: %m", myname);
 
251
 
 
252
        /*
 
253
         * Environment plumbing. Always reset the command search path. XXX
 
254
         * That should probably be done by clean_env().
 
255
         */
 
256
        if (args.export)
 
257
            clean_env(args.export);
 
258
        if (setenv("PATH", _PATH_DEFPATH, 1))
 
259
            msg_fatal("%s: setenv: %m", myname);
 
260
        if (args.env)
 
261
            for (cpp = args.env; *cpp; cpp += 2)
 
262
                if (setenv(cpp[0], cpp[1], 1))
 
263
                    msg_fatal("setenv: %m");
 
264
 
 
265
        /*
 
266
         * Process plumbing. If possible, avoid running a shell.
 
267
         */
 
268
        closelog();
 
269
        if (args.argv) {
 
270
            execvp(args.argv[0], args.argv);
 
271
            msg_fatal("%s: execvp %s: %m", myname, args.argv[0]);
 
272
        } else if (args.shell && *args.shell) {
 
273
            argv = argv_split(args.shell, " \t\r\n");
 
274
            argv_add(argv, args.command, (char *) 0);
 
275
            argv_terminate(argv);
 
276
            execvp(argv->argv[0], argv->argv);
 
277
            msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]);
 
278
        } else {
 
279
            exec_command(args.command);
 
280
        }
 
281
        /* NOTREACHED */
 
282
 
 
283
        /*
 
284
         * Parent.
 
285
         */
 
286
    default:
 
287
 
 
288
        /*
 
289
         * Be prepared for the situation that the child does not terminate.
 
290
         * Make sure that the child terminates before the parent attempts to
 
291
         * retrieve its exit status, otherwise the parent could become stuck,
 
292
         * and the mail system would eventually run out of exec daemons. Do a
 
293
         * thorough job, and kill not just the child process but also its
 
294
         * offspring.
 
295
         */
 
296
        if ((err = timed_waitpid(pid, &wait_status, 0, args.time_limit)) < 0
 
297
            && errno == ETIMEDOUT) {
 
298
            msg_warn("%s: process id %lu: command time limit exceeded",
 
299
                     args.command, (unsigned long) pid);
 
300
            kill(-pid, SIGKILL);
 
301
            err = waitpid(pid, &wait_status, 0);
 
302
        }
 
303
        if (err < 0)
 
304
            msg_fatal("wait: %m");
 
305
        return (wait_status);
 
306
    }
 
307
}