File: tftp.sh

package info (click to toggle)
inetutils 2%3A2.7-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 18,588 kB
  • sloc: ansic: 132,363; sh: 12,498; yacc: 1,651; makefile: 725; perl: 72
file content (543 lines) | stat: -rwxr-xr-x 14,873 bytes parent folder | download
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
#!/bin/sh

# Copyright (C) 2010-2025 Free Software Foundation, Inc.
#
# This file is part of GNU Inetutils.
#
# GNU Inetutils 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 3 of the License, or (at
# your option) any later version.
#
# GNU Inetutils 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, see `http://www.gnu.org/licenses/'.

# Run `inetd' with `tftpd' and try to fetch a file from there using `tftp'.

# Prerequisites:
#
#  * Shell: SVR4 Bourne shell, or newer.
#
#  * dd(1), id(1), kill(1), mktemp(1), netstat(8), uname(1).
#
#  * Accessed by launched Inetd:
#      /etc/nsswitch.conf, /etc/passwd, /etc/protocols.
#
#    OpenBSD uses /etc/services directly, not via /etc/nsswitch.conf.

#
# Currently implemented tests (10 or 12 in total):
#
#  * Read three files in binary mode, from 127.0.0.1 and ::1,
#    needing one, two, and multiple data packets, respectively.
#
#  * Read one moderate size ascii file from 127.0.0.1 and ::1.
#
#  * Reload configuration and read a small binary file twice.
#
#  * (root only) Reload configuration for chrooted mode.
#    Read one binary file with a relative name, and one ascii
#    file with absolute location.
#
# The values of TARGET and TARGET6 replace the loopback addresses
# 127.0.0.1 and ::1, whenever the variables are set.  However,
# setting the variable ADDRESSES to a list of addresses takes
# precedence over all other choices.  The particular value "sense"
# tries to find all local addresses, then go ahead with these.
#
# Whenever set, VERBOSE and LOGGING, this test is performed in verbose
# mode, and lets `tftpd' do system logging, respectively.

. ./tools.sh

$need_dd || exit_no_dd
$need_mktemp || exit_no_mktemp
$need_netstat || exit_no_netstat

if test -z "${VERBOSE+set}"; then
    silence=:
    bucket='>/dev/null'
fi

if test -n "$VERBOSE"; then
    set -x
fi

# Portability fix for SVR4
PWD="${PWD:-`pwd`}"

TFTP="${TFTP:-../src/tftp$EXEEXT}"
TFTPD="${TFTPD:-$PWD/../src/tftpd$EXEEXT}"
INETD="${INETD:-../src/inetd$EXEEXT}"
IFCONFIG="${IFCONFIG:-../ifconfig/ifconfig$EXEEXT --format=unix}"
IFCONFIG_SIMPLE=`expr X"$IFCONFIG" : X'\([^ ]*\)'`	# Remove options

if [ ! -x $TFTP ]; then
    echo "No TFTP client '$TFTP' present.  Skipping test" >&2
    exit 77
elif [ ! -x $TFTPD ]; then
    echo "No TFTP server '$TFTPD' present.  Skipping test" >&2
    exit 77
elif [ ! -x $INETD ]; then
    echo "No inetd superserver '$INETD' present.  Skipping test" >&2
    exit 77
elif [ ! -x $IFCONFIG_SIMPLE ]; then	# Remove options
    echo "No ifconfig '$IFCONFIG_SIMPLE' present.  Skipping test" >&2
    exit 77
fi

# The superserver Inetd puts constraints on any chroot
# when running this script, since it needs to look up
# some basic facts stated in the configuration file.
NSSWITCH=/etc/nsswitch.conf
PASSWD=/etc/passwd
PROTOCOLS=/etc/protocols

# Overrides based on systems.
test `uname -s` = OpenBSD && NSSWITCH=/etc/services

if test ! -r $NSSWITCH || test ! -r $PASSWD \
      || test ! -r $PROTOCOLS; then
    cat <<-EOT >&2
	The use of the superserver Inetd in this script requires
	the availability of "$NSSWITCH", "$PASSWD", and
	"$PROTOCOLS".  At least one of these is now missing.
	Therefore skipping test.
	EOT
    exit 77
fi

AF=${AF:-inet}
PROTO=${PROTO:-udp}
USER=`func_id_user`

# Late supplementary subtest.
do_conf_reload=true
do_secure_setting=true

# Disable chrooted mode for non-root invocation.
test `func_id_uid` -eq 0 || do_secure_setting=false

# Random base directory at testing time.
TMPDIR=`$MKTEMP -d $PWD/tmp.XXXXXXXXXX` ||
    {
	echo 'Failed at creating test directory.  Aborting.' >&2
	exit 1
    }

INETD_CONF="$TMPDIR/inetd.conf.tmp"
INETD_PID="$TMPDIR/inetd.pid.$$"

posttesting () {
    if test -n "$TMPDIR" && test -f "$INETD_PID" \
	&& test -r "$INETD_PID" \
	&& kill -0 "`cat $INETD_PID`" >/dev/null 2>&1
    then
	kill "`cat $INETD_PID`" 2>/dev/null ||
	kill -9 "`cat $INETD_PID`" 2>/dev/null
    fi
    test -n "$TMPDIR" && test -d "$TMPDIR" \
	&& rm -rf "$TMPDIR" $FILELIST
}

trap posttesting EXIT HUP INT QUIT TERM

# Use only "127.0.0.1 ::1" as default address list,
# but take account of TARGET and TARGET6.
# Other configured addresses might be set under
# strict filter policies, thus might block.
#
# Allow a setting "ADDRESSES=sense" to compute the
# available addresses and then to test them all.
if test "$ADDRESSES" = "sense"; then
    ADDRESSES=`$IFCONFIG -a | $SED -e "/$AF /!d" \
	-e "s/^.*$AF \([:.0-9]\{1,\}\) .*$/\1/g"`
fi

if test -z "$ADDRESSES"; then
    test "$TEST_IPV4" = "no" || ADDRESSES="${TARGET:-127.0.0.1}"
    test "$TEST_IPV6" = "no" ||
	ADDRESSES="${ADDRESSES:+$ADDRESSES }${TARGET6:-::1}"
fi

# Work around the peculiar output of netstat(1m,solaris).
#
# locate_port proto port
#
locate_port () {
    if [ "`uname -s`" = "SunOS" ]; then
	$NETSTAT -na -finet -finet6 -P$1 |
	$GREP "\.$2[^0-9]" >/dev/null 2>&1
    else
	$NETSTAT -na |
	$GREP "^$1[46]\{0,2\}.*[^0-9]$2[^0-9]" >/dev/null 2>&1
    fi
}

if [ "$VERBOSE" ]; then
    "$TFTP" --version | $SED '1q'
    "$TFTPD" --version | $SED '1q'
    "$INETD" --version | $SED '1q'
    "$IFCONFIG_SIMPLE" --version | $SED '1q'
fi

# Find an available port number.  There will be some
# room left for a race condition, but we try to be
# flexible enough for running copies of this script.
#
if test -z "$PORT"; then
    for PORT in 7777 7779 7783 7791 7807 7839 none; do
	test $PORT = none && break
	if locate_port $PROTO $PORT; then
	    continue
	else
	    break
	fi
    done
    if test "$PORT" = 'none'; then
	echo 'Our port allocation failed.  Skipping test.' >&2
	exit 77
    fi
fi

# Create `inetd.conf'.  Note: We want $TFTPD to be an absolute file
# name because `inetd' chdirs to `/' in daemon mode; ditto for
# $INETD_CONF.  Thus the dependency on file locations will be
# identical in daemon-mode and in debug-mode.
write_conf () {
    : > "$INETD_CONF" 2>/dev/null

    test "$TEST_IPV4" = "no" ||
	cat >> "$INETD_CONF" <<-EOF
	$PORT dgram ${PROTO}4 wait $USER $TFTPD   tftpd ${LOGGING+"-l"} $TMPDIR/tftp-test
	EOF

    test "$TEST_IPV6" = "no" ||
	cat >> "$INETD_CONF" <<-EOF
	$PORT dgram ${PROTO}6 wait $USER $TFTPD   tftpd ${LOGGING+"-l"} $TMPDIR/tftp-test
	EOF
}

if test "$TEST_IPV4" = "no" && test "$TEST_IPV6" = "no"
then
    echo >&2 "Inet socket test is switched off.  Skipping test."
    exit 77
fi

write_conf ||
    {
	echo 'Could not create configuration file for Inetd.  Aborting.' >&2
	exit 1
    }

# Launch `inetd', assuming it's reachable at all $ADDRESSES.
# Must use '-d' consistently to prevent daemonizing, but we
# would like to suppress the verbose output.  The variable
# REDIRECT is set to '2>/dev/null' in non-verbose mode.
#
test -n "$VERBOSE" || REDIRECT='2>/dev/null'

eval "$INETD -d -p'$INETD_PID' '$INETD_CONF' $REDIRECT &"

# Debug mode allows the shell to recover PID of Inetd.
spawned_pid=$!

sleep 2

inetd_pid="`cat $INETD_PID 2>/dev/null`" ||
    {
	cat <<-EOT >&2
		Inetd did not create a PID-file.  Aborting test,
		but losing control whether an Inetd process is
		still around.
	EOT
	exit 1
    }

test -z "$VERBOSE" || echo "Launched Inetd as process $inetd_pid." >&2

# Wait somewhat for the service to settle.
sleep 1

# Did `inetd' really succeed in establishing a listener?
locate_port $PROTO $PORT
if test $? -ne 0; then
    # No it did not.
    kill -0 "$inetd_pid" >/dev/null 2>&1 && kill -9 "$inetd_pid" 2>/dev/null
    rm -f "$INETD_PID"

    echo 'First attempt at starting Inetd has failed.' >&2
    echo 'A new attempt will follow after some delay.' >&2
    echo 'Increasing verbosity for better backtrace.' >&2
    sleep 7

    # Select a new port, with offset and some randomness.
    PORT=`expr $PORT + 137 + \( ${RANDOM:-$$} % 517 \)`
    write_conf ||
	{
	    echo 'Could not create configuration file for Inetd.  Aborting.' >&2
	    exit 1
	}

    $INETD -d -p"$INETD_PID" "$INETD_CONF" &
    spawned_pid=$!
    sleep 2
    inetd_pid="`cat $INETD_PID 2>/dev/null`" ||
	{
	    cat <<-EOT >&2
		Inetd did not create a PID-file.  Aborting test,
		but losing control whether an Inetd process is
		still around.
		EOT
	    kill -0 "$spawned_pid" >/dev/null 2>&1 && kill -9 "$spawned_pid" 2>/dev/null
	    exit 1
	}

    echo "Launched Inetd as process $inetd_pid." >&2

    if locate_port $PROTO $PORT; then
	: # Successful this time.
    else
	echo "Failed again at starting correct Inetd instance." >&2
	kill -0 "$spawned_pid" >/dev/null 2>&1 && kill -9 "$spawned_pid" 2>/dev/null
	exit 1
    fi
fi

if [ -r /dev/urandom ]; then
    input="/dev/urandom"
else
    input="/dev/zero"
fi

test -d "$TMPDIR" && rm -fr "$TMPDIR/tftp-test" tftp-test-file*
test -d "$TMPDIR" && mkdir -p "$TMPDIR/tftp-test" \
    || {
	echo 'Failed at creating directory for master files.  Aborting.' >&2
	exit 1
    }

# It is important to test data of differing sizes.
# These are binary files.
#
# Input format:
#
#  name  block-size  count

FILEDATA="file-small 320 1
file-medium 320 2
tftp-test-file 1024 170"

echo "$FILEDATA" |
while read name bsize count; do
    test -z "$name" && continue
    $DD if="$input" of="$TMPDIR/tftp-test/$name" \
	bs=$bsize count=$count 2>/dev/null
done

FILELIST="`echo "$FILEDATA" | $SED 's/ .*//' | tr "\n" ' '`"

# Add a file known to be ASCII encoded.
#
ASCIIFILE=asciifile.txt
if test -r tools.sh; then
    cp tools.sh "$TMPDIR/tftp-test/$ASCIIFILE"
    FILELIST="$FILELIST $ASCIIFILE"
fi

SUCCESSES=0
EFFORTS=0
RESULT=0

$silence echo "Looking into '`echo $ADDRESSES | tr "\n" ' '`'."

for addr in $ADDRESSES; do
    $silence echo "trying address '$addr'..." >&2

    for name in $FILELIST missing-file; do
	test -n "$name" || continue
	EFFORTS=`expr $EFFORTS + 1`
	rm -f "$name"
	test "$name" = $ASCIIFILE && type=ascii || type=binary
	echo "$type
get $name" | \
	eval "$TFTP" ${VERBOSE:+-v} "$addr" $PORT $bucket

	if test "$name" != "missing-file"; then
	   cmp "$TMPDIR/tftp-test/$name" "$name" 2>/dev/null
	   result=$?
	else
	   # No data should have arrived, but traditionally an empty
	   # file was created.
	   result=0
	   test ! -s "$name" || result=1
	fi

	if [ "$result" -ne 0 ]; then
	    # Failure.
	    test -z "$VERBOSE" || echo "Failed comparison for $addr/$name." >&2
	    RESULT=$result
	else
	    SUCCESSES=`expr $SUCCESSES + 1`
	    test -z "$VERBOSE" || echo "Successful comparison for $addr/$name." >&2
	fi
    done

    # Do a compound test with multiple requests.
    # Issue one request for locally renamed file.
    rm -f file-small _file-small_ missing-file
    EFFORTS=`expr $EFFORTS + 1`

    cat <<-EOT |
	binary
	get file-small
	get missing-file
	get file-small _file-small_
	quit
	EOT
    eval $TFTP ${VERBOSE:+-v} "$addr" $PORT $bucket

    if cmp "$TMPDIR/tftp-test/file-small" file-small 2>/dev/null \
	&& test ! -s missing-file \
	&& cmp "$TMPDIR/tftp-test/file-small" _file-small_ 2>/dev/null
    then
	SUCCESSES=`expr $SUCCESSES + 1`
	test -z "$VERBOSE" || echo "Successful compound test." >&2
    else
	echo "Failure during compound test." >&2

	# Investigate probable causes.
	test -s _file-small_ ||
	    echo "Third get request failed after file known to be missing." >&2
	{ test ! -f missing-file || test -s missing-file ; } &&
	    echo "The missing file did not appear as empty." >&1
	test -s file-small ||
	    echo "Not even the first request succeeded." >&2
	RESULT=1
    fi

    rm -f file-small _file-small_ missing-file
done

# Test the ability of inetd to reload configuration:
#
# Assign a new port in the configuration file. Send SIGHUP
# to inetd and check whether transmission of the small
# file used previously still succeeds.
#
PORT=`expr $PORT + 1 + ${RANDOM:-$$} % 521`

locate_port $PROTO $PORT &&
    {
	# Try a second port.
	PORT=`expr $PORT + 97 + ${RANDOM:-$$} % 479`
	# Disable subtest if still no free port.
	locate_port $PROTO $PORT && do_conf_reload=false
    }

$silence echo >&2

if $do_conf_reload; then
    $silence echo >&2 'Testing altered and reloaded configuration.'
    write_conf ||
	{
	    echo >&2 'Could not rewrite configuration file for Inetd.  Failing.'
	    exit 1
	}

    kill -HUP $inetd_pid
    name=`echo "$FILELIST" | $SED 's/ .*//'`
    for addr in $ADDRESSES; do
	EFFORTS=`expr $EFFORTS + 1`
	test -f "$name" && rm "$name"
	echo "binary
get $name" | \
	eval "$TFTP" ${VERBOSE:+-v} "$addr" $PORT $bucket
	cmp "$TMPDIR/tftp-test/$name" "$name" 2>/dev/null
	result=$?
	if test $result -ne 0; then
	    test -z "$VERBOSE" || echo >&2 "Failed comparison for $addr/$name."
	    RESULT=$result
	else
	    SUCCESSES=`expr $SUCCESSES + 1`
	    test -z "$VERBOSE" || echo >&2 "Success at new port for $addr/$name."
	fi
    done
else
    $silence echo >&2 'Informational: Inhibiting config reload test.'
fi

if $do_secure_setting; then
    # Allow an underprivileged process owner to read files.
    chmod g=rx,o=rx $TMPDIR

    : > "$INETD_CONF"

    test "$TEST_IPV4" = "no" ||
	cat >> "$INETD_CONF" <<-EOF
	$PORT dgram ${PROTO}4 wait $USER $TFTPD   tftpd ${LOGGING+"-l"} -s $TMPDIR /tftp-test
	EOF

    test "$TEST_IPV6" = "no" ||
	cat >> "$INETD_CONF" <<-EOF
	$PORT dgram ${PROTO}6 wait $USER $TFTPD   tftpd ${LOGGING+"-l"} -s $TMPDIR /tftp-test
	EOF

    # Let inetd reload configuration.
    kill -HUP $inetd_pid

    # Test two files for each address: file-small and asciifile.txt
    #
    name=`echo $FILELIST | $SED 's/ .*//'`

    for addr in $ADDRESSES; do
	rm -f "$name" "$ASCIIFILE"
	EFFORTS=`expr $EFFORTS + 2`

	cat <<-EOT |
		binary
		get $name
		ascii
		get /tftp-test/$ASCIIFILE
	EOT
	eval "$TFTP" ${VERBOSE:+-v} "$addr" $PORT $bucket

	cmp "$TMPDIR/tftp-test/$name" "$name" 2>/dev/null
	result=$?

	if test $? -ne 0; then
	    $silence echo >&2 "Failed chrooted access to $addr:$name."
	    RESULT=$result
	else
	    $silence echo >&2 "Success with chrooted access to $addr:$name."
	    SUCCESSES=`expr $SUCCESSES + 1`
	fi

	cmp "$TMPDIR/tftp-test/$ASCIIFILE" "$ASCIIFILE" 2>/dev/null
	result=$?

	if test $? -ne 0; then
	    $silence echo >&2 \
		"Failed chrooted access to $addr:/tftp-test/$ASCIIFILE."
	    RESULT=$result
	else
	    $silence echo >&2 \
	    "Success with chrooted $addr:/tftp-test/$ASCIIFILE."
	    SUCCESSES=`expr $SUCCESSES + 1`
	fi
    done # addr in ADDRESSES
else
    $silence echo >&2 'Informational: Inhibiting chroot test.'
fi

# Minimal clean up. Main work in posttesting().
$silence echo
test $RESULT -eq 0 && test $SUCCESSES -eq $EFFORTS && $silence false \
    || echo Test had $SUCCESSES successes out of $EFFORTS cases.

exit $RESULT