1#!/bin/ksh 2# 3# $OpenBSD: rcctl.sh,v 1.110 2021/02/27 09:28:04 ajacoutot Exp $ 4# 5# Copyright (c) 2014, 2015-2021 Antoine Jacoutot <ajacoutot@openbsd.org> 6# Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org> 7# 8# Permission to use, copy, modify, and distribute this software for any 9# purpose with or without fee is hereby granted, provided that the above 10# copyright notice and this permission notice appear in all copies. 11# 12# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 18# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 20_special_svcs="accounting check_quotas ipsec library_aslr multicast pf 21 spamd_black" 22readonly _special_svcs 23 24# get local functions from rc.subr(8) 25FUNCS_ONLY=1 26. /etc/rc.d/rc.subr 27_rc_parse_conf 28 29usage() 30{ 31 local _a _i 32 for _i in ${_rc_actions}; do _a="$(echo -n ${_i}${_a:+|${_a}})"; done 33 34 _rc_err \ 35 "usage: rcctl get|getdef|set service | daemon [variable [arguments]] 36 rcctl [-df] ${_a} daemon ... 37 rcctl disable|enable|order [daemon ...] 38 rcctl ls all|failed|off|on|started|stopped" 39} 40 41needs_root() 42{ 43 [ "$(id -u)" -ne 0 ] && _rc_err "${0##*/}: \"$*\" needs root privileges" 44} 45 46rcctl_err() 47{ 48 _rc_err "${0##*/}: ${1}" ${2} 49} 50 51ls_rcscripts() 52{ 53 local _s 54 55 cd /etc/rc.d && set -- * 56 for _s; do 57 [[ ${_s} == +([[:alnum:]_]) ]] || continue 58 [ ! -d "${_s}" ] && echo "${_s}" 59 done 60} 61 62pkg_scripts_append() 63{ 64 local _svc=$1 65 [ -n "${_svc}" ] || return 66 67 rcconf_edit_begin 68 if [ -z "${pkg_scripts}" ]; then 69 echo pkg_scripts="${_svc}" >>${_TMP_RCCONF} 70 elif ! echo ${pkg_scripts} | grep -qw -- ${_svc}; then 71 grep -v "^pkg_scripts.*=" /etc/rc.conf.local >${_TMP_RCCONF} 72 echo pkg_scripts="${pkg_scripts} ${_svc}" >>${_TMP_RCCONF} 73 fi 74 rcconf_edit_end 75} 76 77pkg_scripts_order() 78{ 79 local _svcs="$*" 80 [ -n "${_svcs}" ] || return 81 82 needs_root ${action} 83 local _pkg_scripts _svc 84 for _svc in ${_svcs}; do 85 if svc_is_base ${_svc} || svc_is_special ${_svc}; then 86 rcctl_err "${_svc} is not a pkg script" 87 elif ! svc_get ${_svc} status; then 88 rcctl_err "${_svc} is not enabled" 89 fi 90 done 91 _pkg_scripts=$(echo "${_svcs} ${pkg_scripts}" | tr "[:blank:]" "\n" | \ 92 awk -v ORS=' ' '!x[$0]++') 93 rcconf_edit_begin 94 grep -v "^pkg_scripts.*=" /etc/rc.conf.local >${_TMP_RCCONF} 95 echo pkg_scripts=${_pkg_scripts} >>${_TMP_RCCONF} 96 rcconf_edit_end 97} 98 99pkg_scripts_rm() 100{ 101 local _svc=$1 102 [ -n "${_svc}" ] || return 103 104 [ -z "${pkg_scripts}" ] && return 105 106 rcconf_edit_begin 107 sed "/^pkg_scripts[[:>:]]/{s/[[:<:]]${_svc}[[:>:]]//g 108 s/['\"]//g;s/ *= */=/;s/ */ /g;s/ $//;/=$/d;}" \ 109 /etc/rc.conf.local >${_TMP_RCCONF} 110 rcconf_edit_end 111} 112 113rcconf_edit_begin() 114{ 115 _TMP_RCCONF=$(mktemp -p /etc -t rc.conf.local.XXXXXXXXXX) || \ 116 rcctl_err "cannot create temporary file under /etc" 117 if [ -f /etc/rc.conf.local ]; then 118 cat /etc/rc.conf.local >${_TMP_RCCONF} || \ 119 rcctl_err "cannot append to ${_TMP_RCCONF}" 120 else 121 touch /etc/rc.conf.local || \ 122 rcctl_err "cannot create /etc/rc.conf.local" 123 fi 124} 125 126rcconf_edit_end() 127{ 128 sort -u -o ${_TMP_RCCONF} ${_TMP_RCCONF} || \ 129 rcctl_err "cannot modify ${_TMP_RCCONF}" 130 cat ${_TMP_RCCONF} >/etc/rc.conf.local || \ 131 rcctl_err "cannot append to /etc/rc.conf.local" 132 if [ ! -s /etc/rc.conf.local ]; then 133 rm /etc/rc.conf.local || \ 134 rcctl_err "cannot remove /etc/rc.conf.local" 135 fi 136 rm -f ${_TMP_RCCONF} 137 _rc_parse_conf # reload new values 138} 139 140svc_is_avail() 141{ 142 local _svc=$1 143 _rc_check_name "${_svc}" || return 144 145 [ -x "/etc/rc.d/${_svc}" ] && return 146 svc_is_special ${_svc} 147} 148 149svc_is_base() 150{ 151 local _svc=$1 152 [ -n "${_svc}" ] || return 153 154 local _cached _ret 155 156 _cached=$(eval echo \${cached_svc_is_base_${_svc}}) 157 [ "${_cached}" ] && return "${_cached}" 158 159 grep -qw "^${_svc}_flags" /etc/rc.conf 160 _ret=$? 161 162 set -A cached_svc_is_base_${_svc} -- ${_ret} 163 return ${_ret} 164} 165 166svc_is_meta() 167{ 168 local _svc=$1 169 [ -n "${_svc}" ] || return 170 171 local _cached _ret 172 173 _cached=$(eval echo \${cached_svc_is_meta_${_svc}}) 174 [ "${_cached}" ] && return "${_cached}" 175 176 [ -r "/etc/rc.d/${_svc}" ] && ! grep -qw "^rc_cmd" /etc/rc.d/${_svc} 177 _ret=$? 178 179 set -A cached_svc_is_meta_${_svc} -- ${_ret} 180 return ${_ret} 181} 182 183svc_is_special() 184{ 185 local _svc=$1 186 [ -n "${_svc}" ] || return 187 188 local _cached _ret 189 190 _cached=$(eval echo \${cached_svc_is_special_${_svc}}) 191 [ "${_cached}" ] && return "${_cached}" 192 193 echo ${_special_svcs} | grep -qw -- ${_svc} 194 _ret=$? 195 196 set -A cached_svc_is_special_${_svc} -- ${_ret} 197 return ${_ret} 198} 199 200svc_ls() 201{ 202 local _lsarg=$1 203 [ -n "${_lsarg}" ] || return 204 205 # we do not want to return the "status" nor the rc.d(8) script retcode 206 local _ret=0 _on _svc _started 207 208 case ${_lsarg} in 209 all) 210 ( 211 ls_rcscripts 212 echo ${_special_svcs} | tr "[:blank:]" "\n" 213 ) | sort 214 ;; 215 failed) 216 for _svc in $(svc_ls on); do 217 ! svc_is_special ${_svc} && \ 218 ! /etc/rc.d/${_svc} check >/dev/null && \ 219 echo ${_svc} && _ret=1 220 done 221 ;; 222 off|on) 223 for _svc in $(svc_ls all); do 224 svc_get ${_svc} status && _on=1 225 [ "${_lsarg}" = "on" -a -n "${_on}" ] || \ 226 [ "${_lsarg}" = "off" -a -z "${_on}" ] && \ 227 echo ${_svc} 228 unset _on 229 done 230 ;; 231 started|stopped) 232 for _svc in $(ls_rcscripts); do 233 /etc/rc.d/${_svc} check >/dev/null && _started=1 234 [ "${_lsarg}" = "started" -a -n "${_started}" ] || \ 235 [ "${_lsarg}" = "stopped" -a -z "${_started}" ] && \ 236 echo ${_svc} 237 unset _started 238 done 239 ;; 240 *) 241 _ret=1 242 ;; 243 esac 244 245 return ${_ret} 246} 247 248svc_get() 249{ 250 local _svc=$1 251 [ -n "${_svc}" ] || return 252 253 local _status=0 _val _var=$2 254 local daemon_class daemon_flags daemon_logger daemon_rtable 255 local daemon_timeout daemon_user 256 257 if svc_is_special ${_svc}; then 258 daemon_flags="$(eval echo \${${_svc}})" 259 else 260 # set pkg daemon_flags to "NO" to match base svc 261 if ! svc_is_base ${_svc}; then 262 if ! echo ${pkg_scripts} | grep -qw -- ${_svc}; then 263 daemon_flags="NO" 264 fi 265 fi 266 267 if ! svc_is_meta ${_svc}; then 268 # these are expensive, make sure they are explicitly requested 269 if [ -z "${_var}" -o "${_var}" = "class" ]; then 270 getcap -f /etc/login.conf ${_svc} 1>/dev/null 2>&1 && \ 271 daemon_class=${_svc} 272 [ -z "${daemon_class}" ] && \ 273 daemon_class="$(svc_getdef ${_svc} class)" 274 fi 275 if [[ -z ${_var} || ${_var} == @(flags|status) ]]; then 276 [ -z "${daemon_flags}" ] && \ 277 daemon_flags="$(eval echo \"\${${_svc}_flags}\")" 278 [ -z "${daemon_flags}" ] && \ 279 daemon_flags="$(svc_getdef ${_svc} flags)" 280 fi 281 if [ -z "${_var}" -o "${_var}" = "logger" ]; then 282 [ -z "${daemon_logger}" ] && \ 283 daemon_logger="$(eval echo \"\${${_svc}_logger}\")" 284 [ -z "${daemon_logger}" ] && \ 285 daemon_logger="$(svc_getdef ${_svc} logger)" 286 fi 287 if [ -z "${_var}" -o "${_var}" = "rtable" ]; then 288 [ -z "${daemon_rtable}" ] && \ 289 daemon_rtable="$(eval echo \"\${${_svc}_rtable}\")" 290 [ -z "${daemon_rtable}" ] && \ 291 daemon_rtable="$(svc_getdef ${_svc} rtable)" 292 fi 293 if [ -z "${_var}" -o "${_var}" = "timeout" ]; then 294 [ -z "${daemon_timeout}" ] && \ 295 daemon_timeout="$(eval echo \"\${${_svc}_timeout}\")" 296 [ -z "${daemon_timeout}" ] && \ 297 daemon_timeout="$(svc_getdef ${_svc} timeout)" 298 fi 299 if [ -z "${_var}" -o "${_var}" = "user" ]; then 300 [ -z "${daemon_user}" ] && \ 301 daemon_user="$(eval echo \"\${${_svc}_user}\")" 302 [ -z "${daemon_user}" ] && \ 303 daemon_user="$(svc_getdef ${_svc} user)" 304 fi 305 fi 306 fi 307 308 [ "${daemon_flags}" = "NO" ] && _status=1 309 310 if [ -n "${_var}" ]; then 311 [ "${_var}" = "status" ] && return ${_status} 312 eval _val=\${daemon_${_var}} 313 [ -z "${_val}" ] || print -r -- "${_val}" 314 else 315 svc_is_meta ${_svc} && return ${_status} 316 if svc_is_special ${_svc}; then 317 echo "${_svc}=${daemon_flags}" 318 else 319 echo "${_svc}_class=${daemon_class}" 320 echo "${_svc}_flags=${daemon_flags}" 321 echo "${_svc}_logger=${daemon_logger}" 322 echo "${_svc}_rtable=${daemon_rtable}" 323 echo "${_svc}_timeout=${daemon_timeout}" 324 echo "${_svc}_user=${daemon_user}" 325 fi 326 return ${_status} 327 fi 328} 329 330# to prevent namespace pollution, only call in a subshell 331svc_getdef() 332{ 333 local _svc=$1 334 [ -n "${_svc}" ] || return 335 336 local _status=0 _val _var=$2 337 local daemon_class daemon_flags daemon_logger daemon_rtable 338 local daemon_timeout daemon_user 339 340 if svc_is_special ${_svc}; then 341 # unconditionally parse: we always output flags and/or status 342 _rc_parse_conf /etc/rc.conf 343 daemon_flags="$(eval echo \${${_svc}})" 344 [ "${daemon_flags}" = "NO" ] && _status=1 345 else 346 if ! svc_is_base ${_svc}; then 347 _status=1 # all pkg_scripts are off by default 348 else 349 # abuse /etc/rc.conf behavior of only setting flags 350 # to empty or "NO" to get our default status; 351 # we'll get our default flags from the rc.d script 352 [[ -z ${_var} || ${_var} == status ]] && \ 353 _rc_parse_conf /etc/rc.conf 354 [ "$(eval echo \${${_svc}_flags})" = "NO" ] && _status=1 355 fi 356 357 if ! svc_is_meta ${_svc}; then 358 rc_cmd() { } 359 . /etc/rc.d/${_svc} >/dev/null 2>&1 360 361 daemon_class=daemon 362 [ -z "${daemon_rtable}" ] && daemon_rtable=0 363 [ -z "${daemon_timeout}" ] && daemon_timeout=30 364 [ -z "${daemon_user}" ] && daemon_user=root 365 fi 366 fi 367 368 if [ -n "${_var}" ]; then 369 [ "${_var}" = "status" ] && return ${_status} 370 eval _val=\${daemon_${_var}} 371 [ -z "${_val}" ] || print -r -- "${_val}" 372 else 373 svc_is_meta ${_svc} && return ${_status} 374 if svc_is_special ${_svc}; then 375 echo "${_svc}=${daemon_flags}" 376 else 377 echo "${_svc}_class=${daemon_class}" 378 echo "${_svc}_flags=${daemon_flags}" 379 echo "${_svc}_logger=${daemon_logger}" 380 echo "${_svc}_rtable=${daemon_rtable}" 381 echo "${_svc}_timeout=${daemon_timeout}" 382 echo "${_svc}_user=${daemon_user}" 383 fi 384 return ${_status} 385 fi 386} 387 388svc_rm() 389{ 390 local _svc=$1 391 [ -n "${_svc}" ] || return 392 393 rcconf_edit_begin 394 if svc_is_special ${_svc}; then 395 grep -v "^${_svc}.*=" /etc/rc.conf.local >${_TMP_RCCONF} 396 ( svc_getdef ${_svc} status ) && \ 397 echo "${_svc}=NO" >>${_TMP_RCCONF} 398 else 399 grep -Ev "^${_svc}_(flags|logger|rtable|timeout|user).*=" \ 400 /etc/rc.conf.local >${_TMP_RCCONF} 401 ( svc_getdef ${_svc} status ) && \ 402 echo "${_svc}_flags=NO" >>${_TMP_RCCONF} 403 fi 404 rcconf_edit_end 405} 406 407svc_set() 408{ 409 local _svc=$1 _var=$2 410 [ -n "${_svc}" -a -n "${_var}" ] || return 411 412 shift 2 413 local _args="$*" 414 415 # don't check if we are already enabled or disabled because rc.conf(8) 416 # defaults may have changed in which case we may have a matching 417 # redundant entry in rc.conf.local that we can drop 418 if [ "${_var}" = "status" ]; then 419 if [ "${_args}" = "on" ]; then 420 _var="flags" 421 # keep our flags if we're already enabled 422 eval "_args=\"\${${_svc}_${_var}}\"" 423 [ "${_args}" = "NO" ] && unset _args 424 if ! svc_is_base ${_svc} && ! svc_is_special ${_svc}; then 425 pkg_scripts_append ${_svc} 426 fi 427 elif [ "${_args}" = "off" ]; then 428 if ! svc_is_base ${_svc} && ! svc_is_special ${_svc}; then 429 pkg_scripts_rm ${_svc} 430 fi 431 svc_rm ${_svc} 432 return 433 else 434 rcctl_err "invalid status \"${_args}\"" 435 fi 436 else 437 svc_get ${_svc} status || \ 438 rcctl_err "${svc} is not enabled" 439 fi 440 441 if svc_is_special ${_svc}; then 442 [ "${_var}" = "flags" ] || return 443 rcconf_edit_begin 444 grep -v "^${_svc}.*=" /etc/rc.conf.local >${_TMP_RCCONF} 445 ( svc_getdef ${_svc} status ) || \ 446 echo "${_svc}=YES" >>${_TMP_RCCONF} 447 rcconf_edit_end 448 return 449 fi 450 451 if [ -n "${_args}" ]; then 452 if [ "${_var}" = "logger" ]; then 453 logger -p "${_args}" </dev/null >/dev/null 2>&1 || 454 rcctl_err "unknown priority name: \"${_args}\"" 455 fi 456 if [ "${_var}" = "rtable" ]; then 457 [[ ${_args} != +([[:digit:]]) || ${_args} -lt 0 ]] && \ 458 rcctl_err "\"${_args}\" is not an integer" 459 fi 460 if [ "${_var}" = "timeout" ]; then 461 [[ ${_args} != +([[:digit:]]) || ${_args} -le 0 ]] && \ 462 rcctl_err "\"${_args}\" is not a positive integer" 463 fi 464 if [ "${_var}" = "user" ]; then 465 getent passwd "${_args}" >/dev/null || \ 466 rcctl_err "user \"${_args}\" does not exist" 467 fi 468 # unset flags if they match the default enabled ones 469 [ "${_args}" = "$(svc_getdef ${_svc} ${_var})" ] && \ 470 unset _args 471 fi 472 473 # protect leading whitespace 474 [ "${_args}" = "${_args# }" ] || _args="\"${_args}\"" 475 476 # reset: value may have changed 477 unset ${_svc}_${_var} 478 479 rcconf_edit_begin 480 grep -v "^${_svc}_${_var}.*=" /etc/rc.conf.local >${_TMP_RCCONF} 481 if [ -n "${_args}" ] || \ 482 ( svc_is_base ${_svc} && ! svc_getdef ${_svc} status && [ "${_var}" == "flags" ] ); then 483 echo "${_svc}_${_var}=${_args}" >>${_TMP_RCCONF} 484 fi 485 rcconf_edit_end 486} 487 488unset _RC_DEBUG _RC_FORCE 489while getopts "df" c; do 490 case "$c" in 491 d) _RC_DEBUG=-d;; 492 f) _RC_FORCE=-f;; 493 *) usage;; 494 esac 495done 496shift $((OPTIND-1)) 497[ $# -gt 0 ] || usage 498 499action=$1 500ret=0 501 502case ${action} in 503 ls) 504 lsarg=$2 505 [[ ${lsarg} == @(all|failed|off|on|started|stopped) ]] || usage 506 ;; 507 order) 508 shift 1 509 svcs="$*" 510 for svc in ${svcs}; do 511 svc_is_avail ${svc} || \ 512 rcctl_err "service ${svc} does not exist" 2 513 done 514 ;; 515 disable|enable|start|stop|restart|reload|check) 516 shift 1 517 svcs="$*" 518 [ -z "${svcs}" ] && usage 519 # it's ok to disable a non-existing daemon 520 if [ "${action}" != "disable" ]; then 521 for svc in ${svcs}; do 522 svc_is_avail ${svc} || \ 523 rcctl_err "service ${svc} does not exist" 2 524 done 525 fi 526 ;; 527 get|getdef) 528 svc=$2 529 var=$3 530 [ -z "${svc}" ] && usage 531 [ "${svc}" = "all" ] || svc_is_avail ${svc} || \ 532 rcctl_err "service ${svc} does not exist" 2 533 if [ -n "${var}" ]; then 534 [ "${svc}" = "all" ] && usage 535 [[ ${var} != @(class|flags|logger|rtable|status|timeout|user) ]] && usage 536 if svc_is_meta ${svc}; then 537 [ "${var}" != "status" ] && \ 538 rcctl_err "/etc/rc.d/${svc} is a meta script, cannot \"${action} ${var}\"" 539 fi 540 if svc_is_special ${svc}; then 541 [[ ${var} == @(class|logger|rtable|timeout|user) ]] && \ 542 rcctl_err "\"${svc}\" is a special variable, cannot \"${action} ${var}\"" 543 fi 544 fi 545 ;; 546 set) 547 svc=$2 548 var=$3 549 [ $# -ge 3 ] && shift 3 || shift $# 550 args="$*" 551 [ -z "${svc}" ] && usage 552 # it's ok to disable a non-existing daemon 553 if [ "${action} ${var} ${args}" != "set status off" ]; then 554 svc_is_avail ${svc} || \ 555 rcctl_err "service ${svc} does not exist" 2 556 fi 557 [[ ${var} != @(class|flags|logger|rtable|status|timeout|user) ]] && usage 558 svc_is_meta ${svc} && [ "${var}" != "status" ] && \ 559 rcctl_err "/etc/rc.d/${svc} is a meta script, cannot \"${action} ${var}\"" 560 [[ ${var} = flags && ${args} = NO ]] && \ 561 rcctl_err "\"flags NO\" contradicts \"${action}\"" 562 if svc_is_special ${svc}; then 563 [[ ${var} != status ]] && \ 564 rcctl_err "\"${svc}\" is a special variable, cannot \"${action} ${var}\"" 565 fi 566 [[ ${var} == class ]] && \ 567 rcctl_err "\"${svc}_class\" is a read-only variable set in login.conf(5)" 568 ;; 569 *) 570 usage 571 ;; 572esac 573 574case ${action} in 575 disable) 576 needs_root ${action} 577 for svc in ${svcs}; do 578 svc_set ${svc} status off || ret=$?; 579 done 580 exit ${ret} 581 ;; 582 enable) 583 needs_root ${action} 584 for svc in ${svcs}; do 585 svc_set ${svc} status on || ret=$?; 586 done 587 exit ${ret} 588 ;; 589 get|getdef) 590 if [ "${svc}" = "all" ]; then 591 for svc in $(svc_ls all); do 592 ( svc_${action} ${svc} "${var}" ) 593 done 594 return 0 # we do not want the svc status 595 else 596 ( svc_${action} ${svc} "${var}" ) 597 fi 598 ;; 599 ls) 600 # some rc.d(8) scripts need root for rc_check() 601 [[ ${lsarg} == @(started|stopped|failed) ]] && needs_root ${action} ${lsarg} 602 svc_ls ${lsarg} 603 ;; 604 order) 605 if [ -n "${svcs}" ]; then 606 needs_root ${action} 607 pkg_scripts_order ${svcs} 608 else 609 [[ -z ${pkg_scripts} ]] || echo ${pkg_scripts} 610 fi 611 ;; 612 set) 613 needs_root ${action} 614 svc_set ${svc} "${var}" "${args}" 615 ;; 616 start|stop|restart|reload|check) 617 for svc in ${svcs}; do 618 if svc_is_special ${svc}; then 619 rcctl_err "\"${svc}\" is a special variable, no rc.d(8) script" 620 fi 621 /etc/rc.d/${svc} ${_RC_DEBUG} ${_RC_FORCE} ${action} || ret=$?; 622 done 623 exit ${ret} 624 ;; 625 *) 626 usage 627 ;; 628esac 629