1# 2# Copyright (c) 2023 Klara, Inc. 3# 4# SPDX-License-Identifier: BSD-2-Clause 5# 6 7tftp_dir="${TMPDIR:-/tmp}/tftp.dir" 8inetd_conf="${TMPDIR:-/tmp}/inetd.conf" 9inetd_pid="${TMPDIR:-/tmp}/inetd.pid" 10 11start_tftpd() { 12 if ! [ -z "$(sockstat -PUDP -p69 -q)" ] ; then 13 atf_skip "the tftp port is in use" 14 fi 15 echo "starting inetd for $(atf_get ident)" >&2 16 rm -rf "${tftp_dir}" 17 mkdir "${tftp_dir}" 18 cat >"${inetd_conf}" <<EOF 19tftp dgram udp wait root /usr/libexec/tftpd tftpd -d15 -l ${tftp_dir} 20tftp dgram udp6 wait root /usr/libexec/tftpd tftpd -d15 -l ${tftp_dir} 21EOF 22 /usr/sbin/inetd -a localhost -p "${inetd_pid}" "${inetd_conf}" 23} 24 25stop_tftpd() { 26 echo "stopping inetd for $(atf_get ident)" >&2 27 # Send SIGTERM to inetd, then SIGKILL until it's gone 28 local sig=TERM 29 while pkill -$sig -LF "${inetd_pid}" inetd ; do 30 echo "waiting for inetd to stop" >&2 31 sleep 1 32 sig=KILL 33 done 34 rm -rf "${tftp_dir}" "${inetd_conf}" "${inetd_pid}" 35} 36 37atf_test_case tftp_get_big cleanup 38tftp_get_big_head() { 39 atf_set "descr" "get command with big file" 40 atf_set "require.user" "root" 41} 42tftp_get_big_body() { 43 start_tftpd 44 local remote_file="${tftp_dir}/remote.bin" 45 dd if=/dev/urandom of="${remote_file}" bs=1m count=16 status=none 46 local local_file="local.bin" 47 echo "get ${remote_file##*/} ${local_file}" >client-script 48 atf_check -o match:"Received [0-9]+ bytes" \ 49 tftp localhost <client-script 50 atf_check cmp -s "${local_file}" "${remote_file}" 51} 52tftp_get_big_cleanup() { 53 stop_tftpd 54} 55 56atf_test_case tftp_get_host cleanup 57tftp_get_host_head() { 58 atf_set "descr" "get command with host name" 59 atf_set "require.user" "root" 60} 61tftp_get_host_body() { 62 start_tftpd 63 local remote_file="${tftp_dir}/hello.txt" 64 echo "Hello, $$!" >"${remote_file}" 65 local local_file="${remote_file##*/}" 66 echo "get localhost:${remote_file##*/}" >client-script 67 atf_check -o match:"Received [0-9]+ bytes" \ 68 tftp <client-script 69 atf_check cmp -s "${local_file}" "${remote_file}" 70} 71tftp_get_host_cleanup() { 72 stop_tftpd 73} 74 75atf_test_case tftp_get_ipv4 cleanup 76tftp_get_ipv4_head() { 77 atf_set "descr" "get command with ipv4 address" 78 atf_set "require.user" "root" 79} 80tftp_get_ipv4_body() { 81 start_tftpd 82 local remote_file="${tftp_dir}/hello.txt" 83 echo "Hello, $$!" >"${remote_file}" 84 local local_file="${remote_file##*/}" 85 echo "get 127.0.0.1:${remote_file##*/}" >client-script 86 atf_check -o match:"Received [0-9]+ bytes" \ 87 tftp <client-script 88 atf_check cmp -s "${local_file}" "${remote_file}" 89} 90tftp_get_ipv4_cleanup() { 91 stop_tftpd 92} 93 94atf_test_case tftp_get_ipv6 cleanup 95tftp_get_ipv6_head() { 96 atf_set "descr" "get command with ipv6 address" 97 atf_set "require.user" "root" 98} 99tftp_get_ipv6_body() { 100 sysctl -q kern.features.inet6 || atf_skip "This test requires IPv6 support" 101 start_tftpd 102 local remote_file="${tftp_dir}/hello.txt" 103 echo "Hello, $$!" >"${remote_file}" 104 local local_file="${remote_file##*/}" 105 echo "get [::1]:${remote_file##*/}" >client-script 106 atf_check -o match:"Received [0-9]+ bytes" \ 107 tftp <client-script 108 atf_check cmp -s "${local_file}" "${remote_file}" 109} 110tftp_get_ipv6_cleanup() { 111 stop_tftpd 112} 113 114atf_test_case tftp_get_one cleanup 115tftp_get_one_head() { 116 atf_set "descr" "get command with one argument" 117 atf_set "require.user" "root" 118} 119tftp_get_one_body() { 120 start_tftpd 121 local remote_file="${tftp_dir}/hello.txt" 122 echo "Hello, $$!" >"${remote_file}" 123 local local_file="${remote_file##*/}" 124 echo "get ${remote_file##*/}" >client-script 125 atf_check -o match:"Received [0-9]+ bytes" \ 126 tftp localhost <client-script 127 atf_check cmp -s "${local_file}" "${remote_file}" 128} 129tftp_get_one_cleanup() { 130 stop_tftpd 131} 132 133atf_test_case tftp_get_two cleanup 134tftp_get_two_head() { 135 atf_set "descr" "get command with two arguments" 136 atf_set "require.user" "root" 137} 138tftp_get_two_body() { 139 start_tftpd 140 local remote_file="${tftp_dir}/hello.txt" 141 echo "Hello, $$!" >"${remote_file}" 142 local local_file="world.txt" 143 echo "get ${remote_file##*/} ${local_file}" >client-script 144 atf_check -o match:"Received [0-9]+ bytes" \ 145 tftp localhost <client-script 146 atf_check cmp -s "${local_file}" "${remote_file}" 147} 148tftp_get_two_cleanup() { 149 stop_tftpd 150} 151 152atf_test_case tftp_get_more cleanup 153tftp_get_more_head() { 154 atf_set "descr" "get command with three or more arguments" 155 atf_set "require.user" "root" 156} 157tftp_get_more_body() { 158 start_tftpd 159 for n in 3 4 5 6 7 ; do 160 echo -n "get" >client-script 161 for f in $(jot -c $n 97) ; do 162 echo "test file $$/$f/$n" >"${tftp_dir}/${f}.txt" 163 echo -n " ${f}.txt" >>client-script 164 rm -f "${f}.txt" 165 done 166 echo >>client-script 167 atf_check -o match:"Received [0-9]+ bytes" \ 168 tftp localhost <client-script 169 for f in $(jot -c $n 97) ; do 170 atf_check cmp -s "${f}.txt" "${tftp_dir}/${f}.txt" 171 done 172 done 173} 174tftp_get_more_cleanup() { 175 stop_tftpd 176} 177 178atf_test_case tftp_get_multi_host cleanup 179tftp_get_multi_host_head() { 180 atf_set "descr" "get command with multiple files and host name" 181 atf_set "require.user" "root" 182} 183tftp_get_multi_host_body() { 184 start_tftpd 185 for f in a b c ; do 186 echo "test file $$/$f/$n" >"${tftp_dir}/${f}.txt" 187 rm -f "${f}.txt" 188 done 189 echo "get localhost:a.txt b.txt c.txt" >client-script 190 atf_check -o match:"Received [0-9]+ bytes" \ 191 tftp localhost <client-script 192 for f in a b c ; do 193 atf_check cmp -s "${f}.txt" "${tftp_dir}/${f}.txt" 194 done 195} 196tftp_get_multi_host_cleanup() { 197 stop_tftpd 198} 199 200atf_test_case tftp_put_big cleanup 201tftp_put_big_head() { 202 atf_set "descr" "put command with big file" 203 atf_set "require.user" "root" 204} 205tftp_put_big_body() { 206 start_tftpd 207 local local_file="local.bin" 208 dd if=/dev/urandom of="${local_file}" bs=1m count=16 status=none 209 local remote_file="${tftp_dir}/random.bin" 210 truncate -s 0 "${remote_file}" 211 chown nobody:nogroup "${remote_file}" 212 chmod 0666 "${remote_file}" 213 echo "put ${local_file} ${remote_file##*/}" >client-script 214 atf_check -o match:"Sent [0-9]+ bytes" \ 215 tftp localhost <client-script 216 atf_check cmp -s "${remote_file}" "${local_file}" 217} 218tftp_put_big_cleanup() { 219 stop_tftpd 220} 221 222atf_test_case tftp_put_host cleanup 223tftp_put_host_head() { 224 atf_set "descr" "put command with host name" 225 atf_set "require.user" "root" 226} 227tftp_put_host_body() { 228 start_tftpd 229 local local_file="local.txt" 230 echo "test file $$" >"${local_file}" 231 local remote_file="${tftp_dir}/remote.txt" 232 truncate -s 0 "${remote_file}" 233 chown nobody:nogroup "${remote_file}" 234 chmod 0666 "${remote_file}" 235 echo "put ${local_file} localhost:${remote_file##*/}" >client-script 236 atf_check -o match:"Sent [0-9]+ bytes" \ 237 tftp <client-script 238 atf_check cmp -s "${remote_file}" "${local_file}" 239} 240tftp_put_host_cleanup() { 241 stop_tftpd 242} 243 244atf_test_case tftp_put_ipv4 cleanup 245tftp_put_ipv4_head() { 246 atf_set "descr" "put command with ipv4 address" 247 atf_set "require.user" "root" 248} 249tftp_put_ipv4_body() { 250 start_tftpd 251 local local_file="local.txt" 252 echo "test file $$" >"${local_file}" 253 local remote_file="${tftp_dir}/remote.txt" 254 truncate -s 0 "${remote_file}" 255 chown nobody:nogroup "${remote_file}" 256 chmod 0666 "${remote_file}" 257 echo "put ${local_file} 127.0.0.1:${remote_file##*/}" >client-script 258 atf_check -o match:"Sent [0-9]+ bytes" \ 259 tftp <client-script 260 atf_check cmp -s "${remote_file}" "${local_file}" 261} 262tftp_put_ipv4_cleanup() { 263 stop_tftpd 264} 265 266atf_test_case tftp_put_ipv6 cleanup 267tftp_put_ipv6_head() { 268 atf_set "descr" "put command with ipv6 address" 269 atf_set "require.user" "root" 270} 271tftp_put_ipv6_body() { 272 sysctl -q kern.features.inet6 || atf_skip "This test requires IPv6 support" 273 start_tftpd 274 local local_file="local.txt" 275 echo "test file $$" >"${local_file}" 276 local remote_file="${tftp_dir}/remote.txt" 277 truncate -s 0 "${remote_file}" 278 chown nobody:nogroup "${remote_file}" 279 chmod 0666 "${remote_file}" 280 echo "put ${local_file} [::1]:${remote_file##*/}" >client-script 281 atf_check -o match:"Sent [0-9]+ bytes" \ 282 tftp <client-script 283 atf_check cmp -s "${remote_file}" "${local_file}" 284} 285tftp_put_ipv6_cleanup() { 286 stop_tftpd 287} 288 289atf_test_case tftp_put_one cleanup 290tftp_put_one_head() { 291 atf_set "descr" "put command with one argument" 292 atf_set "require.user" "root" 293} 294tftp_put_one_body() { 295 start_tftpd 296 local local_file="file.txt" 297 echo "test file $$" >"${local_file}" 298 local remote_file="${tftp_dir}/${local_file}" 299 truncate -s 0 "${remote_file}" 300 chown nobody:nogroup "${remote_file}" 301 chmod 0666 "${remote_file}" 302 echo "put ${local_file}" >client-script 303 atf_check -o match:"Sent [0-9]+ bytes" \ 304 tftp localhost <client-script 305 atf_check cmp -s "${remote_file}" "${local_file}" 306} 307tftp_put_one_cleanup() { 308 stop_tftpd 309} 310 311atf_test_case tftp_put_two cleanup 312tftp_put_two_head() { 313 atf_set "descr" "put command with two arguments" 314 atf_set "require.user" "root" 315} 316tftp_put_two_body() { 317 start_tftpd 318 local local_file="local.txt" 319 echo "test file $$" >"${local_file}" 320 local remote_file="${tftp_dir}/remote.txt" 321 truncate -s 0 "${remote_file}" 322 chown nobody:nogroup "${remote_file}" 323 chmod 0666 "${remote_file}" 324 echo "put ${local_file} ${remote_file##*/}" >client-script 325 atf_check -o match:"Sent [0-9]+ bytes" \ 326 tftp localhost <client-script 327 atf_check cmp -s "${remote_file}" "${local_file}" 328} 329tftp_put_two_cleanup() { 330 stop_tftpd 331} 332 333atf_test_case tftp_put_more cleanup 334tftp_put_more_head() { 335 atf_set "descr" "put command with three or more arguments" 336 atf_set "require.user" "root" 337} 338tftp_put_more_body() { 339 start_tftpd 340 mkdir "${tftp_dir}/subdir" 341 for n in 2 3 4 5 6 ; do 342 echo -n "put" >client-script 343 for f in $(jot -c $n 97) ; do 344 echo "test file $$/$f/$n" >"${f}.txt" 345 truncate -s 0 "${tftp_dir}/subdir/${f}.txt" 346 chown nobody:nogroup "${tftp_dir}/subdir/${f}.txt" 347 chmod 0666 "${tftp_dir}/subdir/${f}.txt" 348 echo -n " ${f}.txt" >>client-script 349 done 350 echo " subdir" >>client-script 351 atf_check -o match:"Sent [0-9]+ bytes" \ 352 tftp localhost <client-script 353 for f in $(jot -c $n 97) ; do 354 atf_check cmp -s "${tftp_dir}/subdir/${f}.txt" "${f}.txt" 355 done 356 done 357} 358tftp_put_more_cleanup() { 359 stop_tftpd 360} 361 362atf_test_case tftp_put_multi_host cleanup 363tftp_put_multi_host_head() { 364 atf_set "descr" "put command with multiple files and host name" 365 atf_set "require.user" "root" 366} 367tftp_put_multi_host_body() { 368 start_tftpd 369 mkdir "${tftp_dir}/subdir" 370 echo -n "put" >client-script 371 for f in a b c ; do 372 echo "test file $$/$f" >"${f}.txt" 373 truncate -s 0 "${tftp_dir}/subdir/${f}.txt" 374 chown nobody:nogroup "${tftp_dir}/subdir/${f}.txt" 375 chmod 0666 "${tftp_dir}/subdir/${f}.txt" 376 echo -n " ${f}.txt" >>client-script 377 done 378 echo " localhost:subdir" >>client-script 379 atf_check -o match:"Sent [0-9]+ bytes" \ 380 tftp <client-script 381 for f in a b c ; do 382 atf_check cmp -s "${tftp_dir}/subdir/${f}.txt" "${f}.txt" 383 done 384} 385tftp_put_multi_host_cleanup() { 386 stop_tftpd 387} 388 389atf_test_case tftp_url_host cleanup 390tftp_url_host_head() { 391 atf_set "descr" "URL with hostname" 392 atf_set "require.user" "root" 393} 394tftp_url_host_body() { 395 start_tftpd 396 local remote_file="${tftp_dir}/hello.txt" 397 echo "Hello, $$!" >"${remote_file}" 398 local local_file="${remote_file##*/}" 399 atf_check -o match:"Received [0-9]+ bytes" \ 400 tftp tftp://localhost/"${remote_file##*/}" 401 atf_check cmp -s "${local_file}" "${remote_file}" 402} 403tftp_url_host_cleanup() { 404 stop_tftpd 405} 406 407atf_test_case tftp_url_ipv4 cleanup 408tftp_url_ipv4_head() { 409 atf_set "descr" "URL with IPv4 address" 410 atf_set "require.user" "root" 411} 412tftp_url_ipv4_body() { 413 start_tftpd 414 local remote_file="${tftp_dir}/hello.txt" 415 echo "Hello, $$!" >"${remote_file}" 416 local local_file="${remote_file##*/}" 417 atf_check -o match:"Received [0-9]+ bytes" \ 418 tftp tftp://127.0.0.1/"${remote_file##*/}" 419 atf_check cmp -s "${local_file}" "${remote_file}" 420} 421tftp_url_ipv4_cleanup() { 422 stop_tftpd 423} 424 425atf_test_case tftp_url_ipv6 cleanup 426tftp_url_ipv6_head() { 427 atf_set "descr" "URL with IPv6 address" 428 atf_set "require.user" "root" 429} 430tftp_url_ipv6_body() { 431 sysctl -q kern.features.inet6 || atf_skip "This test requires IPv6 support" 432 atf_expect_fail "tftp does not support bracketed IPv6 literals in URLs" 433 start_tftpd 434 local remote_file="${tftp_dir}/hello.txt" 435 echo "Hello, $$!" >"${remote_file}" 436 local local_file="${remote_file##*/}" 437 atf_check -o match:"Received [0-9]+ bytes" \ 438 tftp tftp://"[::1]"/"${remote_file##*/}" 439 atf_check cmp -s "${local_file}" "${remote_file}" 440} 441tftp_url_ipv6_cleanup() { 442 stop_tftpd 443} 444 445atf_init_test_cases() { 446 atf_add_test_case tftp_get_big 447 atf_add_test_case tftp_get_host 448 atf_add_test_case tftp_get_ipv4 449 atf_add_test_case tftp_get_ipv6 450 atf_add_test_case tftp_get_one 451 atf_add_test_case tftp_get_two 452 atf_add_test_case tftp_get_more 453 atf_add_test_case tftp_get_multi_host 454 atf_add_test_case tftp_put_big 455 atf_add_test_case tftp_put_host 456 atf_add_test_case tftp_put_ipv4 457 atf_add_test_case tftp_put_ipv6 458 atf_add_test_case tftp_put_one 459 atf_add_test_case tftp_put_two 460 atf_add_test_case tftp_put_more 461 atf_add_test_case tftp_put_multi_host 462 atf_add_test_case tftp_url_host 463 atf_add_test_case tftp_url_ipv4 464 atf_add_test_case tftp_url_ipv6 465} 466