File: main.c

package info (click to toggle)
privbind 1.2-1.1
  • links: PTS
  • area: main
  • in suites: bullseye, buster, jessie, jessie-kfreebsd, sid, stretch, wheezy
  • size: 1,584 kB
  • ctags: 89
  • sloc: sh: 11,681; ansic: 629; makefile: 75
file content (402 lines) | stat: -rw-r--r-- 12,465 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
/*
 * privbind - allow unpriviledged apps to bind to priviledged ports
 * Copyright (C) 2006-2007 Shachar Shemesh
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>

#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "config.h"
#include "ipc.h"

#define FALSE (0!=0)
#define TRUE (0==0)

struct cmdoptions {
    uid_t uid; /* UID to turn into */
    gid_t gid; /* GID to turn into */
    int numbinds; /* Number of binds to catch before program can make do on its own */
    const char *libname; /* Path to library to use as preload */
#if DEBUG_TESTING
    int wait; /* Time to artificially prolong the bind time by */
#endif
} options;

void usage( const char *progname )
{
    fprintf(stderr, "Usage: %s -u UID [-g GID] [-n NUM] command [arguments ...]\n", progname);
    fprintf(stderr, "Run '%s -h' for more information.\n", progname);
}
void help( const char *progname )
{
    printf("%s - run a program as an unpriviledged user, while still being\n",
	   PACKAGE_STRING);
    printf("     able to bind to low ports.\n");
    printf("Usage: %s -u UID [-g GID] [-n NUM] command [arguments ...]\n", progname);
    printf("\n"
	"-u - Name or id of user to run as (mandatory)\n"
	"-g - Name or id of group to run as (default: the user's default group)\n"
	"-n - number of binds to catch. After this many binds have happened,\n"
        "     the helper proccess exits.\n"
        "-l - Explicitly specify the path to the libary to use for preload\n"
        "     This option is for debug use only.\n"
#if DEBUG_TESTING
        "-w - Delay each bind by num seconds. Only useful for internal privbind\n"
        "     testing.\n"
#endif
	"-h - This help screen\n");
}

int parse_cmdline( int argc, char *argv[] )
{
    /* Fill in default values */
    options.numbinds=0;
    options.uid=0;
    options.gid=-1;
    options.libname=PKGLIBDIR "/" PRELOADLIBNAME;
    
    int opt;

    while( (opt=getopt(argc, argv, "+n:u:g:l:w:h" ))!=-1 ) {
        switch(opt) {
        case 'n':
            options.numbinds=atoi(optarg);
	    if (options.numbinds < 0) {
	        fprintf(stderr, "Illegal number of binds passed: '%s'\n",
		  optarg);
		exit(1);
	    }
            break;
        case 'u':
            {
                struct passwd *pw=getpwnam(optarg);
                if( pw!=NULL ) {
                    options.uid=pw->pw_uid;
                    /* set the user's default group */
                    if( options.gid==(gid_t)-1 ) {
                        options.gid=pw->pw_gid;
                    }
                } else {
                    options.uid=atoi(optarg);
                    if( options.uid==0 ) {
                        fprintf(stderr, "Username '%s' not found\n", optarg);
                        exit(1);
                    }
                }
            }
            break;
        case 'g':
            {
                if ( *optarg == '\0' ) {
                    fprintf(stderr, "Empty group parameters\n");
                    exit(1);
                }
                struct group *gr=getgrnam(optarg);
                if( gr!=NULL ) {
                    options.gid=gr->gr_gid;
                } else {
                    char *endptr;
                    options.gid=strtol(optarg, &endptr, 10);
                    if( *endptr != '\0') {
                        fprintf(stderr, "Group name '%s' not found\n", optarg);
                        exit(1);
                    }
                    if( options.gid==(gid_t)-1 ) {
                        fprintf(stderr, "Illegal group id %d\n", options.gid);
                        exit(1);
                    }
                }
            }
            break;
        case 'l':
            options.libname=optarg;
            break;
#if DEBUG_TESTING
        case 'w':
            options.wait=atoi(optarg);
            break;
#endif
        case 'h':
            help(argv[0]);
            exit(0);
        case '?':
            usage(argv[0]);
            exit(1);
        }
    }

    if(options.uid==0){
        fprintf(stderr, "Missing UID (-u) option.\n");
        usage(argv[0]);
        exit(1);
    }
    if(options.gid==(gid_t)-1){
        fprintf(stderr, "Missing GID (-g) option.\n");
        usage(argv[0]);
        exit(1);
    }

    if( (argc-optind)<=0 ) {
        fprintf(stderr, "ERROR: missing a command to run.\n");
        usage(argv[0]);
        exit(1);
    }
    return optind;
}

/* Technically speaking, the "child" is the parent process. Internally, we call it by its semantics
 * rather than by its function.
 */
int process_child( int sv[2], int argc, char *argv[] )
{
    /* Drop privileges */
    if( setgroups(0, NULL )<0 ) {
        perror("privbind: setgroups");
        return 2;
    }
    if( setgid(options.gid)<0 ) {
        perror("privbind: setgid");
        close(sv[0]);
        return 2;
    }
    if( setuid(options.uid)<0 ) {
        perror("privbind: setuid");
        close(sv[0]);
        return 2;
    }

    /* Close the parent socket */
    close(sv[1]);

    /* Rename the child socket to the pre-determined fd */
    if( dup2(sv[0], COMM_SOCKET)<0 ) {
        perror("privbind: dup2");
        return 2;
    }
    close(sv[0]);

    /* Set the LD_PRELOAD environment variable */
    char *ldpreload=getenv("LD_PRELOAD");
    if( ldpreload==NULL ) {
        setenv("LD_PRELOAD", options.libname, FALSE );
    } else {
        char *newpreload=malloc(strlen(ldpreload)+strlen(options.libname)+2); /* One extra for the ":", another for the NULL */
        if( newpreload==NULL ) {
            fprintf(stderr, "privbind: Error creating preload environment - out of memory\n");
            return 2;
        }

        sprintf( newpreload, "%s:%s", options.libname, ldpreload );

        setenv("LD_PRELOAD", newpreload, TRUE );

        free(newpreload);
    }

    /* Set up the variables for exec */
    char **new_argv=calloc(argc+1, sizeof(char*) );
    if( new_argv==NULL ) {
        fprintf(stderr, "privbind: Error creating new command line: out of memory\n");
        return 2;
    }

    int i;
    for( i=0; i<argc; ++i ) {
        new_argv[i]=argv[i];
    }

    execvp(new_argv[0], new_argv);
    perror("privbind: exec");
    return 2;
}

/* See comment for "process_child" regarding reverse roles */
int process_parent( int sv[2] )
{
    /* Some of the run options mean that we terminate before our "child". We don't want to confuse
     * the child with SIGCHLD of which it is not aware.
     */
    int grandchild_pid=fork();

    if( grandchild_pid==-1 ) {
        perror("privbind: Error creating grandchild process");

        return 1;
    }

    if( grandchild_pid!=0 ) {
        /* We are the grandchild's parent. Terminate cleanly to indicate to our parent that it's ok
         * to start the actual program.
         */
        return 0;
    }

    /* Close the child socket */
    close(sv[0]);

    /* wait for request from the child */
    do {
        struct msghdr msghdr={.msg_name=NULL};
        struct cmsghdr *cmsg;
        char buf[CMSG_SPACE(sizeof(int))];
        struct ipc_msg_req request;
        struct iovec iov;
        struct ipc_msg_reply reply = {.type=MSG_REP_NONE};
        int recvbytes;

        msghdr.msg_control=buf;
        msghdr.msg_controllen=sizeof(buf);

        iov.iov_base = &request;
        iov.iov_len = sizeof request;

        msghdr.msg_iov = &iov;
        msghdr.msg_iovlen = 1;

        if ( (recvbytes = recvmsg( sv[1], &msghdr, 0)) > 0) {
            if ((cmsg = (struct cmsghdr *)CMSG_FIRSTHDR(&msghdr)) != NULL) {
                switch (request.type) {
                case MSG_REQ_NONE:
                    reply.type = MSG_REP_NONE;
                    if (send(sv[1], &reply, sizeof reply, 0) != sizeof reply)
                        perror("privbind: send");
                    break;
                case MSG_REQ_BIND:
                    reply.type = MSG_REP_STAT;
                    int sock;
                    if (cmsg->cmsg_len == CMSG_LEN(sizeof(int))
                            && cmsg->cmsg_level == SOL_SOCKET
                            && cmsg->cmsg_type == SCM_RIGHTS)
                        sock = *((int*)CMSG_DATA(cmsg));
                    else {
                        sock = -1;
                    }
                    reply.data.stat.retval =
                        bind(sock, (struct sockaddr *)&request.data.bind.addr,
                                sizeof request.data.bind.addr);
                    if (reply.data.stat.retval < 0)
                        reply.data.stat.error = errno;
                    
#if DEBUG_TESTING
                    /* Sleep to check for races */
                    if( options.wait!=0 )
                        sleep(options.wait);
#endif

                    if (send(sv[1], &reply, sizeof reply, 0) != sizeof reply)
                        perror("privbind: send");
                    if (sock > -1 && close(sock))
                        perror("privbind: close");
                    break;
                default:
                    fprintf(stderr, "privbind: bad request type: %d\n",
                            request.type);
                    break;
                }
            } else {
                fprintf(stderr, "privbind: empty request\n");
            }
        } else if (recvbytes == 0) {
            /* If the child closed its end of the socket, it means the
               child has exited. We have nothing more to do. */

            return 0;
        } else {
            perror("privbind: recvmsg");
        }
    } while (options.numbinds == 0 || --options.numbinds > 0);


    /* If we got here, the child has done the number of binds
       specified by the -n option, and we have nothing more to do
       and should exit, leaving behind no helper process */

    return 0;
}

int main( int argc, char *argv[] )
{
    int skipcount=parse_cmdline( argc, argv );
    int ret=0;

    /* Warn if we're run as SUID */
    if( getuid()!=geteuid() ) {
        fprintf(stderr, "!!!!Running privbind SUID is a security risk!!!!\n");
    }

    /* Create a couple of sockets for communication with our children */
    int sv[2];
    if( socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sv)<0 ) {
        perror("privbind: socketpair");
        return 2;
    }

    pid_t child_pid=fork();
    /* http://sourceforge.net/mailarchive/message.php?msg_name=20070601194744.GA29875%40fermat.math.technion.ac.il
     * Reverse the usual role of "parent" and "child".
     * Parent process perform "child" actions - running the command.
     * Child process is the one that listens on the socket and handles the binds
     */
    switch(child_pid) {
    case -1:
        perror("privbind: fork");
        exit(1);

    case 0:
        /* We are the child */
        ret=process_parent( sv );
        break;
    default:
        /* We are the parent */

        {
            /* Wait for the child to exit. */
            int status=0;

            do {
                waitpid( child_pid, &status, 0 );
            } while( !WIFEXITED(status) && !WIFSIGNALED(status) );

            if( WIFEXITED(status) ) {
                ret=WEXITSTATUS(status);

                if( ret==0 ) {
                    /* Child has indicated that it is ready */
                    ret=process_child( sv, argc-skipcount, argv+skipcount );
                }
            } else {
                fprintf(stderr, "privbind: root process terminated with signal %d\n", WTERMSIG(status) );
                ret=2;
            }
        }
        break;
    }

    return ret;
}