1#!/bin/ksh 2# 3# $OpenBSD: rcctl.sh,v 1.108 2020/03/31 08:03:44 ajacoutot Exp $ 4# 5# Copyright (c) 2014, 2015 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} = *.* ]] && 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_rtable daemon_timeout daemon_user 255 256 if svc_is_special ${_svc}; then 257 daemon_flags="$(eval echo \${${_svc}})" 258 else 259 # set pkg daemon_flags to "NO" to match base svc 260 if ! svc_is_base ${_svc}; then 261 if ! echo ${pkg_scripts} | grep -qw -- ${_svc}; then 262 daemon_flags="NO" 263 fi 264 fi 265 266 if ! svc_is_meta ${_svc}; then 267 # these are expensive, make sure they are explicitly requested 268 if [ -z "${_var}" -o "${_var}" = "class" ]; then 269 getcap -f /etc/login.conf ${_svc} 1>/dev/null 2>&1 && \ 270 daemon_class=${_svc} 271 [ -z "${daemon_class}" ] && \ 272 daemon_class="$(svc_getdef ${_svc} class)" 273 fi 274 if [[ -z ${_var} || ${_var} == @(flags|status) ]]; then 275 [ -z "${daemon_flags}" ] && \ 276 daemon_flags="$(eval echo \"\${${_svc}_flags}\")" 277 [ -z "${daemon_flags}" ] && \ 278 daemon_flags="$(svc_getdef ${_svc} flags)" 279 fi 280 if [ -z "${_var}" -o "${_var}" = "rtable" ]; then 281 [ -z "${daemon_rtable}" ] && \ 282 daemon_rtable="$(eval echo \"\${${_svc}_rtable}\")" 283 [ -z "${daemon_rtable}" ] && \ 284 daemon_rtable="$(svc_getdef ${_svc} rtable)" 285 fi 286 if [ -z "${_var}" -o "${_var}" = "timeout" ]; then 287 [ -z "${daemon_timeout}" ] && \ 288 daemon_timeout="$(eval echo \"\${${_svc}_timeout}\")" 289 [ -z "${daemon_timeout}" ] && \ 290 daemon_timeout="$(svc_getdef ${_svc} timeout)" 291 fi 292 if [ -z "${_var}" -o "${_var}" = "user" ]; then 293 [ -z "${daemon_user}" ] && \ 294 daemon_user="$(eval echo \"\${${_svc}_user}\")" 295 [ -z "${daemon_user}" ] && \ 296 daemon_user="$(svc_getdef ${_svc} user)" 297 fi 298 fi 299 fi 300 301 [ "${daemon_flags}" = "NO" ] && _status=1 302 303 if [ -n "${_var}" ]; then 304 [ "${_var}" = "status" ] && return ${_status} 305 eval _val=\${daemon_${_var}} 306 [ -z "${_val}" ] || print -r -- "${_val}" 307 else 308 svc_is_meta ${_svc} && return ${_status} 309 if svc_is_special ${_svc}; then 310 echo "${_svc}=${daemon_flags}" 311 else 312 echo "${_svc}_class=${daemon_class}" 313 echo "${_svc}_flags=${daemon_flags}" 314 echo "${_svc}_rtable=${daemon_rtable}" 315 echo "${_svc}_timeout=${daemon_timeout}" 316 echo "${_svc}_user=${daemon_user}" 317 fi 318 return ${_status} 319 fi 320} 321 322# to prevent namespace pollution, only call in a subshell 323svc_getdef() 324{ 325 local _svc=$1 326 [ -n "${_svc}" ] || return 327 328 local _status=0 _val _var=$2 329 local daemon_class daemon_flags daemon_rtable daemon_timeout daemon_user 330 331 if svc_is_special ${_svc}; then 332 # unconditionally parse: we always output flags and/or status 333 _rc_parse_conf /etc/rc.conf 334 daemon_flags="$(eval echo \${${_svc}})" 335 [ "${daemon_flags}" = "NO" ] && _status=1 336 else 337 if ! svc_is_base ${_svc}; then 338 _status=1 # all pkg_scripts are off by default 339 else 340 # abuse /etc/rc.conf behavior of only setting flags 341 # to empty or "NO" to get our default status; 342 # we'll get our default flags from the rc.d script 343 [[ -z ${_var} || ${_var} == status ]] && \ 344 _rc_parse_conf /etc/rc.conf 345 [ "$(eval echo \${${_svc}_flags})" = "NO" ] && _status=1 346 fi 347 348 if ! svc_is_meta ${_svc}; then 349 rc_cmd() { } 350 . /etc/rc.d/${_svc} >/dev/null 2>&1 351 352 daemon_class=daemon 353 [ -z "${daemon_rtable}" ] && daemon_rtable=0 354 [ -z "${daemon_timeout}" ] && daemon_timeout=30 355 [ -z "${daemon_user}" ] && daemon_user=root 356 fi 357 fi 358 359 if [ -n "${_var}" ]; then 360 [ "${_var}" = "status" ] && return ${_status} 361 eval _val=\${daemon_${_var}} 362 [ -z "${_val}" ] || print -r -- "${_val}" 363 else 364 svc_is_meta ${_svc} && return ${_status} 365 if svc_is_special ${_svc}; then 366 echo "${_svc}=${daemon_flags}" 367 else 368 echo "${_svc}_class=${daemon_class}" 369 echo "${_svc}_flags=${daemon_flags}" 370 echo "${_svc}_rtable=${daemon_rtable}" 371 echo "${_svc}_timeout=${daemon_timeout}" 372 echo "${_svc}_user=${daemon_user}" 373 fi 374 return ${_status} 375 fi 376} 377 378svc_rm() 379{ 380 local _svc=$1 381 [ -n "${_svc}" ] || return 382 383 rcconf_edit_begin 384 if svc_is_special ${_svc}; then 385 grep -v "^${_svc}.*=" /etc/rc.conf.local >${_TMP_RCCONF} 386 ( svc_getdef ${_svc} status ) && \ 387 echo "${_svc}=NO" >>${_TMP_RCCONF} 388 else 389 grep -Ev "^${_svc}_(flags|rtable|timeout|user).*=" \ 390 /etc/rc.conf.local >${_TMP_RCCONF} 391 ( svc_getdef ${_svc} status ) && \ 392 echo "${_svc}_flags=NO" >>${_TMP_RCCONF} 393 fi 394 rcconf_edit_end 395} 396 397svc_set() 398{ 399 local _svc=$1 _var=$2 400 [ -n "${_svc}" -a -n "${_var}" ] || return 401 402 shift 2 403 local _args="$*" 404 405 # don't check if we are already enabled or disabled because rc.conf(8) 406 # defaults may have changed in which case we may have a matching 407 # redundant entry in rc.conf.local that we can drop 408 if [ "${_var}" = "status" ]; then 409 if [ "${_args}" = "on" ]; then 410 _var="flags" 411 # keep our flags if we're already enabled 412 eval "_args=\"\${${_svc}_${_var}}\"" 413 [ "${_args}" = "NO" ] && unset _args 414 if ! svc_is_base ${_svc} && ! svc_is_special ${_svc}; then 415 pkg_scripts_append ${_svc} 416 fi 417 elif [ "${_args}" = "off" ]; then 418 if ! svc_is_base ${_svc} && ! svc_is_special ${_svc}; then 419 pkg_scripts_rm ${_svc} 420 fi 421 svc_rm ${_svc} 422 return 423 else 424 rcctl_err "invalid status \"${_args}\"" 425 fi 426 else 427 svc_get ${_svc} status || \ 428 rcctl_err "${svc} is not enabled" 429 fi 430 431 if svc_is_special ${_svc}; then 432 [ "${_var}" = "flags" ] || return 433 rcconf_edit_begin 434 grep -v "^${_svc}.*=" /etc/rc.conf.local >${_TMP_RCCONF} 435 ( svc_getdef ${_svc} status ) || \ 436 echo "${_svc}=YES" >>${_TMP_RCCONF} 437 rcconf_edit_end 438 return 439 fi 440 441 if [ -n "${_args}" ]; then 442 if [ "${_var}" = "rtable" ]; then 443 [[ ${_args} != +([[:digit:]]) || ${_args} -lt 0 ]] && \ 444 rcctl_err "\"${_args}\" is not an integer" 445 fi 446 if [ "${_var}" = "timeout" ]; then 447 [[ ${_args} != +([[:digit:]]) || ${_args} -le 0 ]] && \ 448 rcctl_err "\"${_args}\" is not a positive integer" 449 fi 450 if [ "${_var}" = "user" ]; then 451 getent passwd "${_args}" >/dev/null || \ 452 rcctl_err "user \"${_args}\" does not exist" 453 fi 454 # unset flags if they match the default enabled ones 455 [ "${_args}" = "$(svc_getdef ${_svc} ${_var})" ] && \ 456 unset _args 457 fi 458 459 # protect leading whitespace 460 [ "${_args}" = "${_args# }" ] || _args="\"${_args}\"" 461 462 # reset: value may have changed 463 unset ${_svc}_${_var} 464 465 rcconf_edit_begin 466 grep -v "^${_svc}_${_var}.*=" /etc/rc.conf.local >${_TMP_RCCONF} 467 if [ -n "${_args}" ] || \ 468 ( svc_is_base ${_svc} && ! svc_getdef ${_svc} status && [ "${_var}" == "flags" ] ); then 469 echo "${_svc}_${_var}=${_args}" >>${_TMP_RCCONF} 470 fi 471 rcconf_edit_end 472} 473 474unset _RC_DEBUG _RC_FORCE 475while getopts "df" c; do 476 case "$c" in 477 d) _RC_DEBUG=-d;; 478 f) _RC_FORCE=-f;; 479 *) usage;; 480 esac 481done 482shift $((OPTIND-1)) 483[ $# -gt 0 ] || usage 484 485action=$1 486ret=0 487 488case ${action} in 489 ls) 490 lsarg=$2 491 [[ ${lsarg} == @(all|failed|off|on|started|stopped) ]] || usage 492 ;; 493 order) 494 shift 1 495 svcs="$*" 496 for svc in ${svcs}; do 497 svc_is_avail ${svc} || \ 498 rcctl_err "service ${svc} does not exist" 2 499 done 500 ;; 501 disable|enable|start|stop|restart|reload|check) 502 shift 1 503 svcs="$*" 504 [ -z "${svcs}" ] && usage 505 # it's ok to disable a non-existing daemon 506 if [ "${action}" != "disable" ]; then 507 for svc in ${svcs}; do 508 svc_is_avail ${svc} || \ 509 rcctl_err "service ${svc} does not exist" 2 510 done 511 fi 512 ;; 513 get|getdef) 514 svc=$2 515 var=$3 516 [ -z "${svc}" ] && usage 517 [ "${svc}" = "all" ] || svc_is_avail ${svc} || \ 518 rcctl_err "service ${svc} does not exist" 2 519 if [ -n "${var}" ]; then 520 [ "${svc}" = "all" ] && usage 521 [[ ${var} != @(class|flags|status|rtable|timeout|user) ]] && usage 522 if svc_is_meta ${svc}; then 523 [ "${var}" != "status" ] && \ 524 rcctl_err "/etc/rc.d/${svc} is a meta script, cannot \"${action} ${var}\"" 525 fi 526 if svc_is_special ${svc}; then 527 [[ ${var} == @(class|rtable|timeout|user) ]] && \ 528 rcctl_err "\"${svc}\" is a special variable, cannot \"${action} ${var}\"" 529 fi 530 fi 531 ;; 532 set) 533 svc=$2 534 var=$3 535 [ $# -ge 3 ] && shift 3 || shift $# 536 args="$*" 537 [ -z "${svc}" ] && usage 538 # it's ok to disable a non-existing daemon 539 if [ "${action} ${var} ${args}" != "set status off" ]; then 540 svc_is_avail ${svc} || \ 541 rcctl_err "service ${svc} does not exist" 2 542 fi 543 [[ ${var} != @(class|flags|rtable|status|timeout|user) ]] && usage 544 svc_is_meta ${svc} && [ "${var}" != "status" ] && \ 545 rcctl_err "/etc/rc.d/${svc} is a meta script, cannot \"${action} ${var}\"" 546 [[ ${var} = flags && ${args} = NO ]] && \ 547 rcctl_err "\"flags NO\" contradicts \"${action}\"" 548 if svc_is_special ${svc}; then 549 [[ ${var} != status ]] && \ 550 rcctl_err "\"${svc}\" is a special variable, cannot \"${action} ${var}\"" 551 fi 552 [[ ${var} == class ]] && \ 553 rcctl_err "\"${svc}_class\" is a read-only variable set in login.conf(5)" 554 ;; 555 *) 556 usage 557 ;; 558esac 559 560case ${action} in 561 disable) 562 needs_root ${action} 563 for svc in ${svcs}; do 564 svc_set ${svc} status off || ret=$?; 565 done 566 exit ${ret} 567 ;; 568 enable) 569 needs_root ${action} 570 for svc in ${svcs}; do 571 svc_set ${svc} status on || ret=$?; 572 done 573 exit ${ret} 574 ;; 575 get|getdef) 576 if [ "${svc}" = "all" ]; then 577 for svc in $(svc_ls all); do 578 ( svc_${action} ${svc} "${var}" ) 579 done 580 return 0 # we do not want the svc status 581 else 582 ( svc_${action} ${svc} "${var}" ) 583 fi 584 ;; 585 ls) 586 # some rc.d(8) scripts need root for rc_check() 587 [[ ${lsarg} == @(started|stopped|failed) ]] && needs_root ${action} ${lsarg} 588 svc_ls ${lsarg} 589 ;; 590 order) 591 if [ -n "${svcs}" ]; then 592 needs_root ${action} 593 pkg_scripts_order ${svcs} 594 else 595 [[ -z ${pkg_scripts} ]] || echo ${pkg_scripts} 596 fi 597 ;; 598 set) 599 needs_root ${action} 600 svc_set ${svc} "${var}" "${args}" 601 ;; 602 start|stop|restart|reload|check) 603 for svc in ${svcs}; do 604 if svc_is_special ${svc}; then 605 rcctl_err "\"${svc}\" is a special variable, no rc.d(8) script" 606 fi 607 /etc/rc.d/${svc} ${_RC_DEBUG} ${_RC_FORCE} ${action} || ret=$?; 608 done 609 exit ${ret} 610 ;; 611 *) 612 usage 613 ;; 614esac 615