summaryrefslogtreecommitdiffstats
path: root/stap-serverd
blob: d7a575139320676a7c7c6ee0cb1111778408befe (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
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
#!/bin/bash

# Compile server manager for systemtap
#
# Copyright (C) 2008, 2009 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
. `dirname $0`/stap-env

#-----------------------------------------------------------------------------
# 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

    # Parse the arguments
    parse_options "$@"

    # 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
	# Update the certificate file if it is old.
	if test -f $ssl_db/$stap_old_certfile; then
	    if ! test -e $ssl_db/$stap_certfile; then
		mv $ssl_db/$stap_old_certfile $ssl_db/$stap_certfile
	    else
		rm -fr $ssl_db/$stap_old_certfile
	    fi
	fi
	# If no certificate/key database has been specified, then find/create
	# a local one.
	if ! test -f $ssl_db/$stap_certfile; then
	    ${stap_exec_prefix}stap-gen-cert $ssl_db >> $logfile 2>&1 || exit 1
            # Now add the server's certificate to the client's database,
	    # making it a trusted peer. 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
	    # 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` -a \
                    -x `which ${stap_exec_prefix}stap-client`; then
		${stap_exec_prefix}stap-authorize-server-cert $ssl_db/$stap_certfile >> $logfile 2>&1
	    fi
	fi
    fi

    # Check the security of the database.
    check_db $ssl_db

    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
		    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
		    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
		    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: advertise_presence
#
# Advertise the availability of the server on the network.
function advertise_presence {
    # Build up a string representing our server's properties.
    local service_name="Systemtap Compile Server for $uname_r $arch"
    local txt="sysinfo=$uname_r $arch"

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

    echo "$service_name listening on port $port" >> $logfile
}

# function: listen
#
# Listen for and handle requests to the server.
function listen {
    # The stap-server-connect program will listen forever
    # accepting requests.
    ${stap_exec_prefix}stap-server-connect \
	-p $port -n $nss_cert -d $ssl_db -w $nss_pw \
	-s "$stap_options" \
	>> $logfile 2>&1 &
    wait '%${stap_exec_prefix}stap-server-connect' >> $logfile 2>&1
}

# function: check_db DBNAME
#
# Check the security of the given database directory.
function check_db {
    local dir=$1
    local rc=0

    # Check that we have been given a directory
    if ! test -e $dir; then
	warning "Certificate database '$dir' does not exist"
	return 1
    fi
    if ! test -d $dir; then
	warning "Certificate database '$dir' is not a directory"
	return 1
    fi

    # Check that we can read the directory
    if ! test -r $dir; then
	warning "Certificate database '$dir' is not readble"
	rc=1
    fi

    # We must be the owner of the database.
    local ownerid=`stat -c "%u" $dir`
    if test "X$ownerid" != "X$EUID"; then
	warning "Certificate database '$dir' must be owned by $USER"
	rc=1
    fi

    # Check the access permissions of the directory
    local perm=0`stat -c "%a" $dir`
    if test $((($perm & 0400) == 0400)) = 0; then
	warning "Certificate database '$dir' should be readable by the owner"
    fi
    if test $((($perm & 0200) == 0200)) = 0; then
	warning "Certificate database '$dir' should be writeable by the owner"
    fi
    if test $((($perm & 0100) == 0100)) = 0; then
	warning "Certificate database '$dir' should be searchable by the owner"
    fi
    if test $((($perm & 0040) == 0040)) = 0; then
	warning "Certificate database '$dir' should be readable by the group"
    fi
    if test $((($perm & 0020) == 0020)) = 1; then
	warning "Certificate database '$dir' must not be writable by the group"
	rc=1
    fi
    if test $((($perm & 0010) == 0010)) = 0; then
	warning "Certificate database '$dir' should be searchable by the group"
    fi
    if test $((($perm & 0004) == 0004)) = 0; then
	warning "Certificate database '$dir' should be readable by others"
    fi
    if test $((($perm & 0002) == 0002)) = 1; then
	warning "Certificate database '$dir' must not be writable by others"
	rc=1
    fi
    if test $((($perm & 0001) == 0001)) = 0; then
	warning "Certificate database '$dir' should be searchable by others"
    fi

    # Now check the permissions of the critical files.
    check_db_file $dir/cert8.db || rc=1
    check_db_file $dir/key3.db || rc=1
    check_db_file $dir/secmod.db || rc=1
    check_db_file $dir/pw || rc=1
    check_cert_file $dir/$stap_certfile || rc=1

    test $rc = 1 && fatal "Unable to use certificate database '$dir' due to errors"

    return $rc
}

# function: check_db_file FILENAME
#
# Check the security of the given database file.
function check_db_file {
    local file=$1
    local rc=0

    # Check that we have been given a file
    if ! test -e $file; then
	warning "Certificate database file '$file' does not exist"
	return 1
    fi
    if ! test -f $file; then
	warning "Certificate database file '$file' is not a regular file"
	return 1
    fi

    # We must be the owner of the file.
    local ownerid=`stat -c "%u" $file`
    if test "X$ownerid" != "X$EUID"; then
	warning "Certificate database file '$file' must be owned by $USER"
	rc=1
    fi

    # Check that we can read the file
    if ! test -r $file; then
	warning "Certificate database file '$file' is not readble"
	rc=1
    fi

    # Check the access permissions of the file
    local perm=0`stat -c "%a" $file`
    if test $((($perm & 0400) == 0400)) = 0; then
	warning "Certificate database file '$file' should be readable by the owner"
    fi
    if test $((($perm & 0200) == 0200)) = 0; then
	warning "Certificate database file '$file' should be writeable by the owner"
    fi
    if test $((($perm & 0100) == 0100)) = 1; then
	warning "Certificate database file '$file' must not be executable by the owner"
	rc=1
    fi
    if test $((($perm & 0040) == 0040)) = 1; then
	warning "Certificate database file '$file' must not be readable by the group"
	rc=1
    fi
    if test $((($perm & 0020) == 0020)) = 1; then
	warning "Certificate database file '$file' must not be writable by the group"
	rc=1
    fi
    if test $((($perm & 0010) == 0010)) = 1; then
	warning "Certificate database file '$file' must not be executable by the group"
	rc=1
    fi
    if test $((($perm & 0004) == 0004)) = 1; then
	warning "Certificate database file '$file' must not be readable by others"
	rc=1
    fi
    if test $((($perm & 0002) == 0002)) = 1; then
	warning "Certificate database file '$file' must not be writable by others"
	rc=1
    fi
    if test $((($perm & 0001) == 0001)) = 1; then
	warning "Certificate database file '$file' must not be executable by others"
	rc=1
    fi

    return $rc
}

# function: check_db_file FILENAME
#
# Check the security of the given database file.
function check_cert_file {
    local file=$1
    local rc=0

    # Check that we have been given a file
    if ! test -e $file; then
	warning "Certificate database file '$file' does not exist"
	return 1
    fi
    if ! test -f $file; then
	warning "Certificate database file '$file' is not a regular file"
	return 1
    fi

    # We must be the owner of the file.
    local ownerid=`stat -c "%u" $file`
    if test "X$ownerid" != "X$EUID"; then
	warning "Certificate file '$file' must be owned by $USER"
	rc=1
    fi

    # Check the access permissions of the file
    local perm=0`stat -c "%a" $file`
    if test $((($perm & 0400) == 0400)) = 0; then
	warning "Certificate file '$file' should be readable by the owner"
    fi
    if test $((($perm & 0200) == 0200)) = 0; then
	warning "Certificate file '$file' should be writeable by the owner"
    fi
    if test $((($perm & 0100) == 0100)) = 1; then
	warning "Certificate file '$file' must not be executable by the owner"
	rc=1
    fi
    if test $((($perm & 0040) == 0040)) = 0; then
	warning "Certificate file '$file' should be readable by the group"
    fi
    if test $((($perm & 0020) == 0020)) = 1; then
	warning "Certificate file '$file' must not be writable by the group"
	rc=1
    fi
    if test $((($perm & 0010) == 0010)) = 1; then
	warning "Certificate file '$file' must not be executable by the group"
	rc=1
    fi
    if test $((($perm & 0004) == 0004)) = 0; then
	warning "Certificate file '$file' should be readable by others"
    fi
    if test $((($perm & 0002) == 0002)) = 1; then
	warning "Certificate file '$file' must not be writable by others"
	rc=1
    fi
    if test $((($perm & 0001) == 0001)) = 1; then
	warning "Certificate file '$file' must not be executable by others"
	rc=1
    fi

    return $rc
}

# 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_exec_prefix}stap-server-connect' >> $logfile 2>&1
    wait '%${stap_exec_prefix}stap-server-connect' >> $logfile 2>&1

    exit
}

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