summaryrefslogtreecommitdiffstats
path: root/stap-serverd
blob: b93f5e4141d58f2a8871ad299988850638f4b8a4 (plain)
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
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
#!/bin/bash

# Compile server manager for systemtap
#
# Copyright (C) 2008-2010 Red Hat Inc.
#
# This file is part of systemtap, and is free software.  You can
# redistribute it and/or modify it under the terms of the GNU General
# Public License (GPL); either version 2, or (at your option) any
# later version.

# This script publishes its presence on the network and then listens for
# incoming connections. When a connection is detected, the stap-server script
# is run to handle the request.

# Catch ctrl-c and other termination signals
trap 'terminate' SIGTERM SIGINT

# Initialize the environment
. ${PKGLIBEXECDIR}stap-env

# PR11197: security prophylactics
set_ulimits=0
if [ -z "$STAP_PR11197_OVERRIDE" ]; then
    # 1) reject use as root, except via a special environment variable
    if [ `id -u` -eq 0 ]; then
        echo "For security reasons, invocation of stap-server as root is not supported." 1>&2
        exit 1
    fi 
    # 2) resource limits should be set if the user is the 'stap-server' daemon
    test `id -un` = "stap-server" && set_ulimits=1
fi


#-----------------------------------------------------------------------------
# Helper functions.
#-----------------------------------------------------------------------------
# function: initialization PORT
function initialization {
    # Initial values
    port=
    ssl_db=
    stap_options=
    uname_r="`uname -r`"
    arch="`stap_get_arch`"
    logfile=/dev/null
    B_options=
    I_options=
    R_option=

    # Parse the arguments
    parse_options "$@"

    echo ===== compile server pid $$ started >> $logfile

    # What port will we listen on?
    test "X$port" = "X" && port=$((1024+$RANDOM%64000))
    while netstat -atn | awk '{print $4}' | cut -f2 -d: | egrep -q "^$port\$";
    do
        # Whoops, the port is busy; try another one.
	echo "$0: Port $port is busy" >> $logfile
        port=$((1024+($port + $RANDOM)%64000))
    done

    # Where is the ssl certificate/key database?
    if test "X$ssl_db" = "X"; then
	ssl_db=$stap_ssl_db/server
    fi

    nss_pw=$ssl_db/pw
    nss_cert=stap-server
}

# function: parse_options [ STAP-OPTIONS ]
#
# Examine the command line. We need not do much checking, but we do need to
# parse all options in order to discover the ones we're interested in.
function parse_options {
    while test $# != 0
    do
	advance_p=0
	dash_seen=0

        # Start of a new token.
	first_token=$1

	# Process the option.
	until test $advance_p != 0
	do
            # Identify the next option
	    first_char=`expr "$first_token" : '\(.\).*'`
	    if test $dash_seen = 0; then
		if test "$first_char" = "-"; then
		    if test "$first_token" != "-"; then
	                # It's not a lone dash, so it's an option.
			# Is it a long option (i.e. --option)?
			second_char=`expr "$first_token" : '.\(.\).*'`
			if test "X$second_char" = "X-"; then
			    case `expr "$first_token" : '--\([^=]*\)'` in
				port)
				    get_long_arg $first_token $2
				    port=$stap_arg
				    ;;
				ssl)
				    get_long_arg $first_token $2
				    ssl_db=$stap_arg
				    ;;
				log)
				    get_long_arg $first_token $2
				    logfile=$stap_arg
				    ;;
				*)
				    warning "Option '$first_token' ignored"
				    advance_p=$(($advance_p + 1))
				    break
				    ;;
			    esac
			fi
	                # It's not a lone dash, or a long option, so it's a short option string.
			# Remove the dash.
			first_token=`expr "$first_token" : '-\(.*\)'`
			dash_seen=1
			first_char=`expr "$first_token" : '\(.\).*'`
		    fi
		fi
		if test $dash_seen = 0; then
	            # The dash has not been seen. This is not an option at all.
		    warning "Option '$first_token' ignored"
		    advance_p=$(($advance_p + 1))
		    break
		fi
	    fi
	    
            # We are at the start of an option. Look at the first character.
	    case $first_char in
		a)
		    get_arg $first_token $2
		    process_a $stap_arg
		    ;;
		B)
		    get_arg $first_token $2
		    B_options="$B_options $stap_arg"
		    stap_options="$stap_options -$first_char $stap_arg"
		    ;;
		c)
		    get_arg $first_token $2
		    warning "Option '-$first_char $stap_arg' ignored"
		    ;;
		d)
		    get_arg $first_token $2
		    warning "Option '-$first_char $stap_arg' ignored"
		    ;;
		D)
		    get_arg $first_token $2
		    warning "Option '-$first_char $stap_arg' ignored"
		    ;;
		e)
		    get_arg $first_token "$2"
		    warning "Option '-$first_char '$stap_arg' ignored'"
		    ;;
		I)
		    get_arg $first_token $2
		    I_options="$I_options $stap_arg"
		    stap_options="$stap_options -$first_char $stap_arg"
		    ;;	
		l)
		    get_arg $first_token $2
		    warning "Option '-$first_char $stap_arg' ignored"
		    ;;
		L)
		    get_arg $first_token $2
		    warning "Option '-$first_char $stap_arg' ignored"
		    ;;
		m)
		    get_arg $first_token $2
		    warning "Option '-$first_char $stap_arg' ignored"
		    ;;
		o)
		    get_arg $first_token $2
		    warning "Option '-$first_char $stap_arg' ignored"
		    ;;
		p)
		    get_arg $first_token $2
		    warning "Option '-$first_char $stap_arg' ignored"
		    ;;
		r)
		    get_arg $first_token $2
		    process_r $stap_arg
		    ;;	
		R)
		    get_arg $first_token $2
		    R_option="$stap_arg"
		    stap_options="$stap_options -$first_char $stap_arg"
		    ;;	
		s)
		    get_arg $first_token $2
		    warning "Option '-$first_char $stap_arg' ignored"
		    ;;	
		S)
		    get_arg $first_token $2
		    warning "Option '-$first_char $stap_arg' ignored"
		    ;;	
		x)
		    get_arg $first_token $2
		    warning "Option '-$first_char $stap_arg' ignored"
		    ;;
		*)
		    # An unknown flag. Ignore it.
		    ;;
	    esac

	    if test $advance_p = 0; then
	        # Just another flag character. Consume it.
		warning "Option '-$first_char' ignored"
		first_token=`expr "$first_token" : '.\(.*\)'`
		if test "X$first_token" = "X"; then
		    advance_p=$(($advance_p + 1))
		fi
	    fi
	done

        # Consume the arguments we just processed.
	while test $advance_p != 0
	do
	    shift
	    advance_p=$(($advance_p - 1))
	done
    done
}

# function: get_arg FIRSTWORD SECONDWORD
#
# Collect an argument to the given short option
function get_arg {
    # Remove first character. Advance to the next token, if the first one
    # is exhausted.
    local first=`expr "$1" : '.\(.*\)'`
    if test "X$first" = "X"; then
	shift
	advance_p=$(($advance_p + 1))
	first=$1
    fi
    stap_arg="$first"
    advance_p=$(($advance_p + 1))
}

# function: get_arg FIRSTWORD SECONDWORD
#
# Collect an argument to the given long option
function get_long_arg {
    # Remove first character. Advance to the next token, if the first one
    # is exhausted.
    local first=`expr "$1" : '.*\=\(.*\)'`
    if test "X$first" = "X"; then
	shift
	advance_p=$(($advance_p + 1))
	first=$1
    fi
    stap_arg="$first"
    advance_p=$(($advance_p + 1))
}

# function: process_a ARGUMENT
#
# Process the -a flag.
function process_a {
    if test "X$1" != "X$arch"; then
	arch=$1
	stap_options="$stap_options -a $1"
    fi
}

# function: process_r ARGUMENT
#
# Process the -r flag.
function process_r {
    local first_char=`expr "$1" : '\(.\).*'`

    if test "$first_char" = "/"; then # fully specified path
        kernel_build_tree=$1
        version_file_name="$kernel_build_tree/include/config/kernel.release"
        # The file include/config/kernel.release within the kernel
        # build tree is used to pull out the version information
	release=`cat $version_file_name 2>/dev/null`
	if test "X$release" = "X"; then
	    fatal "Missing $version_file_name"
	    return
	fi
    else
	# kernel release specified directly
	release=$1
    fi

    if test "X$release" != "X$uname_r"; then
	uname_r=$release
	stap_options="$stap_options -r $release"
    fi
}

# function: check_cert
#
# Ensure that our certificate exists and is valid.
# Generate a new one if not.
function check_cert {
    # If our certificate exists, log some information about it.
    if test -f $ssl_db/cert8.db; then
	echo "Certificate found in database $ssl_db" >> $logfile
	certutil -L -d "$ssl_db" -n $nss_cert | \
	    awk '/Validity|Not After|Not Before/ { print $0 }' | \
	    sed 's/^ */  /' >> $logfile
    fi

    # If the certificate does not exist or the certificate
    # is not valid, then generate a new one.
    if test ! -d $ssl_db -o ! -f $ssl_db/$stap_certfile -o ! -f $ssl_db/cert8.db || \
       ! certutil -V -n $nss_cert -u V -d $ssl_db -e -f $nss_pw \
                     -b `date +%g%m%d%H%M%S`+0005 >> $logfile 2>&1; then
	# Our certificate does not exist or is not valid.
	# Generate a new certificate database.
	${stap_pkglibexecdir}stap-gen-cert $ssl_db >> $logfile 2>&1 || exit 1

        # Now add the new certificate to the client's database,
        # making it a trusted peer for this user.
	# Do this only if the client has been installed.
	if test -f `which ${stap_exec_prefix}stap-client 2>/dev/null` -a \
	        -x `which ${stap_exec_prefix}stap-client 2>/dev/null`; then
	    ${stap_exec_prefix}stap-authorize-server-cert $ssl_db/$stap_certfile >> $logfile 2>&1
	fi
    elif ! test -f $stap_ssl_db/client/cert8.db; then
	# Our certificate exists and is valid.
	# If the client's database does not exist, then initialize it with our certificate.
	# Do this only if the client has been installed.
	if test -f `which ${stap_exec_prefix}stap-client 2>/dev/null` -a \
	    -x `which ${stap_exec_prefix}stap-client 2>/dev/null`; then
	    ${stap_exec_prefix}stap-authorize-server-cert $ssl_db/$stap_certfile >> $logfile 2>&1
	fi
    fi
}

# function: advertise_presence
#
# Advertise the availability of the server on the network.
function advertise_presence {
    # Build up a strings representing our server's properties.
    # The service name must differ for each server, so put the port number
    # in it.
    service_name="Systemtap Compile Server on port $port"

    local sysinfo="sysinfo=$uname_r $arch"
    local optinfo="optinfo="
    test -n "$R_option" && optinfo="${optinfo}-R '$R_option'"
    for opt in $B_options; do
	optinfo="$optinfo -B '$opt'"
    done
    for opt in $I_options; do
	optinfo="$optinfo -I '$opt'"
    done
    optinfo=`echo $optinfo | sed 's/optinfo= /optinfo=/'`

    # Call avahi-publish-service to advertise our presence.
    avahi-publish-service "$service_name" \
	$stap_avahi_service_tag $port "$sysinfo" "$optinfo" >> $logfile 2>&1 &
}

# function: listen
#
# Listen for and handle requests to the server.
function listen {
    # The stap-server-connect program will listen forever
    # accepting requests.
    # CVE-2009-4273 ... or at least, until resource limits fire
    while true; do # NB: loop to avoid DoS by deliberate rlimit-induced halt
	# Ensure that our certificate is valid. Generate a new one if
	# not.
	check_cert

        # NB: impose resource limits in case of mischevious data inducing
        # too much / long computation
        (test $set_ulimits = 1 && ulimit -f 50000 -s 1000 -t 60 -u 20 -v 500000;
         exec ${stap_pkglibexecdir}stap-server-connect \
	   -p $port -n $nss_cert -d $ssl_db -w $nss_pw \
	   -s "$stap_options") &
        stap_server_connect_pid=$!
	echo "$service_name ready"
        wait $stap_server_connect_pid

        # NB: avoid superfast spinning in case of a ulimit or other failure
        sleep 1
    done >> $logfile 2>&1
}

# function: warning [ MESSAGE ]
#
# Warning error
# Prints its arguments to stderr
function warning {
    echo "$0: WARNING:" "$@" >> $logfile
}

# function: fatal [ MESSAGE ]
#
# Fatal error
# Prints its arguments to stderr and exits
function fatal {
    echo "$0: ERROR:" "$@" >> $logfile
    terminate
    exit 1
}

# function: terminate
#
# Terminate gracefully.
function terminate {
    echo "$0: Exiting" >> $logfile

    # Kill the running 'avahi-publish-service' job
    kill -s SIGTERM '%avahi-publish-service' >> $logfile 2>&1
    wait '%avahi-publish-service' >> $logfile 2>&1

    # Kill any running 'stap-server-connect' job.
    kill -s SIGTERM $stap_server_connect_pid >> $logfile 2>&1
    wait $stap_server_connect_pid >> $logfile 2>&1

    exit
}

#-----------------------------------------------------------------------------
# Beginning of main line execution.
#-----------------------------------------------------------------------------
initialization "$@"
advertise_presence
listen