├── README.md ├── join-ad-winbind.sh └── join-ad-sssd.sh /README.md: -------------------------------------------------------------------------------- 1 | # Join Debian to Active Directory 2 | 3 | Bash scripts for interactive joining Debain machines to the Active Directory. 4 | 5 | ## join-ad-sssd.sh 6 | 7 | Joins Debian machine to the Active Directory by using sssd and realmd. 8 | 9 | This script configures the environment and joins the machine to the Active Directory domain. To join the domain, used sssd (System Security Services Daemon) and realmd. 10 | 11 | realmd discovers information about the domain or realm automatically and does not require complicated configuration in order to join a domain or realm. realmd configures the basic settings of the sssd to do the actual network authentication and user account lookups. 12 | 13 | The script configures several subsystems during execution. 14 | 15 | - Configures the local DNS cache using dnsmasq. Available DNS servers are automatically detected. 16 | - Configures the local DNS resolver and checks that DNS settings are correct. 17 | - Configures the NTP client and forces synchronization of the system time. Available NTP servers are automatically detected. 18 | - Configures Kerberos. Available KDC servers are automatically detected. 19 | - Configures realmd and joins the machine to the domain using the first available LDAP server. The available LDAP servers are detected automatically. 20 | - Configures sssd, fine tuning after realmd. 21 | - Configures PAM, enables mkhomedir module. 22 | - Configures access to the server (login) using domain groups. 23 | - Configures administrator rights on the server (sudo) using the domain groups. 24 | 25 | Bonus: 26 | 27 | - Configures SSH and enables GSSAPI for passwordless login. 28 | - Configures autocomplete in bash, enables autocomplete for a interactive root sessions. 29 | 30 | #### Command line options 31 | 32 | ``` 33 | Usage: 34 | join-ad-sssd.sh [-hq] [-s hostname] [-d domainname] [-u username] 35 | 36 | Options: 37 | -h Show this message. 38 | -q Suppress debug messages. 39 | -s hostname Specifies available domain controller. Can be specified multiple times. 40 | -d domainname Specifies domain name. 41 | -u username Specifies domain user name that will be used for joining to the domain. 42 | ``` 43 | 44 | #### Supported and tested Linux 45 | 46 | Debian 8 (jessie) 47 | Debian 9 (stretch) 48 | 49 | --- 50 | 51 | ## join-ad-winbind.sh (obsolete) 52 | 53 | Joins machine to the domain using Samba and Winbind. 54 | 55 | #### Supported Linux 56 | 57 | Debian Wheezy and Debian Jessie 58 | 59 | #### Tested 60 | 61 | Samba 3.6.6 and 4.1.17 (not work with Samba 4.2.10) 62 | -------------------------------------------------------------------------------- /join-ad-winbind.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # You can use this script on your own risk 4 | 5 | VERFILE=/etc/debian_version 6 | DEPENDENCIES="sudo samba krb5-user winbind libnss-winbind libpam-winbind ntp bash-completion" 7 | PACKAGES="" # list of not installed packages 8 | 9 | # Configs backup directory 10 | BACKUPDIR=~/.ADCONNECT/ 11 | 12 | # Configuration files location 13 | SMBCNF=/etc/samba/smb.conf 14 | KRBCNF=/etc/krb5.conf 15 | NSSWITCHCNF=/etc/nsswitch.conf 16 | PAMAUTHCNF=/etc/pam.d/common-auth 17 | PAMSESSCNF=/etc/pam.d/common-session 18 | SUDOCNF=/etc/pam.d/sudo 19 | SUDOERS=/etc/sudoers 20 | 21 | CONFIGS="$SMBCNF $KRBCNF $NSSWITCHCNF $PAMAUTHCNF $PAMSESSCNF $SUDOCNF $SUDOERS" 22 | UNFCONF="" # list of unfounded configs 23 | 24 | if [[ $USER != "root" ]]; then 25 | echo -e "[\033[31mWARN\033[m] Script needs root privileges!" 26 | exit 27 | fi 28 | 29 | if [ -f $VERFILE ]; then 30 | VERSION=$(sed 's/\..*//' $VERFILE) 31 | fi 32 | 33 | case $VERSION in 34 | 7) 35 | OSNAME="Debian Wheezy" 36 | OSVER="Debian $(cat $VERFILE), Linux kernel $(uname -r)" 37 | echo -e "\033[1;37mNow we try to connect your \033[33mWheezy \033[1;37mto AD\033[m" 38 | ;; 39 | 8) 40 | OSNAME="Debian Jessie" 41 | OSVER="Debian $(cat $VERFILE), Linux kernel $(uname -r)" 42 | echo -e "\033[1;37mNow we try to connect your \033[33mJessie\033[1;37m to AD\033[m" 43 | ;; 44 | *) 45 | echo -e "\033[1;37mThis is \033[31munsupported\033[1;37m version of Linux\033[m" 46 | echo Stoping script... 47 | exit 48 | ;; 49 | esac 50 | 51 | echo -e "\nCheck packages installation:" 52 | 53 | # Check installed packages 54 | for i in $DEPENDENCIES 55 | do 56 | CHECK=$(dpkg-query -f '\${binary:Package}\n' -W | grep ${i} -c) 57 | if [[ $CHECK != 0 ]]; then 58 | echo -e "[ \033[32mOK\033[m ] \033[33m$i\033[m is installed" 59 | else 60 | echo -e "[\033[31mWARN\033[m] \033[33m$i\033[m is not installed" 61 | PACKAGES="$PACKAGES $i" 62 | fi 63 | done 64 | 65 | #PACKAGES=" " 66 | 67 | if [[ $PACKAGES != "" ]]; then 68 | echo "" 69 | echo -e "Packages for instalation: \033[33m$PACKAGES\033[m" 70 | 71 | TRY=0 72 | while true ; do 73 | read -erp "Do You want to install it? [Yes/No]: " 74 | case $REPLY in 75 | [Yy]|[Yy][Ee]|[Yy][Ee][Ss] ) 76 | apt-get update 77 | DEBIAN_FRONTEND=noninteractive apt-get -y install $PACKAGES 78 | break 79 | ;; 80 | [Nn]|[Nn][Oo] ) 81 | echo Stoping script... 82 | exit 83 | ;; 84 | * ) 85 | echo "Please answer \"Yes\" or \"No\"". 86 | ;; 87 | esac 88 | done 89 | fi 90 | 91 | echo -e "\nCheck config files:" 92 | 93 | for i in $CONFIGS 94 | do 95 | if [ -e $i ]; then 96 | echo -e "[ \033[32mOK\033[m ] \033[33m$i\033[m is exist" 97 | else 98 | echo -e "[\033[31mWARN\033[m] \033[33m$i\033[m is not exist" 99 | UNFCONF="$UNFCONF $i" 100 | fi 101 | done 102 | 103 | if [[ $UNFCONF != "" ]]; then 104 | echo Please recover configuration files: 105 | for i in $UNFCONF 106 | do 107 | echo -e "\t$UNFCONF" 108 | done 109 | echo Stoping script... 110 | exit 111 | fi 112 | 113 | # Setting bash_completion for root 114 | 115 | COMPLETITION=$(grep -c ". /etc/bash_completion" /root/.bashrc) 116 | 117 | if [ $COMPLETITION == 0 ]; then 118 | echo -e "if [ -f /etc/bash_completion ]; then\n\t. /etc/bash_completion\nfi" >> /root/.bashrc 119 | fi 120 | 121 | # Backup config files 122 | 123 | test -d $BACKUPDIR || mkdir -p $BACKUPDIR 124 | 125 | echo "" 126 | echo "Check of existing config's backups:" 127 | 128 | EXBACKUP=0 # number of exist backups 129 | 130 | for i in $CONFIGS 131 | do 132 | BACKUPFILE="$BACKUPDIR$(echo $i | sed 's/.*\///').BACK" 133 | if [ -e $BACKUPFILE ] ; then 134 | echo -e "[ \033[33mEX\033[m ] \033[33m$BACKUPFILE\033[m is exist" 135 | let EXBACKUP+=1 136 | else 137 | echo -e "[ \033[32mOK\033[m ] \033[33m$BACKUPFILE\033[m is not exist" 138 | fi 139 | done 140 | 141 | if [ $EXBACKUP -gt 0 ] ; then 142 | while true ; do 143 | read -erp "Some backup files was found. Do you want to rewrite it? [Yes/No]: " 144 | case $REPLY in 145 | [Yy]|[Yy][Ee]|[Yy][Ee][Ss] ) 146 | # OK exit loop and resume script 147 | break 148 | ;; 149 | [Nn]|[Nn][Oo] ) 150 | # Stop script 151 | echo Stoping script... 152 | exit 153 | ;; 154 | * ) 155 | echo "Please answer \"Yes\" or \"No\"". 156 | ;; 157 | esac 158 | done 159 | fi 160 | 161 | echo "Backuping configs..." 162 | for i in $CONFIGS 163 | do 164 | cp $i "$BACKUPDIR$(echo $i | sed 's/.*\///').BACK" 165 | done 166 | 167 | cp /etc/resolv.conf "${BACKUPDIR}resolv.conf.BACK" 168 | 169 | # Set domain 170 | while true ; do 171 | read -erp "Please enter your domain name: " 172 | # regex get from http://myregexp.com/examples.html 173 | if [[ $REPLY =~ ^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$ ]]; then 174 | DOMAIN=${REPLY^^} 175 | WORKGROUP=$(echo $DOMAIN | sed 's/\..*//') 176 | SERVER=${DOMAIN,,} 177 | break 178 | else 179 | echo "Wrong domain format ($REPLY), please try again!" 180 | #exit 181 | fi 182 | done 183 | 184 | # Check resolving domain 185 | while true ; do 186 | host ${DOMAIN,,} >/dev/null 2>&1 187 | if [[ $? != "0" ]]; then 188 | echo "Can't resolve ${DOMAIN,,}" 189 | # http://www.regextester.com/22 190 | # ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ 191 | read -erp "Please enter your domain DNS IP address: " 192 | if [[ $REPLY =~ ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]]; then 193 | echo nameserver $REPLY > /etc/resolv.conf 194 | host ${DOMAIN,,} >/dev/null 2>&1 195 | if [[ $? == "0" ]]; then 196 | DNSS=$(host ${DOMAIN,,} | grep "has address" | sed 's/.*\shas\saddress\s//') 197 | echo domain $DOMAIN > /etc/resolv.conf 198 | echo search ${DOMAIN,,} >> /etc/resolv.conf 199 | for i in $DNSS ; do 200 | nslookup ${DOMAIN,,} $i > /dev/null 2>&1 201 | if [[ $? == "0" ]]; then 202 | echo $i 203 | echo "nameserver ${i}" >> /etc/resolv.conf 204 | fi 205 | done 206 | 207 | while true ; do 208 | read -erp "This is a your real DNS servers? [Yes/No]: " 209 | case $REPLY in 210 | [Yy]|[Yy][Ee]|[Yy][Ee][Ss] ) 211 | echo OK thanks. 212 | break 213 | ;; 214 | [Nn]|[Nn][Oo] ) 215 | while true ; do 216 | read -erp "Please write your DNS servers's IP separeted by space or coma(,): " 217 | # check list of IP adresses 218 | if [[ $REPLY =~ ^((((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])),?[ ]{0,}){1,}((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])))|((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))$ ]]; then 219 | echo domain ${DOMAIN,,} > /etc/resolv.conf 220 | echo search ${DOMAIN,,} >> /etc/resolv.conf 221 | DNSS=$(echo $REPLY | sed 's/,\s*\|\s/ /g') 222 | for i in $DNSS ; do 223 | nslookup $DOMAIN $i > /dev/null 2>&1 224 | if [[ $? == "0" ]]; then 225 | echo $i 226 | echo "nameserver ${i}" >> /etc/resolv.conf 227 | fi 228 | done 229 | host ${DOMAIN,,} >/dev/null 2>&1 230 | if [[ $? != "0" ]]; then 231 | echo -e "[\033[31mWARN\033[m] Cant resolve ${DOMAIN,,}. Please check your network connection" 232 | else 233 | break 234 | fi 235 | else 236 | echo Wrong format. Try again. 237 | fi 238 | done 239 | ;; 240 | * ) 241 | echo "Please answer \"Yes\" or \"No\"". 242 | ;; 243 | esac 244 | done 245 | fi 246 | else 247 | echo You write not valid IP address! 248 | fi 249 | else 250 | break 251 | fi 252 | done 253 | 254 | # Edit /etc/hosts 255 | 256 | if [ $(grep -c -e "^[^#]*${HOSTNAME}" /etc/hosts) -gt 0 ]; then 257 | #echo 1 #DEBUG 258 | if [ $(grep -c -e "^[^#]*${HOSTNAME}.${DOMAIN,,}" /etc/hosts) == 0 ]; then 259 | HOSTTEXT=$(grep -e "^[^#]*${HOSTNAME}" /etc/hosts) 260 | #echo 1.1 #DEBUG 261 | sed -i "s/^[^#]*${HOSTNAME}/${HOSTTEXT} $HOSTNAME.${DOMAIN,,}/" /etc/hosts 262 | fi 263 | else 264 | HOSTSTR=$(grep -n -e '^\s*127.0.1.1' /etc/hosts | sed 's/:.*//') 265 | HOSTTEXT=$(grep -e '^\s*127.0.1.1' /etc/hosts) 266 | #echo 2 #DEBUG 267 | if [[ $HOSTSTR != 0 ]] && [[ $HOSTTEXT != "" ]]; then 268 | sed -i '/^\s*127.0.1.1/d' /etc/hosts 269 | sed -i "${HOSTSTR}i${HOSTTEXT} $HOSTNAME $HOSTNAME.${DOMAIN,,}" /etc/hosts 270 | #echo 2.1 #DEBUG 271 | else 272 | #echo 2.2 #DEBUG 273 | if [[ $(grep -c -e '^\s*127.0.0.1' /etc/hosts) != 0 ]]; then 274 | #echo 2.2.1 #DEBUG 275 | HOSTSTR=$(grep -n -e '^\s*127.0.0.1' /etc/hosts | sed 's/:.*//') 276 | let HOSTSTR+=1 277 | sed -i "${HOSTSTR}i127.0.1.1\t$HOSTNAME $HOSTNAME.${DOMAIN,,}" /etc/hosts 278 | else 279 | #echo 2.2.2 #DEBUG 280 | sed -i "1i127.0.0.1\tlocalhost\n127.0.1.1\t$HOSTNAME $HOSTNAME.${DOMAIN,,}" /etc/hosts 281 | # echo 127.0.0.1 string not found in /etc/hosts file. 282 | fi 283 | fi 284 | fi 285 | 286 | if [[ $(grep -c -e '^\s*127.0.0.1' /etc/hosts) == 0 ]]; then 287 | #echo 3 #DEBUG 288 | sed -i "1i127.0.0.1\tlocalhost" /etc/hosts 289 | fi 290 | 291 | # Edit smb config 292 | 293 | STARTSTR=$(grep -n '#======================= Global Settings =======================' $SMBCNF | sed 's/:.*//') 294 | ENDSTR=$(grep -n '#======================= Share Definitions =======================' $SMBCNF | sed 's/:.*//') 295 | 296 | #echo $STARTSTR 297 | #echo $ENDSTR 298 | 299 | if [[ $STARTSTR != "" ]] && [[ $ENDSTR != "" ]]; then 300 | sed -i '/#======================= Global Settings =======================/,/#======================= Share Definitions =======================/d' $SMBCNF 301 | ABRACADABRA="#======================= Global Settings =======================\n\n[global]\n\tsecurity = ads\n\tname resolve order = wins bcast host\n\trealm = ${DOMAIN}\n\tpassword server = ${SERVER}\n\tworkgroup = ${WORKGROUP}\n\n\twinbind refresh tickets = yes\n\twinbind enum users = yes\n\twinbind enum groups = yes\n\twinbind use default domain = yes\n\twinbind nss info = template\n\twinbind cache time = 10800\n\twinbind offline logon = true\n\n\tidmap config * : backend = tdb\n\tidmap config * : range = 2000-9999\n\n\tidmap config ${WORKGROUP}:backend = rid\n\tidmap config ${WORKGROUP}:range = 10000-99999\n\n\ttemplate homedir = /home/%U\n\ttemplate shell = /bin/bash\n\n\tclient use spnego = yes\n\tclient ntlmv2 auth = yes\n\tclient ldap sasl wrapping = seal\n\n\tencrypt passwords = yes\n\n\trestrict anonymous = 2\n\n\tdomain master = no\n\tlocal master = no\n\tpreferred master = no\n\tos level = 0\n\n\tload printers = no\n\tprinting = bsd\n\tprintcap name = /dev/null\n\n#======================= Share Definitions =======================" 302 | sed -i "${STARTSTR}i${ABRACADABRA}" $SMBCNF 303 | else 304 | echo Key string not found. Process was stopped on samba configuration. 305 | echo Stoping script... 306 | exit 307 | fi 308 | 309 | # Edit krb config 310 | 311 | sed -i "/^\sdefault_realm/ s/default_realm.*$/default_realm = ${DOMAIN}/" $KRBCNF 312 | 313 | # Edit nsswitch config 314 | 315 | sed -i 's/^\s*passwd:.*/passwd: compat winbind/' $NSSWITCHCNF 316 | sed -i 's/^\s*group:.*/group: compat winbind/' $NSSWITCHCNF 317 | sed -i 's/^\s*shadow:.*/shadow: compat winbind/' $NSSWITCHCNF 318 | 319 | case $VERSION in 320 | 7) 321 | service winbind stop 322 | service samba restart 323 | service winbind start 324 | ;; 325 | 8) 326 | service winbind stop 327 | service smbd restart 328 | service winbind start 329 | ;; 330 | *) 331 | echo -e "This is \033[31munsupported\033[m version of Linux" 332 | echo Stoping script... 333 | exit 334 | ;; 335 | esac 336 | 337 | sleep 0.5 338 | 339 | while true ; do 340 | read -erp "Please enter your domain administrator login: " 341 | if [[ $REPLY =~ ^[a-zA-Z0-9\.\-]{0,}$ ]]; then 342 | net ads join -U ${REPLY}@${DOMAIN} osname="${OSNAME}" osver="${OSVER}" 343 | break 344 | else 345 | echo Wrong username 346 | fi 347 | done 348 | 349 | case $VERSION in 350 | 7) 351 | service winbind stop 352 | service samba restart 353 | service winbind start 354 | ;; 355 | 8) 356 | service winbind stop 357 | service smbd restart 358 | service winbind start 359 | ;; 360 | *) 361 | echo -e "This is \033[31munsupported\033[m version of Linux" 362 | echo Stoping script... 363 | exit 364 | ;; 365 | esac 366 | 367 | sleep 0.5 368 | 369 | # Configure pam and sudoers 370 | 371 | if [ $(getent group | grep "domain users" -c) -gt 0 ]; then 372 | echo -e "[ \033[32mOK\033[m ] Computer was connected to domain." 373 | while true ; do 374 | read -erp "Do you whant to specify group or user for domain authorization? [Yes/No]: " 375 | case $REPLY in 376 | [Yy]|[Yy][Ee]|[Yy][Ee][Ss] ) 377 | while true ; do 378 | read -erp "Please type AD group or user name: " 379 | GROUP=${REPLY,,} 380 | WBINFO=$(wbinfo -n "$REPLY") 381 | if [[ $? == 0 ]] ; then 382 | SID=$(echo $WBINFO | awk '{ print $1 }') 383 | 384 | AUTH="# ${SID} - \"$GROUP\"\nauth [success=1 default=ignore] pam_winbind.so krb5_auth krb5_ccache_type=FILE cached_login try_first_pass require_membership_of=$SID" 385 | echo -e $AUTH 386 | sed -i "s/^\s*auth\s*\[success=1 default=ignore\]\s*pam_winbind.so\skrb5_auth\skrb5_ccache_type=FILE.*$/${AUTH}/" $PAMAUTHCNF 387 | 388 | break 389 | else 390 | echo "Please try again" 391 | fi 392 | done 393 | break 394 | ;; 395 | [Nn]|[Nn][Oo] ) 396 | break 397 | ;; 398 | * ) 399 | echo "Please answer \"Yes\" or \"No\"". 400 | ;; 401 | esac 402 | done 403 | 404 | if [[ $(grep -e "^session\s*required\s*pam_mkhomedir.so" $PAMSESSCNF) == "" ]]; then 405 | sed -i "s/session\s*required\s*pam_unix.so/session\trequired\t\t\tpam_unix.so\nsession\trequired\t\t\tpam_mkhomedir.so/" $PAMSESSCNF 406 | fi 407 | 408 | if [[ $( grep -e "#@include common-session-noninteractive" $SUDOCNF) == "" ]]; then 409 | sed -i "s/^\@include\scommon-session-noninteractive/\#\@include\scommon-session-noninteractive/" $SUDOCNF 410 | fi 411 | 412 | while true ; do 413 | read -erp "Please type user or AD group witch can use sudo: " 414 | case $(wbinfo -n "${REPLY}") in 415 | *SID_USER* ) 416 | USER=${REPLY,,} 417 | SUDO="# \"$USER\" user from AD\n$(echo $USER | sed 's/\s/\\ /g') ALL=(ALL:ALL) ALL" 418 | echo -e $SUDO > /etc/sudoers.d/AD 419 | break 420 | ;; 421 | *SID_DOM_GROUP* ) 422 | GROUP=${REPLY,,} 423 | SUDO="# \"$GROUP\" group from AD\n%$(echo $GROUP | sed 's/\s/\\ /g') ALL=(ALL:ALL) ALL" 424 | echo -e $SUDO > /etc/sudoers.d/AD 425 | break 426 | ;; 427 | * ) 428 | echo "Please try again" 429 | ;; 430 | esac 431 | done 432 | else 433 | echo -e "[\033[31mWARN\033[m] Computer was not connected to domain. Something going wrong." 434 | echo Stoping script... 435 | exit 436 | fi 437 | 438 | echo Finish! 439 | 440 | exit 441 | -------------------------------------------------------------------------------- /join-ad-sssd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Joins Debian machine to the Active Directory by using sssd and realmd. 4 | # 5 | # This script configures the environment and joins the machine to the Active Directory domain. 6 | # To join the domain, used sssd (System Security Services Daemon) and realmd. 7 | # 8 | # realmd discovers information about the domain or realm automatically and 9 | # does not require complicated configuration in order to join a domain or realm. 10 | # realmd configures the basic settings of the sssd to do the actual network authentication and 11 | # user account lookups. 12 | # 13 | # The script configures several subsystems during execution. 14 | # 15 | # - Configures the local DNS cache using dnsmasq. Available DNS servers are automatically detected. 16 | # - Configures the local DNS resolver and checks that DNS settings are correct. 17 | # - Configures the NTP client and forces synchronization of the system time. 18 | # Available NTP servers are automatically detected. 19 | # - Configures Kerberos. Available KDC servers are automatically detected. 20 | # - Configures realmd and joins the machine to the domain using the first available LDAP server. 21 | # The available LDAP servers are detected automatically. 22 | # - Configures sssd, fine tuning after realmd. 23 | # - Configures PAM, enables mkhomedir module. 24 | # - Configures access to the server (login) using domain groups. 25 | # - Configures administrator rights on the server (sudo) using the domain groups. 26 | # 27 | # Bonus: 28 | # 29 | # - Configures SSH and enables GSSAPI for passwordless login. 30 | # - Configures autocomplete in bash, enables autocomplete for a interactive root sessions. 31 | # 32 | # Copyright (C) 2017 Stepan Kokhanovskiy 33 | # 34 | # This program is free software: you can redistribute it and/or modify 35 | # it under the terms of the GNU General Public License as published by 36 | # the Free Software Foundation, either version 3 of the License, or 37 | # (at your option) any later version. 38 | # 39 | # This program is distributed in the hope that it will be useful, 40 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 41 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 42 | # GNU General Public License for more details. 43 | # 44 | # You should have received a copy of the GNU General Public License 45 | # along with this program. If not, see . 46 | 47 | 48 | # Script configuration section 49 | 50 | readonly PROGNAME="$(basename "${0}")" 51 | readonly LOCK_ENABLED=1 52 | readonly LOCK_FILE="/tmp/${PROGNAME}.lock" 53 | readonly LOCK_FD=931 54 | readonly LOG_NAME="${PROGNAME%.*}" 55 | readonly LOG_ENABLED=0 56 | readonly LOG_FILE="/var/log/${LOG_NAME}/${LOG_NAME}.log" 57 | readonly SYSLOG_ENABLED=0 58 | readonly SYSLOG_PRIORITY="user.notice" 59 | 60 | 61 | # Global constants 62 | 63 | readonly APTGET_ASSUME_YES=1 64 | readonly BACKUP_DIR="${HOME}/.${PROGNAME}" 65 | readonly OS_RELEASE_FILE="/etc/os-release" 66 | 67 | readonly PACKAGE_INSTALLED_STRING="ok installed" 68 | 69 | readonly DNS_LDAP_SRV_FORMAT="_ldap._tcp.%s" 70 | 71 | readonly DIG_TRIES=1 72 | readonly DIG_TIMEOUT=3 73 | 74 | readonly DNSMASQ_CONFIG_FILE="/etc/dnsmasq.d/dnscache.conf" 75 | readonly DNSMASQ_RESOLV_FILE="/etc/resolv.dnsmasq" 76 | readonly DNSMASQ_SYSTEM_RESOLV_FILE="/etc/resolv.conf" 77 | readonly DNSMASQ_HOSTS_FILE="/etc/hosts" 78 | readonly DNSMASQ_SERVICE_NAME="dnsmasq" 79 | 80 | readonly NTP_CONFIG_FILE="/etc/ntp.conf" 81 | readonly NTP_CONFIG_BACKUP="${BACKUP_DIR}/$(basename "${NTP_CONFIG_FILE}")" 82 | readonly NTP_SERVICE_NAME="ntp" 83 | readonly NTP_TEMP_LOG="/tmp/ntpd.log" 84 | 85 | readonly PORT_CONNECT_TIMEOUT=5 86 | readonly PORT_TEST_COMMAND="cat /dev/tcp/%s/%s" 87 | 88 | readonly LDAP_PORT=389 89 | 90 | readonly KERBEROS_PORT=88 91 | readonly KERBEROS_CONFIG_FILE="/etc/krb5.conf" 92 | readonly KERBEROS_TICKET_LIFETIME="10h" 93 | readonly KERBEROS_RENEW_LIFETIME="7d" 94 | readonly KERBEROS_CLOCKSKEW=300 95 | 96 | readonly USER_NAME_PROMPT="Enter the domain user name or leave empty to exit. 97 | Username: " 98 | readonly LOGIN_GROUP_PROMPT="Enter the name of the domain group that will be permitted to login or leave empty to continue. 99 | Group to login: " 100 | readonly SUDO_GROUP_PROMPT="Enter the name of the domain group that will be permitted to sudo or leave empty to continue. 101 | Group to sudo: " 102 | 103 | readonly REALMD_CONFIG_FILE="/etc/realmd.conf" 104 | readonly REALMD_PRINCIPAL="host/%s@%s" 105 | 106 | readonly SSSD_CONFIG_FILE="/etc/sssd/sssd.conf" 107 | readonly SSSD_DB_DIR="/var/lib/sss/db" 108 | readonly SSSD_CACHE_DIR="/var/lib/sss/mc" 109 | readonly SSSD_SERVICE_NAME="sssd" 110 | readonly SSSD_DISCOVERY_SERVER="_srv_" 111 | readonly SSSD_DEBUG_LEVEL=0 112 | readonly SSSD_AD_SERVER_DISCOVERY=1 113 | readonly SSSD_CACHE_CREDENTIALS=1 114 | readonly SSSD_KRB5_AUTH_TIMEOUT=60 115 | readonly SSSD_LDAP_OPT_TIMEOUT=${SSSD_KRB5_AUTH_TIMEOUT} 116 | readonly SSSD_PAM_ID_TIMEOUT=${SSSD_KRB5_AUTH_TIMEOUT} 117 | readonly SSSD_IGNORE_GROUP_MEMBERS=0 118 | readonly SSSD_USE_FQDN_NAMES=0 119 | 120 | readonly PAM_SESSIONS_CONFIG_FILE="/etc/pam.d/common-session" 121 | readonly PAM_MKHOMEDIR_CONFIG_FILE="/usr/share/pam-configs/mkhomedir" 122 | 123 | readonly SUDO_CONFIG_DIR="/etc/sudoers.d" 124 | readonly SUDO_SERVICE_NAME="sudo" 125 | 126 | readonly SSHD_CONFIG_FILE="/etc/ssh/sshd_config" 127 | readonly SSHD_TMP_CONFIG_FILE="/tmp/sshd_config" 128 | readonly SSHD_SERVICE_NAME="ssh" 129 | 130 | readonly BASH_SYSTEM_STARTUP_FILE="/etc/bash.bashrc" 131 | readonly BASH_TMP_SYSTEM_STARTUP_FILE="/tmp/bash.bashrc" 132 | readonly BASH_COMPLETION_FROM_PATTERN="# enable bash completion in interactive shells\r#if ! shopt -oq posix; then\r# if \[ -f \/usr\/share\/bash-completion\/bash_completion \]; then\r# \. \/usr\/share\/bash-completion\/bash_completion\r# elif \[ -f \/etc\/bash_completion \]; then\r# \. \/etc\/bash_completion\r# fi\r#fi\r" 133 | readonly BASH_COMPLETION_TO_PATTERN="# enable bash completion in interactive shells\rif ! shopt -oq posix; then\r if \[ -f \/usr\/share\/bash-completion\/bash_completion \]; then\r \. \/usr\/share\/bash-completion\/bash_completion\r elif \[ -f \/etc\/bash_completion \]; then\r \. \/etc\/bash_completion\r fi\rfi\r" 134 | 135 | 136 | # Paths to binaries 137 | 138 | readonly LOGGER_PATH="/usr/bin/logger" 139 | readonly APTGET_PATH="/usr/bin/apt-get" 140 | readonly SERVICE_PATH="/usr/sbin/service" 141 | readonly DIRNAME_PATH="/usr/bin/dirname" 142 | readonly GETENT_PATH="/usr/bin/getent" 143 | readonly DIG_PATH="/usr/bin/dig" 144 | readonly NTPDATE_PATH="/usr/sbin/ntpdate" 145 | readonly NTPD_PATH="/usr/sbin/ntpd" 146 | readonly REALM_PATH="/usr/sbin/realm" 147 | readonly TIMEOUT_PATH="/usr/bin/timeout" 148 | readonly TR_PATH="/usr/bin/tr" 149 | readonly KINIT_PATH="/usr/bin/kinit" 150 | readonly KLIST_PATH="/usr/bin/klist" 151 | readonly KDESTROY_PATH="/usr/bin/kdestroy" 152 | readonly DNSMASQ_PATH="/usr/sbin/dnsmasq" 153 | readonly PAM_AUTH_UPDATE_PATH="/usr/sbin/pam-auth-update" 154 | readonly HEAD_PATH="/usr/bin/head" 155 | readonly WC_PATH="/usr/bin/wc" 156 | readonly AWK_PATH="/usr/bin/awk" 157 | 158 | 159 | # Error messages 160 | 161 | readonly E_ANOTHER_INSTANCE_IS_RUNNING="Possibly an another instance of the ${PROGNAME} script is currently running." 162 | readonly E_CAN_NOT_CREATE_LOCK_FILE="Can not create lock file: '${LOCK_FILE}'. ${E_ANOTHER_INSTANCE_IS_RUNNING}" 163 | readonly E_CAN_NOT_LOCK_FILE="Can not lock file: '${LOCK_FILE}'. ${E_ANOTHER_INSTANCE_IS_RUNNING}" 164 | readonly E_CAN_NOT_CREATE_LOG_FILE="Can not create log file: ${LOG_NAME}." 165 | readonly E_CAN_NOT_WRITE_LOG_FILE="Can not write to log file: ${LOG_NAME}." 166 | readonly E_LOG_FILE_IS_NOT_SPECIFIED="Log enabled (LOG_ENABLED = ${LOG_ENABLED}) but a file name for log (LOG_FILE) is not specified. Check the script configuration section." 167 | readonly E_LOGGER_FAILED="Can not write message to the syslog." 168 | readonly E_ARGS_INVALID="Try '${PROGNAME} -h' for help." 169 | readonly E_APTGET_UPDATE="apt-get update failed." 170 | readonly E_APTGET_INSTALL="apt-get install failed." 171 | readonly E_ROOT_REQUIRED="This script must be run as root." 172 | readonly E_DOMAIN_NAME_NOT_FOUND="The DNS domain name not found. Try to specify the domain name using -d parameter or see 'man dnsdomainname' for details." 173 | readonly E_DOMAIN_NAME_NOT_RESOLVED="Check the DNS settings specified at the /etc/resolv.conf." 174 | readonly E_LDAP_SRV_NOT_RESOLVED="Can not resolve the LDAP SRV record '%s'. Check that the DNS servers are configured properly." 175 | readonly E_DOMAIN_CONTROLLER_NOT_FOUND="Can not found domain controllers for the DNS domain name '%s'." 176 | readonly E_NTP_SERVER_UNAVAILABLE="NTP server '%s' is unavailable." 177 | readonly E_NTP_SERVER_NO_AVAILABLE="There are no NTP servers available." 178 | readonly E_NTP_SYNC_FAILED="Time synchronization failed." 179 | readonly E_PORT_UNAVAILABLE="Port '%s:%s' is unavailable." 180 | readonly E_LDAP_SERVER_NO_AVAILABLE="There are no LDAP servers available." 181 | readonly E_DIG_EMPTY_RESPONSE="Can not resolve the DNS record '%s', type %s. Empty response from DNS server." 182 | readonly E_REALM_ALREADY_JOINED="Already joined to the domain '%s'." 183 | readonly E_DNS_SERVER_UNAVAILABLE="DNS server %s is unavailable." 184 | readonly E_GROUP_NOT_FOUND="Can not found group '%s'." 185 | readonly E_HOST_ADDRESS_NOT_FOUND="Can not determine the IP address of the host." 186 | readonly E_USER_NAME_NOT_SPECIFIED="Domain user name is not specified." 187 | readonly E_KERBEROS_SERVER_NO_AVAILABLE="There are no kerberos servers available." 188 | 189 | 190 | # Global flags 191 | 192 | IS_APTGET_UPDATE_COMPLETED=0 193 | DEBUG_ENABLED=1 194 | 195 | 196 | # Creates empty file 197 | # Arguments: 198 | # 1: Path to file to create 199 | # Returns: 200 | # 0: success 201 | # 1: failure 202 | 203 | create_file() 204 | { 205 | local path="${1}" 206 | 207 | [[ -z "${path}" ]] && return 0 208 | 209 | install -D "/dev/null" "${path}" || return 1 210 | 211 | return 0 212 | } 213 | 214 | 215 | # Checks that flag value is true: not empty or zero. 216 | # Arguments: 217 | # 1: value to check 218 | # Returns: 219 | # 0: value is true 220 | # 1: value is false 221 | 222 | is_true() 223 | { 224 | local value="${1}" 225 | 226 | if [[ -n "${value}" ]] && [[ "${value}" != "0" ]] ; then 227 | return 0 228 | else 229 | return 1 230 | fi 231 | } 232 | 233 | 234 | # Prints message to the error output and to the log file 235 | # Arguments: 236 | # 1: message to write 237 | # Returns: 238 | # 0: success 239 | # 1: failure 240 | 241 | log() 242 | { 243 | local msg="${@}" 244 | local timestamp="$(date --rfc-3339=seconds)" 245 | 246 | echo "${msg}" 1>&2 247 | 248 | if is_true "${LOG_ENABLED}" ; then 249 | 250 | # Exit if log file is not specified 251 | 252 | if [[ -z "${LOG_FILE}" ]] ; then 253 | echo "${E_LOG_FILE_IS_NOT_SPECIFIED}" 1>&2 254 | return 1 255 | fi 256 | 257 | # Create log file if it is not exists 258 | 259 | if ! [[ -f "${LOG_FILE}" ]] ; then 260 | if ! create_file "${LOG_FILE}" ; then 261 | echo "${E_CAN_NOT_CREATE_LOG_FILE}" 1>&2 262 | return 1 263 | fi 264 | fi 265 | 266 | if ! echo "[${timestamp}]: ${msg}" >> "${LOG_FILE}" ; then 267 | echo "${E_CAN_NOT_WRITE_LOG_FILE}" 1>&2 268 | return 1 269 | fi 270 | fi 271 | 272 | if is_true "${SYSLOG_ENABLED}" && test_app "${LOGGER_PATH}" ; then 273 | if ! "${LOGGER_PATH}" -t "${LOG_NAME}" -p "${SYSLOG_PRIORITY}" "${msg}" ; then 274 | echo "${E_LOGGER_FAILED}" 1>&2 275 | fi 276 | fi 277 | 278 | return 0 279 | } 280 | 281 | 282 | # Prints debug log message 283 | # Arguments: 284 | # 1: message to write 285 | # Returns: 286 | # 0: success 287 | # 1: failure 288 | 289 | debug() { 290 | 291 | local msg="${@}" 292 | 293 | if is_true "${DEBUG_ENABLED}" ; then 294 | log "DEBUG: ${msg}" || return 1 295 | fi 296 | 297 | return 0 298 | } 299 | 300 | 301 | # Locks file to prevent multiple instances of the script from running at the same time 302 | # Arguments: 303 | # None 304 | # Returns: 305 | # 0: success 306 | # 1: failure 307 | 308 | lock() 309 | { 310 | if is_true "${LOCK_ENABLED}" ; then 311 | 312 | # Create lock file 313 | 314 | if ! eval "exec ${LOCK_FD}>${LOCK_FILE}" ; then 315 | log "${E_CAN_NOT_CREATE_LOCK_FILE}" 316 | return 1 317 | fi 318 | 319 | # Acquier the lock 320 | 321 | if ! flock -n "${LOCK_FD}" ; then 322 | log "${E_CAN_NOT_LOCK_FILE}" 323 | return 1 324 | fi 325 | 326 | fi 327 | 328 | return 0 329 | } 330 | 331 | 332 | # Stop script execution with error message 333 | # Arguments: 334 | # 1: error message 335 | # Returns: 336 | # None 337 | 338 | eexit() 339 | { 340 | local msg="${@}" 341 | 342 | log "${msg}" 343 | 344 | exit 1 345 | } 346 | 347 | 348 | # Print text with the specified new line at the end 349 | # Arguments: 350 | # 1: text to print 351 | # 2: new line to print 352 | # Returns: 353 | # 0: success 354 | # 1: failure 355 | 356 | add_line() 357 | { 358 | local text="${1}" 359 | local new_line="${2}" 360 | 361 | [[ -z "${text}" ]] || echo "${text}" 362 | [[ -z "${new_line}" ]] || echo "${new_line}" 363 | 364 | return 0 365 | } 366 | 367 | 368 | # Print joined lines by using the specified delimiter 369 | # Arguments: 370 | # 1: lines of the text 371 | # 2: delimiter, empty by default 372 | # Returns: 373 | # 0: success 374 | # 1: failure 375 | 376 | join_lines() 377 | { 378 | local text="${1}" 379 | local delimiter="${2}" 380 | 381 | local line="" 382 | local first_line_flag=1 383 | 384 | [[ -z "${text}" ]] && return 0 385 | 386 | while read line ; do 387 | 388 | if is_true "${first_line_flag}" ; then 389 | first_line_flag=0 390 | else 391 | printf "%s" "${delimiter}" 392 | fi 393 | 394 | printf "%s" "${line}" 395 | 396 | done <<< "${text}" 397 | 398 | return 0 399 | } 400 | 401 | 402 | # Prints only the first line from text 403 | # Arguments: 404 | # 1: lines of the text 405 | # Returns: 406 | # 0: success 407 | # 1: failure 408 | 409 | first_line() 410 | { 411 | local text="${1}" 412 | 413 | [[ -z "${text}" ]] && return 0 414 | 415 | "${HEAD_PATH}" --lines=1 <<< "${text}" || return 1 416 | 417 | return 0 418 | } 419 | 420 | 421 | # Prints number of lines in the text 422 | # Arguments: 423 | # 1: lines of the text 424 | # Returns: 425 | # 0: success 426 | # 1: failure 427 | 428 | print_lines_count() 429 | { 430 | local text="${1}" 431 | 432 | "${WC_PATH}" --lines <<< "${text}" || return 1 433 | 434 | return 0 435 | } 436 | 437 | 438 | # Prints text with replaced char 439 | # Arguments: 440 | # 1: source text 441 | # 2: char to replace 442 | # 3: replacing char 443 | # Returns: 444 | # 0: success 445 | # 1: failure 446 | 447 | replace_chars() 448 | { 449 | local text="${1}" 450 | local from_char="${2}" 451 | local to_char="${3}" 452 | 453 | [[ -z "${text}" ]] && return 0 454 | 455 | echo "${text}" | "${TR_PATH}" "${from_char}" "${to_char}" || return 1 456 | 457 | return 0 458 | } 459 | 460 | 461 | # Prints text with replaced substring 462 | # Arguments: 463 | # 1: source text 464 | # 2: substring to replace 465 | # 3: replacing substring 466 | # Returns: 467 | # 0: success 468 | # 1: failure 469 | 470 | replace_string() 471 | { 472 | local text="${1}" 473 | local from_string="${2}" 474 | local to_string="${3}" 475 | 476 | [[ -z "${text}" ]] && return 0 477 | 478 | echo "${text}" | sed "s/${from_string}/${to_string}/g" || return 1 479 | 480 | return 0 481 | } 482 | 483 | 484 | # Prints text uppercase 485 | # Arguments: 486 | # 1: text to uppercase 487 | # Returns: 488 | # 0: success 489 | # 1: failure 490 | 491 | print_uppercase() 492 | { 493 | local text="${1}" 494 | 495 | replace_chars "${text}" '[:lower:]' '[:upper:]' || return 1 496 | 497 | return 0 498 | } 499 | 500 | 501 | # Prints text lowercase 502 | # Arguments: 503 | # 1: text to lowercase 504 | # Returns: 505 | # 0: success 506 | # 1: failure 507 | 508 | print_lowercase() 509 | { 510 | local text="${1}" 511 | 512 | replace_chars "${text}" '[:upper:]' '[:lower:]' || return 1 513 | 514 | return 0 515 | } 516 | 517 | 518 | # Checks that scipt running as root 519 | # Arguments: 520 | # None 521 | # Returns: 522 | # 0: running as root 523 | # 1: running as non-root user 524 | 525 | test_root() 526 | { 527 | if [[ "${EUID}" != "0" ]] ; then 528 | 529 | log "${E_ROOT_REQUIRED}" 530 | return 1 531 | 532 | fi 533 | 534 | return 0 535 | } 536 | 537 | 538 | # Checks that application binaries is exists 539 | # Arguments: 540 | # @: paths or names of the application binaries 541 | # Returns: 542 | # 0: all application exists 543 | # 1: one or more applications does not exists 544 | 545 | test_app() 546 | { 547 | local app_list="${@}" 548 | local app="" 549 | 550 | [[ -z "${app_list}" ]] && return 0 551 | 552 | for app in ${app_list} ; do 553 | 554 | debug "Check application: '${app}'." 555 | 556 | if ! command -v "${app}" &>/dev/null ; then 557 | 558 | log "Application '${app}' does not exists." 559 | return 1 560 | 561 | fi 562 | done 563 | 564 | return 0 565 | } 566 | 567 | 568 | # Checks that specified packages are installed 569 | # Arguments: 570 | # @: names of the packages to check 571 | # Returns: 572 | # 0: all packages are installed 573 | # 1: one or more packages are not installed 574 | 575 | test_package() 576 | { 577 | local package_list="${@}" 578 | 579 | local package="" 580 | local status_string="" 581 | 582 | [[ -z "${package_list}" ]] && return 0 583 | 584 | for package in ${package_list} ; do 585 | 586 | debug "Check package installed: '${package}'." 587 | 588 | status_string="$(dpkg-query --show --showformat='${status}\n' "${package}")" 2>/dev/null || return 1 589 | 590 | if ! echo "${status_string}" | grep "${PACKAGE_INSTALLED_STRING}" &>/dev/null ; then 591 | 592 | debug "Package '${package}' does not installed." 593 | return 1 594 | 595 | fi 596 | done 597 | 598 | return 0 599 | } 600 | 601 | 602 | # Starts 'apt-get update' once 603 | # Arguments: 604 | # None 605 | # Returns: 606 | # 0: apt-get update (already) finished successfully 607 | # 1: apt-get update failed 608 | 609 | start_aptget_update() 610 | { 611 | if ! is_true "${IS_APTGET_UPDATE_COMPLETED}" ; then 612 | 613 | debug "Start 'apt-get update' once." 614 | 615 | if ! "${APTGET_PATH}" "update" ; then 616 | log "${E_APTGET_UPDATE}" 617 | return 1 618 | fi 619 | 620 | IS_APTGET_UPDATE_COMPLETED=1 621 | 622 | debug "'apt-get update' finished successfully." 623 | fi 624 | 625 | return 0 626 | } 627 | 628 | 629 | # Starts 'apt-get install' with specified package list 630 | # Arguments: 631 | # @: names of the packages to install 632 | # Returns: 633 | # 0: success 634 | # 1: failure 635 | 636 | start_aptget_install() 637 | { 638 | local package_list="${@}" 639 | local is_error=0 640 | 641 | [[ -z "${package_list}" ]] && return 0 642 | 643 | if [[ -z "${APTGET_ASSUME_YES}" ]] || [[ "${APTGET_ASSUME_YES}" == "0" ]] ; then 644 | 645 | debug "Start 'apt-get install ${package_list}'." 646 | 647 | "${APTGET_PATH}" "install" ${package_list} || is_error=1 648 | else 649 | 650 | debug "Start 'apt-get -y install ${package_list}'." 651 | 652 | "${APTGET_PATH}" --assume-yes "install" ${package_list} || is_error=1 653 | fi 654 | 655 | if [[ "${is_error}" != 0 ]] ; then 656 | log "${E_APTGET_INSTALL}" 657 | return 1 658 | fi 659 | 660 | debug "Installed successfully: ${package_list}." 661 | 662 | return 0 663 | } 664 | 665 | 666 | # Installs packages 667 | # Arguments: 668 | # @: names of the packages to install 669 | # Returns: 670 | # 0: success 671 | # 1: failure 672 | 673 | install_package() 674 | { 675 | local pkg="${@}" 676 | 677 | [[ -z "${pkg}" ]] && return 0 678 | 679 | debug "Install package: ${pkg}." 680 | 681 | start_aptget_update || return 1 682 | start_aptget_install "${pkg}" || return 1 683 | 684 | return 0 685 | } 686 | 687 | 688 | # Check that the service is running 689 | # Arguments: 690 | # 1: service name 691 | # Returns: 692 | # 0: service is running 693 | # 1: service not running 694 | 695 | test_service() 696 | { 697 | local service_name="${1}" 698 | 699 | [[ -z "${service_name}" ]] && return 0 700 | 701 | debug "Test state of service '${service_name}'." 702 | 703 | if ! "${SERVICE_PATH}" "${service_name}" status &>/dev/null ; then 704 | 705 | debug "Service is stopped." 706 | return 1 707 | 708 | fi 709 | 710 | debug "Service is running." 711 | 712 | return 0 713 | } 714 | 715 | 716 | # Stops the service 717 | # Arguments: 718 | # 1: service name 719 | # Returns: 720 | # 0: stopped successfully 721 | # 1: failure 722 | 723 | stop_service() 724 | { 725 | local service_name="${1}" 726 | 727 | [[ -z "${service_name}" ]] && return 0 728 | 729 | if test_service "${service_name}" ; then 730 | 731 | debug "Stop service '${service_name}'." 732 | 733 | "${SERVICE_PATH}" "${service_name}" stop || return 1 734 | 735 | debug "Stopped successfully." 736 | 737 | fi 738 | 739 | return 0 740 | } 741 | 742 | 743 | # Starts the service 744 | # Arguments: 745 | # 1: service name 746 | # Returns: 747 | # 0: started successfully 748 | # 1: failure 749 | 750 | start_service() 751 | { 752 | local service_name="${1}" 753 | 754 | [[ -z "${service_name}" ]] && return 0 755 | 756 | debug "Start service '${service_name}'." 757 | 758 | "${SERVICE_PATH}" "${service_name}" restart || return 1 759 | 760 | debug "Started successfully." 761 | 762 | return 0 763 | } 764 | 765 | 766 | # Creates directories from file path 767 | # Arguments: 768 | # 1: path to file 769 | # Returns: 770 | # 0: success 771 | # 1: failure 772 | 773 | make_path_for_file() 774 | { 775 | local file_path="${1}" 776 | local dir_path="" 777 | 778 | [[ -z "${file_path}" ]] && return 0 779 | 780 | dir_path="$("${DIRNAME_PATH}" "${file_path}")" || return 1 781 | 782 | if [[ ! -d "${dir_path}" ]] ; then 783 | 784 | debug "Create directory '${dir_path}'." 785 | 786 | mkdir -p "${dir_path}" || return 1 787 | fi 788 | 789 | return 0 790 | } 791 | 792 | 793 | # Copies file to the backup directory 794 | # Arguments: 795 | # 1: path to file 796 | # Returns: 797 | # 0: success 798 | # 1: failure 799 | 800 | backup_file() 801 | { 802 | local src_file="${1}" 803 | 804 | local dst_file="${BACKUP_DIR}/$(basename "${src_file}")" 805 | 806 | [[ -z "${src_file}" ]] && return 0 807 | [[ -f "${src_file}" ]] || return 0 808 | 809 | debug "Backup file '${src_file}' to '${dst_file}'." 810 | 811 | make_path_for_file "${dst_file}" || return 1 812 | cp --backup=numbered "${src_file}" "${dst_file}" || return 1 813 | 814 | return 0 815 | } 816 | 817 | 818 | # Writes a text data to the file and makes backup of file before that 819 | # Arguments: 820 | # 1: text to write 821 | # 2: path to file 822 | # Returns: 823 | # 0: success 824 | # 1: failure 825 | 826 | write_to_file() 827 | { 828 | local text="${1}" 829 | local dst_file="${2}" 830 | 831 | [[ -z "${text}" ]] && return 0 832 | [[ -z "${dst_file}" ]] && return 0 833 | 834 | local lines_count="$(print_lines_count "${text}")" 835 | 836 | backup_file "${dst_file}" || return 1 837 | make_path_for_file "${dst_file}" || return 1 838 | 839 | debug "Write data to the file '${dst_file}'." 840 | 841 | echo "${text}" > "${dst_file}" 842 | 843 | debug "Wrote ${lines_count} lines successfully." 844 | 845 | return 0 846 | } 847 | 848 | 849 | # Prints name of the domain 850 | # Arguments: 851 | # None 852 | # Returns: 853 | # 0: success 854 | # 1: failure 855 | 856 | print_domain_name() 857 | { 858 | local domain_name="" 859 | 860 | domain_name="$(dnsdomainname)" || return 1 861 | 862 | if [[ -z "${domain_name}" ]] ; then 863 | log "${E_DOMAIN_NAME_NOT_FOUND}" 864 | return 1 865 | fi 866 | 867 | debug "Found domain '${domain_name}'." 868 | echo "${domain_name}" 869 | 870 | return 0 871 | } 872 | 873 | 874 | # Prints name of the current host as FQDN 875 | # Arguments: 876 | # 1: domain name 877 | # Returns: 878 | # 0: success 879 | # 1: failure 880 | 881 | print_host_fqdn() 882 | { 883 | local domain_name="${1}" 884 | 885 | local host_name="" 886 | 887 | [[ -z "${domain_name}" ]] && return 0 888 | 889 | host_name="$(hostname --fqdn)" || return 1 890 | 891 | if [[ ! "${host_name}" == *.${domain_name} ]] ; then 892 | host_name="${host_name}.${domain_name}" 893 | fi 894 | 895 | debug "Found host FQDN '${host_name}'." 896 | echo "${host_name}" 897 | 898 | return 0 899 | } 900 | 901 | 902 | # Installs dnsutils package 903 | # Arguments: 904 | # None 905 | # Returns: 906 | # 0: success 907 | # 1: failure 908 | 909 | install_dnsutils() 910 | { 911 | test_package "dnsutils" || install_package "dnsutils" || return 1 912 | test_app "${DIG_PATH}" || return 1 913 | 914 | return 0 915 | } 916 | 917 | 918 | # Resolves DNS name to ip address 919 | # Arguments: 920 | # 1: name to resolve 921 | # 2: DNS record type, A by default 922 | # 3: DNS server that will be used for resolve, empty by default. 923 | # system nameservers from /etc/resolv.conf will be used if empty. 924 | # Returns: 925 | # 0: success 926 | # 1: failure 927 | 928 | lookup_hostname() 929 | { 930 | local name="${1}" 931 | local type="${2:-A}" 932 | local server="${3}" 933 | 934 | local output="" 935 | 936 | [[ -z "${name}" ]] && return 0 937 | 938 | if [[ -z "${server}" ]] ; then 939 | 940 | if [[ "${type}" == "A" ]] ; then 941 | 942 | if test_app "${DIG_PATH}" &>/dev/null ; then 943 | 944 | debug "Lookup DNS record '${name}'." 945 | output="$("${DIG_PATH}" "${name}" "${type}" +short +search +tries=${DIG_TRIES} +time=${DIG_TIMEOUT})" || return 1 946 | 947 | else 948 | 949 | debug "Lookup DNS record '${name}'." 950 | output="$("${GETENT_PATH}" hosts "${name}" | "${AWK_PATH}" '{ print $1 }')" 951 | 952 | fi 953 | 954 | else 955 | 956 | debug "Lookup DNS record '${name}', type ${type}." 957 | output="$("${DIG_PATH}" "${name}" "${type}" +short +search +tries=${DIG_TRIES} +time=${DIG_TIMEOUT})" || return 1 958 | 959 | fi 960 | 961 | else 962 | 963 | debug "Lookup DNS record '${name}', type ${type} by the server '$server'." 964 | output="$("${DIG_PATH}" "@${server}" "${name}" "${type}" +short +search +tries=${DIG_TRIES} +time=${DIG_TIMEOUT})" || return 1 965 | 966 | fi 967 | 968 | if [[ -z "${output}" ]] ; then 969 | log "$(printf "${E_DIG_EMPTY_RESPONSE}" "${name}" "${type}")" 970 | return 1 971 | fi 972 | 973 | debug "Resolved successfully." 974 | echo "${output}" 975 | 976 | return 0 977 | } 978 | 979 | 980 | # Resolves ip address to DNS name 981 | # Arguments: 982 | # 1: ip address to resolve 983 | # Returns: 984 | # 0: success 985 | # 1: failure 986 | 987 | lookup_address() 988 | { 989 | local address="${1}" 990 | 991 | local dig_output="" 992 | 993 | [[ -z "${address}" ]] && return 0 994 | 995 | debug "Reverse lookup DNS record for address '${address}'." 996 | dig_output="$("${DIG_PATH}" -x "${address}" +short +tries=${DIG_TRIES} +time=${DIG_TIMEOUT})" || return 1 997 | 998 | if [[ -z "${dig_output}" ]] ; then 999 | log "$(printf "${E_DIG_EMPTY_RESPONSE}" "${address}" "PTR")" 1000 | return 1 1001 | fi 1002 | 1003 | dig_output="$(replace_string "${dig_output}" "\.$" "")" || return 1 1004 | 1005 | debug "Resolved successfully." 1006 | echo "${dig_output}" 1007 | 1008 | return 0 1009 | } 1010 | 1011 | 1012 | # Prints hostnames for the specified ip addresses 1013 | # Prints the ip address if it can not be resolved 1014 | # Arguments: 1015 | # 1: list of the ip addresses 1016 | # Returns: 1017 | # 0: success 1018 | # 1: failure 1019 | 1020 | print_hostname_or_address() 1021 | { 1022 | local address_list="${1}" 1023 | 1024 | local address="" 1025 | local hostname="" 1026 | 1027 | [[ -z "${address_list}" ]] && return 0 1028 | 1029 | while read address ; do 1030 | 1031 | hostname="$(lookup_address "${address}")" 1032 | if [[ -z "${hostname}" ]] ; then 1033 | echo "${address}" 1034 | else 1035 | echo "${hostname}" 1036 | fi 1037 | 1038 | done <<< "${address_list}" 1039 | 1040 | return 0 1041 | } 1042 | 1043 | 1044 | # Checks that TCP port is open and available 1045 | # Arguments: 1046 | # 1: server to connect 1047 | # 2: TCP port to connect 1048 | # 3: connection timeout, ${PORT_CONNECT_TIMEOUT} by default 1049 | # Returns: 1050 | # 0: TCP port is available 1051 | # 1: TCP port is not available 1052 | 1053 | test_port() 1054 | { 1055 | local server="${1}" 1056 | local port="${2}" 1057 | local timeout="${3:-${PORT_CONNECT_TIMEOUT}}" 1058 | 1059 | [[ -z "${server}" ]] && return 0 1060 | [[ -z "${port}" ]] && return 0 1061 | 1062 | local test_command="$(printf "${PORT_TEST_COMMAND}" "${server}" "${port}")" 1063 | 1064 | debug "Test port: '${server}:${port}'." 1065 | 1066 | if ! "${TIMEOUT_PATH}" "${timeout}" "bash" -c "${test_command}" ; then 1067 | log "$(printf "${E_PORT_UNAVAILABLE}" "${server}" "${port}")" 1068 | return 1 1069 | fi 1070 | 1071 | debug "Port is available." 1072 | 1073 | return 0 1074 | } 1075 | 1076 | 1077 | # Checks that DNS client on the current host is configured properly 1078 | # Arguments: 1079 | # 1: domain name 1080 | # Returns: 1081 | # 0: success 1082 | # 1: failure 1083 | 1084 | test_dns_settings() 1085 | { 1086 | local domain_name="${1}" 1087 | local dns_ldap_srv="$(printf "${DNS_LDAP_SRV_FORMAT}" "${domain_name}")" 1088 | 1089 | [[ -z "${domain_name}" ]] && return 0 1090 | 1091 | if ! lookup_hostname "${domain_name}" 1>/dev/null ; then 1092 | log "${E_DOMAIN_NAME_NOT_RESOLVED}" 1093 | return 1 1094 | fi 1095 | 1096 | if ! lookup_hostname "${dns_ldap_srv}" "SRV" 1>/dev/null ; then 1097 | log "$(printf "${E_LDAP_SRV_NOT_RESOLVED}" "${domain_name}")" 1098 | return 1 1099 | fi 1100 | 1101 | return 0 1102 | } 1103 | 1104 | 1105 | # Print ip addresses of AD domain controllers 1106 | # Arguments: 1107 | # 1: domain name 1108 | # Returns: 1109 | # 0: success 1110 | # 1: failure 1111 | 1112 | print_domain_controllers() 1113 | { 1114 | local domain_name="${1}" 1115 | 1116 | local server_list="" 1117 | local server="" 1118 | 1119 | [[ -z "${domain_name}" ]] && return 0 1120 | 1121 | server_list="$(lookup_hostname "${domain_name}")" || return 1 1122 | 1123 | if [[ -z "${server_list}" ]] ; then 1124 | 1125 | log "$(printf "${E_DOMAIN_CONTROLLER_NOT_FOUND}" "${domain_name}")" 1126 | return 1 1127 | 1128 | fi 1129 | 1130 | while read server ; do 1131 | debug "Found domain controller: '${server}'." 1132 | done <<< "${server_list}" 1133 | 1134 | echo "${server_list}" 1135 | 1136 | return 0 1137 | } 1138 | 1139 | 1140 | # Installs dnsmasq package 1141 | # Arguments: 1142 | # None 1143 | # Returns: 1144 | # 0: success 1145 | # 1: failure 1146 | 1147 | install_dnsmasq() 1148 | { 1149 | test_package "dnsmasq" || install_package "dnsmasq" || return 1 1150 | test_app "${DNSMASQ_PATH}" || return 1 1151 | 1152 | return 0 1153 | } 1154 | 1155 | 1156 | # Prints contents of the dnsmasq configuration file 1157 | # Arguments: 1158 | # None 1159 | # Returns: 1160 | # 0: success 1161 | # 1: failure 1162 | 1163 | print_dnsmasq_config() 1164 | { 1165 | echo "listen-address=127.0.0.1" 1166 | echo "bind-interfaces" 1167 | echo "no-poll" 1168 | echo "no-negcache" 1169 | echo "cache-size=1000" 1170 | echo "dns-forward-max=150" 1171 | echo "domain-needed" 1172 | echo "resolv-file=${DNSMASQ_RESOLV_FILE}" 1173 | echo "all-servers" 1174 | 1175 | return 0 1176 | } 1177 | 1178 | 1179 | # Prints contents of the dnsmasq resolv.conf file 1180 | # Arguments: 1181 | # 1: list of the DNS servers 1182 | # Returns: 1183 | # 0: success 1184 | # 1: failure 1185 | 1186 | print_dnsmasq_resolv_config() 1187 | { 1188 | local server_list="${1}" 1189 | 1190 | [[ -z "${server_list}" ]] && return 0 1191 | 1192 | while read server ; do 1193 | debug "Add DNS server: '${server}'." 1194 | echo "nameserver ${server}" 1195 | done <<< "${server_list}" 1196 | 1197 | return 0 1198 | } 1199 | 1200 | 1201 | # Prints contents of the system resolv.conf file that points to the dnsmasq 1202 | # Arguments: 1203 | # 1: domain name 1204 | # Returns: 1205 | # 0: success 1206 | # 1: failure 1207 | 1208 | print_dnsmasq_system_resolv_config() 1209 | { 1210 | local domain_name="${1}" 1211 | 1212 | if [[ ! -z "${domain_name}" ]] ; then 1213 | echo "search ${domain_name}" 1214 | echo "domain ${domain_name}" 1215 | fi 1216 | 1217 | echo "nameserver 127.0.0.1" 1218 | 1219 | return 0 1220 | } 1221 | 1222 | 1223 | # Checks that specified DNS server is available 1224 | # Arguments: 1225 | # 1: DNS server 1226 | # 2: hostname to lookup 1227 | # Returns: 1228 | # 0: DNS server is available 1229 | # 1: DNS server is not available 1230 | 1231 | test_dns_server() 1232 | { 1233 | local server="${1}" 1234 | local lookup_name="${2}" 1235 | 1236 | [[ -z "${server}" ]] && return 0 1237 | [[ -z "${lookup_name}" ]] && return 0 1238 | 1239 | debug "Test DNS server: '${server}'." 1240 | 1241 | if ! lookup_hostname "${lookup_name}" "A" "${server}" 1>/dev/null ; then 1242 | log "$(printf "${E_DNS_SERVER_UNAVAILABLE}" "${server}")" 1243 | return 1 1244 | fi 1245 | 1246 | debug "DNS server available." 1247 | 1248 | return 0 1249 | } 1250 | 1251 | 1252 | # Prints only available DNS servers from specified list 1253 | # Arguments: 1254 | # 1: list of the DNS servers 1255 | # 2: hostname to lookup 1256 | # Returns: 1257 | # 0: success 1258 | # 1: there are no available DNS servers 1259 | 1260 | print_dns_server() 1261 | { 1262 | local server_list="${1}" 1263 | local lookup_name="${2}" 1264 | 1265 | local error_flag=1 1266 | 1267 | [[ -z "${server_list}" ]] && return 0 1268 | [[ -z "${lookup_name}" ]] && return 0 1269 | 1270 | while read server ; do 1271 | if test_dns_server "${server}" "${lookup_name}" ; then 1272 | echo "${server}" 1273 | error_flag=0 1274 | fi 1275 | done <<< "${server_list}" 1276 | 1277 | if is_true "${error_flag}" ; then 1278 | log "${E_DNS_SERVER_NO_AVAILABLE}" 1279 | return 1 1280 | fi 1281 | 1282 | return 0 1283 | } 1284 | 1285 | 1286 | # Prints ip address of the current host 1287 | # Arguments: 1288 | # 1: hostname of the current host 1289 | # 2: list of the DNS servers 1290 | # Returns: 1291 | # 0: success 1292 | # 1: failure 1293 | 1294 | print_host_address() 1295 | { 1296 | local host_name="${1}" 1297 | local server_list="${2}" 1298 | 1299 | local host_address="" 1300 | 1301 | [[ -z "${host_name}" ]] && return 0 1302 | [[ -z "${server_list}" ]] && return 0 1303 | 1304 | debug "Determine the IP address of the host." 1305 | 1306 | host_address="$(hostname --all-ip-addresses)" 1307 | 1308 | if [[ -z "${host_address}" ]] ; then 1309 | 1310 | debug "Lookup the IP address of the host by DNS." 1311 | 1312 | while read server ; do 1313 | host_address="$(lookup_hostname "${host_name}" "A" "${server}")" && break 1314 | done <<< "${server_list}" 1315 | 1316 | fi 1317 | 1318 | if [[ -z "${host_address}" ]] ; then 1319 | log "${E_HOST_ADDRESS_NOT_FOUND}" 1320 | return 1 1321 | fi 1322 | 1323 | host_address="$(first_line "${host_address}")" 1324 | 1325 | debug "Found host IP address '${host_address}'." 1326 | echo "${host_address}" 1327 | 1328 | return 0 1329 | } 1330 | 1331 | 1332 | # Prints contents of the system hosts file 1333 | # Gets contents of the system hosts file and remove line with hostname 1334 | # Arguments: 1335 | # 1: path to the hosts file 1336 | # 2: short hostname of the current host 1337 | # 3: FQDN of the current host 1338 | # 4: ip address of the current host 1339 | # Returns: 1340 | # 0: success 1341 | # 1: failure 1342 | 1343 | print_hosts_config() 1344 | { 1345 | local hosts_file="${1}" 1346 | local host_name="${2}" 1347 | 1348 | local custom_lines="" 1349 | 1350 | [[ -z "${hosts_file}" ]] && return 0 1351 | [[ -z "${host_name}" ]] && return 0 1352 | 1353 | grep --invert-match --ignore-case "${host_name}" "${hosts_file}" 1354 | 1355 | return 0 1356 | } 1357 | 1358 | 1359 | # Configures dnsmasq as the local DNS cache 1360 | # Arguments: 1361 | # 1: domain name 1362 | # 2: list of the DNS servers 1363 | # Returns: 1364 | # 0: success 1365 | # 1: failure 1366 | 1367 | configure_dnsmasq() 1368 | { 1369 | local domain_name="${1}" 1370 | local server_list="${2}" 1371 | 1372 | local host_name="" 1373 | local host_fqdn="" 1374 | local host_address="" 1375 | 1376 | [[ -z "${server_list}" ]] && return 0 1377 | [[ -z "${domain_name}" ]] && return 0 1378 | 1379 | debug "Configure local DNS cache." 1380 | 1381 | server_list="$(print_dns_server "${server_list}" "${domain_name}")" || return 1 1382 | 1383 | write_to_file "$(print_dnsmasq_resolv_config "${server_list}")" "${DNSMASQ_RESOLV_FILE}" || return 1 1384 | write_to_file "$(print_dnsmasq_config)" "${DNSMASQ_CONFIG_FILE}" || return 1 1385 | write_to_file "$(print_dnsmasq_system_resolv_config "${domain_name}")" "${DNSMASQ_SYSTEM_RESOLV_FILE}" || return 1 1386 | 1387 | host_name="$(hostname)" || return 1 1388 | 1389 | write_to_file \ 1390 | "$(print_hosts_config "${DNSMASQ_HOSTS_FILE}" "${host_name}")" "${DNSMASQ_HOSTS_FILE}" || return 1 1391 | 1392 | stop_service "${DNSMASQ_SERVICE_NAME}" && start_service "${DNSMASQ_SERVICE_NAME}" || return 1 1393 | 1394 | debug "Local DNS cache configured successfully." 1395 | 1396 | return 0 1397 | } 1398 | 1399 | 1400 | # Installs ntp and ntpdate packages 1401 | # Arguments: 1402 | # None 1403 | # Returns: 1404 | # 0: success 1405 | # 1: failure 1406 | 1407 | install_ntp() 1408 | { 1409 | test_package "ntpdate" "ntp" || install_package "ntpdate" "ntp" || return 1 1410 | test_app "${NTPDATE_PATH}" || return 1 1411 | test_app "${NTPD_PATH}" || return 1 1412 | 1413 | return 0 1414 | } 1415 | 1416 | 1417 | # Prints contents of the ntpd configuration file 1418 | # Arguments: 1419 | # 1: list of the NTP servers 1420 | # Returns: 1421 | # 0: success 1422 | # 1: failure 1423 | 1424 | print_ntp_config() 1425 | { 1426 | local server_list="${1}" 1427 | 1428 | echo "driftfile /var/lib/ntp/ntp.drift" 1429 | echo "statistics loopstats peerstats clockstats" 1430 | echo "filegen loopstats file loopstats type day enable" 1431 | echo "filegen peerstats file peerstats type day enable" 1432 | echo "filegen clockstats file clockstats type day enable" 1433 | 1434 | if [[ ! -z "${server_list}" ]] ; then 1435 | while read server ; do 1436 | 1437 | server="$(print_hostname_or_address "${server}")" 1438 | 1439 | debug "Add NTP server: ${server}." 1440 | 1441 | echo "server ${server}" 1442 | 1443 | done <<< "${server_list}" 1444 | fi 1445 | 1446 | echo "restrict -4 default notrap nomodify nopeer noquery" 1447 | echo "restrict -6 default notrap nomodify nopeer noquery" 1448 | echo "restrict 127.0.0.1" 1449 | echo "restrict ::1" 1450 | # echo "restrict source notrap nomodify noquery" 1451 | 1452 | return 0 1453 | } 1454 | 1455 | 1456 | # Checks that NTP server is available 1457 | # Arguments: 1458 | # 1: NTP server 1459 | # Returns: 1460 | # 0: NTP server is available 1461 | # 1: NTP server is not available 1462 | 1463 | test_ntp_server() 1464 | { 1465 | local server="${1}" 1466 | 1467 | [[ -z "${server}" ]] && return 0 1468 | 1469 | debug "Test NTP server: '${server}'." 1470 | 1471 | if ! "${NTPDATE_PATH}" -p1 -q "${server}" 1>/dev/null ; then 1472 | log "$(printf "${E_NTP_SERVER_UNAVAILABLE}" "${server}")" 1473 | return 1 1474 | fi 1475 | 1476 | debug "NTP server available." 1477 | 1478 | return 0 1479 | 1480 | } 1481 | 1482 | 1483 | # Prints only available NTP servers from specified list 1484 | # Arguments: 1485 | # 1: list of the NTP servers 1486 | # Returns: 1487 | # 0: success 1488 | # 1: there are no available NTP servers 1489 | 1490 | print_ntp_server() 1491 | { 1492 | local server_list="${1}" 1493 | 1494 | local error_flag=1 1495 | 1496 | [[ -z "${server_list}" ]] && return 0 1497 | 1498 | while read server ; do 1499 | if test_ntp_server "${server}" ; then 1500 | echo "${server}" 1501 | error_flag=0 1502 | fi 1503 | done <<< "${server_list}" 1504 | 1505 | if is_true "${error_flag}" ; then 1506 | log "${E_NTP_SERVER_NO_AVAILABLE}" 1507 | return 1 1508 | fi 1509 | 1510 | return 0 1511 | } 1512 | 1513 | 1514 | # Forces time sync with NTP servers 1515 | # Arguments: 1516 | # None 1517 | # Returns: 1518 | # 0: success 1519 | # 1: failure 1520 | 1521 | sync_ntp_time() 1522 | { 1523 | debug "Synchronize time. This can take a while." 1524 | 1525 | if ! "${NTPD_PATH}" -g -l "${NTP_TEMP_LOG}" -q ; then 1526 | log "${E_NTP_SYNC_FAILED}" 1527 | return 1 1528 | fi 1529 | 1530 | debug "Time synchronized successfully." 1531 | 1532 | return 0 1533 | } 1534 | 1535 | 1536 | # Configures ntpd and sync time 1537 | # Arguments: 1538 | # 1: list of the NTP servers 1539 | # Returns: 1540 | # 0: success 1541 | # 1: failure 1542 | 1543 | configure_ntp() 1544 | { 1545 | local server_list="${1}" 1546 | 1547 | [[ -z "${server_list}" ]] && return 0 1548 | 1549 | debug "Configure NTP client." 1550 | 1551 | stop_service "${NTP_SERVICE_NAME}" || return 1 1552 | 1553 | server_list="$(print_ntp_server "${server_list}")" || return 1 1554 | write_to_file "$(print_ntp_config "${server_list}")" "${NTP_CONFIG_FILE}" || return 1 1555 | 1556 | sync_ntp_time || return 1 1557 | 1558 | start_service "${NTP_SERVICE_NAME}" || return 1 1559 | 1560 | debug "NTP client configured successfully." 1561 | 1562 | return 0 1563 | } 1564 | 1565 | 1566 | # Installs kerberos package 1567 | # Arguments: 1568 | # None 1569 | # Returns: 1570 | # 0: success 1571 | # 1: failure 1572 | 1573 | install_kerberos() 1574 | { 1575 | test_package "krb5-user" || install_package "krb5-user" || return 1 1576 | test_app "${KINIT_PATH}" "${KLIST_PATH}" "${KDESTROY_PATH}" || return 1 1577 | 1578 | return 0 1579 | } 1580 | 1581 | 1582 | # Prints kerberos realm name from the domain name 1583 | # Arguments: 1584 | # 1: domain name 1585 | # Returns: 1586 | # 0: success 1587 | # 1: failure 1588 | 1589 | print_realm_name() 1590 | { 1591 | local domain_name="${1}" 1592 | 1593 | local realm_name="" 1594 | 1595 | realm_name="$(print_uppercase "${domain_name}")" || return 1 1596 | 1597 | debug "Found realm name: '${realm_name}'." 1598 | 1599 | echo "${realm_name}" 1600 | 1601 | return 0 1602 | } 1603 | 1604 | 1605 | # Prints only available by TCP kerberos servers from specified list 1606 | # Arguments: 1607 | # 1: list of the KDC servers 1608 | # Returns: 1609 | # 0: success 1610 | # 1: there are no available KDC servers 1611 | 1612 | print_kerberos_server() 1613 | { 1614 | local server_list="${1}" 1615 | 1616 | local is_empty=1 1617 | 1618 | [[ -z "${server_list}" ]] && return 0 1619 | 1620 | while read server ; do 1621 | if test_port "${server}" "${KERBEROS_PORT}" ; then 1622 | echo "${server}" 1623 | is_empty=0 1624 | fi 1625 | done <<< "${server_list}" 1626 | 1627 | if is_true "${is_empty}" ; then 1628 | log "${E_KERBEROS_SERVER_NO_AVAILABLE}" 1629 | return 1 1630 | fi 1631 | 1632 | return 0 1633 | } 1634 | 1635 | 1636 | # Prints contents of the krb5 configuration file 1637 | # Arguments: 1638 | # 1: domain name 1639 | # 2: realm name 1640 | # 3: list of the KDC servers 1641 | # Returns: 1642 | # 0: success 1643 | # 1: failure 1644 | 1645 | print_kerberos_config() 1646 | { 1647 | local domain_name="${1}" 1648 | local realm_name="${2}" 1649 | local server_list="${3}" 1650 | 1651 | [[ -z "${domain_name}" ]] && return 0 1652 | [[ -z "${realm_name}" ]] && return 0 1653 | [[ -z "${server_list}" ]] && return 0 1654 | 1655 | local server="" 1656 | local is_first=1 1657 | local tab=" " 1658 | 1659 | echo "[libdefaults]" 1660 | echo "default_realm = ${realm_name}" 1661 | echo "dns_lookup_realm = true" 1662 | echo "dns_lookup_kdc = true" 1663 | echo "forwardable = true" 1664 | echo "ticket_lifetime = ${KERBEROS_TICKET_LIFETIME}" 1665 | echo "renew_lifetime = ${KERBEROS_RENEW_LIFETIME}" 1666 | echo "clockskew = ${KERBEROS_CLOCKSKEW}" 1667 | echo "" 1668 | echo "[realms]" 1669 | echo "${realm_name} = {" 1670 | 1671 | while read server ; do 1672 | 1673 | server="$(print_hostname_or_address "${server}")" 1674 | 1675 | if is_true "${is_first}" ; then 1676 | 1677 | debug "Add master kerberos server: '${server}'." 1678 | echo "${tab}admin_server = ${server}" 1679 | is_first=0 1680 | 1681 | fi 1682 | 1683 | debug "Add KDC server: '${server}'." 1684 | echo "${tab}kdc = ${server}" 1685 | 1686 | done <<< "${server_list}" 1687 | 1688 | echo "}" 1689 | echo "" 1690 | echo "[domain_realm]" 1691 | echo ".${domain_name} = ${realm_name}" 1692 | echo "${domain_name} = ${realm_name}" 1693 | 1694 | return 0 1695 | } 1696 | 1697 | 1698 | # Configures kerberos 1699 | # Arguments: 1700 | # 1: domain name 1701 | # 2: list of the KDC servers 1702 | # Returns: 1703 | # 0: success 1704 | # 1: failure 1705 | 1706 | configure_kerberos() 1707 | { 1708 | local domain_name="${1}" 1709 | local server_list="${2}" 1710 | 1711 | local realm_name="" 1712 | 1713 | debug "Configure kerberos." 1714 | 1715 | realm_name="$(print_realm_name "${domain_name}")" || return 1 1716 | server_list="$(print_kerberos_server "${server_list}")" || return 1 1717 | 1718 | write_to_file "$(print_kerberos_config "${domain_name}" "${realm_name}" "${server_list}")" \ 1719 | "${KERBEROS_CONFIG_FILE}" || return 1 1720 | 1721 | debug "Kerberos configured successfully." 1722 | 1723 | return 0 1724 | } 1725 | 1726 | 1727 | # Removes all cached kerberos tickets 1728 | # Arguments: 1729 | # None 1730 | # Returns: 1731 | # 0: success 1732 | # 1: failure 1733 | 1734 | clear_kerberos_ticket() 1735 | { 1736 | debug "Clear Kerberos tickets." 1737 | 1738 | "${KDESTROY_PATH}" -Aq || return 1 1739 | 1740 | debug "Cleared successfully." 1741 | } 1742 | 1743 | 1744 | # Gets the kerberos ticket for specified user and realm 1745 | # Arguments: 1746 | # 1: realm name 1747 | # 2: user name 1748 | # Returns: 1749 | # 0: success 1750 | # 1: failure 1751 | 1752 | init_kerberos_ticket() 1753 | { 1754 | local realm_name="${1}" 1755 | local user_name="${2}" 1756 | 1757 | [[ -z "${realm_name}" ]] && return 0 1758 | [[ -z "${user_name}" ]] && return 0 1759 | 1760 | local principal="${user_name}@${realm_name}" 1761 | 1762 | debug "Get Kerberos ticket for the principal '${principal}'." 1763 | 1764 | "${KINIT_PATH}" -V "${principal}" 1>&2 || return 1 1765 | "${KLIST_PATH}" || return 1 1766 | 1767 | debug "Ticket received successfully." 1768 | 1769 | return 0 1770 | } 1771 | 1772 | 1773 | # Prints user name 1774 | # Asks user for input if user name is empty 1775 | # Arguments: 1776 | # 1: user name 1777 | # Returns: 1778 | # 0: success 1779 | # 1: failure 1780 | 1781 | print_user_name() 1782 | { 1783 | local user_name="${1}" 1784 | 1785 | if [[ -z "${user_name}" ]] ; then 1786 | 1787 | read -p "${USER_NAME_PROMPT}" user_name || return 1 1788 | 1789 | if [[ -z "${user_name}" ]] ; then 1790 | log "${E_USER_NAME_NOT_SPECIFIED}" 1791 | return 1 1792 | fi 1793 | 1794 | fi 1795 | 1796 | echo "${user_name}" 1797 | 1798 | return 0 1799 | } 1800 | 1801 | 1802 | # Gets the kerberos ticket for specified user 1803 | # Asks user for input if user name is empty 1804 | # Arguments: 1805 | # 1: domain name 1806 | # 2: user name 1807 | # Returns: 1808 | # 0: success 1809 | # 1: failure 1810 | 1811 | init_user_name() 1812 | { 1813 | local domain_name="${1}" 1814 | local user_name="${2}" 1815 | 1816 | local realm_name="" 1817 | 1818 | [[ -z "${domain_name}" ]] && return 0 1819 | 1820 | realm_name="$(print_realm_name "${domain_name}")" || return 1 1821 | 1822 | while : ; do 1823 | 1824 | user_name="$(print_user_name "${user_name}")" || return 1 1825 | 1826 | if init_kerberos_ticket "${realm_name}" "${user_name}" ; then 1827 | break 1828 | else 1829 | clear_kerberos_ticket 1830 | user_name="" 1831 | fi 1832 | done 1833 | 1834 | echo "${user_name}" 1835 | 1836 | return 0 1837 | } 1838 | 1839 | 1840 | # Installs realmd package 1841 | # Arguments: 1842 | # None 1843 | # Returns: 1844 | # 0: success 1845 | # 1: failure 1846 | 1847 | install_realm() 1848 | { 1849 | test_package "realmd" "policykit-1" "packagekit" || \ 1850 | install_package "realmd" "policykit-1" "packagekit" || return 1 1851 | 1852 | test_app "${REALM_PATH}" || return 1 1853 | 1854 | return 0 1855 | } 1856 | 1857 | 1858 | # Installs sssd package 1859 | # Arguments: 1860 | # None 1861 | # Returns: 1862 | # 0: success 1863 | # 1: failure 1864 | 1865 | install_sssd() 1866 | { 1867 | test_package "sssd-tools" "sssd" "libnss-sss" "libpam-sss" "adcli" "samba-common-bin" || \ 1868 | install_package "sssd-tools" "sssd" "libnss-sss" "libpam-sss" "adcli" "samba-common-bin" || \ 1869 | return 1 1870 | 1871 | return 0 1872 | } 1873 | 1874 | 1875 | # Gets domain information for the specified realm server 1876 | # Arguments: 1877 | # 1: hostname or ip address of the server 1878 | # Returns: 1879 | # 0: success 1880 | # 1: failure 1881 | 1882 | discover_realm() 1883 | { 1884 | local realm_name="${1}" 1885 | 1886 | [[ -z "${realm_name}" ]] && return 0 1887 | 1888 | debug "Discover realm: '${realm_name}'." 1889 | 1890 | if ! "${REALM_PATH}" discover "${realm_name}" --verbose 1>&2 ; then 1891 | log "$(printf "${E_REALM_UNAVAILABLE}" "${realm_name}")" 1892 | return 1 1893 | fi 1894 | 1895 | debug "Realm is available." 1896 | 1897 | return 0 1898 | } 1899 | 1900 | 1901 | # Prints only available LDAP servers from specified list 1902 | # Arguments: 1903 | # 1: list of the LDAP servers 1904 | # Returns: 1905 | # 0: success 1906 | # 1: there are no available LDAP servers 1907 | 1908 | print_ldap_server() 1909 | { 1910 | local server_list="${1}" 1911 | 1912 | local is_empty=1 1913 | 1914 | [[ -z "${server_list}" ]] && return 0 1915 | 1916 | while read server ; do 1917 | if test_port "${server}" "${LDAP_PORT}" && discover_realm "${server}" ; then 1918 | echo "${server}" 1919 | is_empty=0 1920 | fi 1921 | done <<< "${server_list}" 1922 | 1923 | if is_true "${is_empty}" ; then 1924 | log "${E_LDAP_SERVER_NO_AVAILABLE}" 1925 | return 1 1926 | fi 1927 | 1928 | return 0 1929 | } 1930 | 1931 | 1932 | # Prints information about operation system 1933 | # Arguments: 1934 | # 1: parameter name, see contents of the ${OS_RELEASE_FILE} file 1935 | # Returns: 1936 | # 0: success 1937 | # 1: failure 1938 | 1939 | print_os_info() 1940 | { 1941 | local info_name="${1}" 1942 | 1943 | local info_value="" 1944 | 1945 | [[ -z "${info_name}" ]] && return 1 1946 | info_name="$(print_uppercase "${info_name}")" || return 1 1947 | 1948 | [[ -f "${OS_RELEASE_FILE}" ]] || return 1 1949 | info_value="$(bash -c ". \"${OS_RELEASE_FILE}\" && echo \"\${${info_name}}\"")" || return 1 1950 | [[ -z "${info_value}" ]] && return 1 1951 | 1952 | echo "${info_value}" 1953 | 1954 | return 0 1955 | } 1956 | 1957 | 1958 | # Prints contents of the realmd configuration file 1959 | # Arguments: 1960 | # 1: os name 1961 | # 2: os version 1962 | # Returns: 1963 | # 0: success 1964 | # 1: failure 1965 | 1966 | print_realmd_config() 1967 | { 1968 | local os_name="${1}" 1969 | local os_version="${2}" 1970 | 1971 | [[ -z "${os_name}" ]] && [[ -z "${os_version}" ]] && return 0 1972 | 1973 | echo "[active-directory]" 1974 | 1975 | [[ -z "${os_name}" ]] || echo "os-name = ${os_name}" 1976 | [[ -z "${os_version}" ]] || echo "os-version = ${os_version}" 1977 | 1978 | return 0 1979 | } 1980 | 1981 | 1982 | # Joins host to the domain 1983 | # Arguments: 1984 | # 1: domain name 1985 | # 2: list of the domain controllers 1986 | # Returns: 1987 | # 0: success 1988 | # 1: failure 1989 | 1990 | join_realm() 1991 | { 1992 | local domain_name="${1}" 1993 | local server_list="${2}" 1994 | 1995 | [[ -z "${domain_name}" ]] && return 0 1996 | [[ -z "${server_list}" ]] && return 0 1997 | 1998 | local server="" 1999 | local os_name="" 2000 | local os_version="" 2001 | local current_domain_name="" 2002 | local host_name_short="" 2003 | local host_name_fqdn="" 2004 | local is_joined=0 2005 | 2006 | current_domain_name="$("${REALM_PATH}" list --name-only)" 2007 | 2008 | if [[ ! -z "${current_domain_name}" ]] ; then 2009 | log "$(printf "${E_REALM_ALREADY_JOINED}" "${current_domain_name}")" 2010 | if [[ "${domain_name}" == "${current_domain_name}" ]] ; then 2011 | return 0 2012 | else 2013 | return 1 2014 | fi 2015 | fi 2016 | 2017 | os_name="$(print_os_info "name")" || return 1 2018 | os_version="$(print_os_info "version_id")" || return 1 2019 | write_to_file "$(print_realmd_config "${os_name}" "${os_version}")" "${REALMD_CONFIG_FILE}" || return 1 2020 | 2021 | host_name_short="$(hostname)" || return 1 2022 | host_name_fqdn="$(print_host_fqdn "${domain_name}")" || return 1 2023 | 2024 | if [[ "${host_name_short}" != "${host_name_fqdn}" ]] ; then 2025 | 2026 | # < adcli bug description > 2027 | # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=858981 2028 | # https://bugs.freedesktop.org/show_bug.cgi?id=86107 2029 | 2030 | debug "Change hostname to FQDN: '${host_name_fqdn}'." 2031 | hostname "${host_name_fqdn}" || return 1 2032 | fi 2033 | 2034 | while read server ; do 2035 | 2036 | debug "Join to the domain '${domain_name}' by the server '${server}'..." 2037 | 2038 | "${REALM_PATH}" join \ 2039 | --verbose \ 2040 | --client-software="sssd" \ 2041 | --server-software="active-directory" \ 2042 | --membership-software="adcli" \ 2043 | "${server}" || continue 2044 | 2045 | is_joined=1 2046 | debug "Joined successfully." 2047 | break 2048 | 2049 | done <<< "${server_list}" 2050 | 2051 | if [[ "${host_name_short}" != "${host_name_fqdn}" ]] ; then 2052 | debug "Change hostname back: '${host_name_short}'." 2053 | hostname "${host_name_short}" || return 1 2054 | fi 2055 | 2056 | if ! is_true "${is_joined}" ; then 2057 | return 1 2058 | fi 2059 | 2060 | return 0 2061 | } 2062 | 2063 | 2064 | # Prints 'True' or 'False' for sssd configuration file 2065 | # Arguments: 2066 | # 1: flag value 2067 | # Returns: 2068 | # 0: success 2069 | # 1: failure 2070 | 2071 | print_sssd_bool() 2072 | { 2073 | local value="${1}" 2074 | 2075 | [[ -z "${value}" ]] && return 0 2076 | 2077 | if is_true "${value}" ; then 2078 | printf "%s" "True" 2079 | else 2080 | printf "%s" "False" 2081 | fi 2082 | 2083 | return 0 2084 | } 2085 | 2086 | 2087 | # Prints contents of the sssd configuration file 2088 | # Arguments: 2089 | # 1: domain name 2090 | # 2: list of the active directory servers 2091 | # Returns: 2092 | # 0: success 2093 | # 1: failure 2094 | 2095 | print_sssd_config() 2096 | { 2097 | local domain_name="${1}" 2098 | local address_list="${2}" 2099 | 2100 | [[ -z "${domain_name}" ]] && return 0 2101 | 2102 | local server_list="$(print_hostname_or_address "${address_list}")" 2103 | local backup_server_list="" 2104 | 2105 | if [[ -z "${server_list}" ]] ; then 2106 | server_list="${SSSD_DISCOVERY_SERVER}" 2107 | else 2108 | if is_true "${SSSD_AD_SERVER_DISCOVERY}" ; then 2109 | backup_server_list="${SSSD_DISCOVERY_SERVER}" 2110 | fi 2111 | fi 2112 | 2113 | echo "[sssd]" 2114 | echo "debug_level = ${SSSD_DEBUG_LEVEL}" 2115 | echo "domains = ${domain_name}" 2116 | echo "config_file_version = 2" 2117 | echo "services = nss, pam, sudo" 2118 | echo "" 2119 | echo "[nss]" 2120 | echo "debug_level = ${SSSD_DEBUG_LEVEL}" 2121 | echo "" 2122 | echo "[pam]" 2123 | echo "debug_level = ${SSSD_DEBUG_LEVEL}" 2124 | echo "pam_id_timeout = ${SSSD_PAM_ID_TIMEOUT}" 2125 | echo "" 2126 | echo "[domain/${domain_name}]" 2127 | echo "debug_level = ${SSSD_DEBUG_LEVEL}" 2128 | echo "ad_domain = ${domain_name}" 2129 | echo "ad_server = $(join_lines "${server_list}" ", ")" 2130 | echo "ad_backup_server = ${backup_server_list}" 2131 | echo "ad_hostname = $(print_host_fqdn "${domain_name}")" 2132 | echo "krb5_realm = $(print_realm_name "${domain_name}")" 2133 | echo "realmd_tags = manages-system joined-with-adcli" 2134 | echo "id_provider = ad" 2135 | echo "krb5_store_password_if_offline = True" 2136 | echo "default_shell = /bin/bash" 2137 | echo "ldap_id_mapping = True" 2138 | echo "fallback_homedir = /home/%d/%u" 2139 | echo "sudo_provider = none" 2140 | echo "use_fully_qualified_names = $(print_sssd_bool "${SSSD_USE_FQDN_NAMES}")" 2141 | echo "cache_credentials = $(print_sssd_bool "${SSSD_CACHE_CREDENTIALS}")" 2142 | echo "krb5_auth_timeout = ${SSSD_KRB5_AUTH_TIMEOUT}" 2143 | echo "ldap_opt_timeout = ${SSSD_LDAP_OPT_TIMEOUT}" 2144 | echo "access_provider = simple" 2145 | 2146 | return 0 2147 | } 2148 | 2149 | 2150 | # Removes all data from the sssd cache 2151 | # Arguments: 2152 | # None 2153 | # Returns: 2154 | # 0: success 2155 | # 1: failure 2156 | 2157 | clear_sssd_cache() 2158 | { 2159 | debug "Clear sssd cache." 2160 | 2161 | [[ ! -z "${SSSD_DB_DIR}" ]] && rm -vf "${SSSD_DB_DIR}/*" || return 1 2162 | [[ ! -z "${SSSD_CACHE_DIR}" ]] && rm -vf "${SSSD_CACHE_DIR}/*" || return 1 2163 | 2164 | debug "Cleared successfully." 2165 | 2166 | return 0 2167 | } 2168 | 2169 | 2170 | # Configures the sssd 2171 | # Arguments: 2172 | # 1: domain name 2173 | # 2: list of the active directory servers 2174 | # Returns: 2175 | # 0: success 2176 | # 1: failure 2177 | 2178 | configure_sssd() 2179 | { 2180 | local domain_name="${1}" 2181 | local server_list="${2}" 2182 | 2183 | [[ -z "${domain_name}" ]] && return 0 2184 | 2185 | debug "Configure sssd." 2186 | 2187 | write_to_file "$(print_sssd_config "${domain_name}" "${server_list}")" \ 2188 | "${SSSD_CONFIG_FILE}" || return 1 2189 | 2190 | chown 'root:root' "${SSSD_CONFIG_FILE}" || return 1 2191 | chmod '0600' "${SSSD_CONFIG_FILE}" || return 1 2192 | 2193 | stop_service "${SSSD_SERVICE_NAME}" || return 1 2194 | clear_sssd_cache || return 1 2195 | start_service "${SSSD_SERVICE_NAME}" || return 1 2196 | 2197 | debug "Sssd configured successfully." 2198 | 2199 | return 0 2200 | } 2201 | 2202 | 2203 | # Installs libpam-modules package 2204 | # Arguments: 2205 | # None 2206 | # Returns: 2207 | # 0: success 2208 | # 1: failure 2209 | 2210 | install_pam_modules() 2211 | { 2212 | test_package "libpam-modules" || install_package "libpam-modules" || return 1 2213 | 2214 | return 0 2215 | } 2216 | 2217 | 2218 | # Prints contents of the configuration file for the mkhomedir PAM module 2219 | # Arguments: 2220 | # None 2221 | # Returns: 2222 | # 0: success 2223 | # 1: failure 2224 | 2225 | print_pam_mkhomedir_config() 2226 | { 2227 | echo "Name: Activate mkhomedir" 2228 | echo "Default: yes" 2229 | echo "Priority: 900" 2230 | echo "Session-Type: Additional" 2231 | echo "Session:" 2232 | echo " required pam_mkhomedir.so umask=0022 skel=/etc/skel" 2233 | 2234 | return 0 2235 | } 2236 | 2237 | 2238 | # Configures the PAM mkhomedir module 2239 | # Arguments: 2240 | # None 2241 | # Returns: 2242 | # 0: success 2243 | # 1: failure 2244 | 2245 | configure_pam() 2246 | { 2247 | debug "Configure PAM." 2248 | 2249 | write_to_file "$(print_pam_mkhomedir_config)" "${PAM_MKHOMEDIR_CONFIG_FILE}" || return 1 2250 | 2251 | "${PAM_AUTH_UPDATE_PATH}" --force --package || return 1 2252 | 2253 | debug "PAM configured successfully." 2254 | 2255 | return 0 2256 | } 2257 | 2258 | 2259 | # Prints group name with domain sufffix 2260 | # Arguments: 2261 | # 1: group name 2262 | # 2: domain name 2263 | # Returns: 2264 | # 0: success 2265 | # 1: failure 2266 | 2267 | print_group_fqdn() 2268 | { 2269 | local group_name="${1}" 2270 | local domain_name="${2}" 2271 | 2272 | [[ -z "${group_name}" ]] && return 0 2273 | [[ -z "${domain_name}" ]] && return 0 2274 | 2275 | if [[ ! "${group_name}" == *@${domain_name} ]] ; then 2276 | group_name="${group_name}@${domain_name}" 2277 | fi 2278 | 2279 | echo "${group_name}" 2280 | 2281 | return 0 2282 | } 2283 | 2284 | 2285 | # Check that group exists 2286 | # Arguments: 2287 | # 1: group name 2288 | # Returns: 2289 | # 0: group exists 2290 | # 1: group does not exists 2291 | 2292 | test_group() 2293 | { 2294 | local group_name="${1}" 2295 | 2296 | local getent_output="" 2297 | 2298 | [[ -z "${group_name}" ]] && return 0 2299 | 2300 | debug "Check group: '${group_name}'." 2301 | 2302 | if ! getent_output="$("${GETENT_PATH}" group "${group_name}")" ; then 2303 | log "$(printf "${E_GROUP_NOT_FOUND}" "${group_name}")" 2304 | return 1 2305 | fi 2306 | 2307 | debug "Group exists: ${getent_output}." 2308 | 2309 | return 0 2310 | } 2311 | 2312 | 2313 | # Configures login permissions for groups specified by user 2314 | # Arguments: 2315 | # 1: domain name 2316 | # Returns: 2317 | # 0: success 2318 | # 1: failure 2319 | 2320 | configure_login_permissions() 2321 | { 2322 | local domain_name="${1}" 2323 | 2324 | local group_name="" 2325 | 2326 | [[ -z "${domain_name}" ]] && return 0 2327 | 2328 | debug "Configure login permissions." 2329 | 2330 | while : ; do 2331 | 2332 | read -p "${LOGIN_GROUP_PROMPT}" group_name || break 2333 | [[ -z "${group_name}" ]] && break 2334 | 2335 | group_name="$(print_lowercase "${group_name}")" || continue 2336 | group_name="$(print_group_fqdn "${group_name}" "${domain_name}")" || continue 2337 | test_group "${group_name}" || continue 2338 | 2339 | debug "Permit login for group: '${group_name}'." 2340 | "${REALM_PATH}" permit --groups "${group_name}" || continue 2341 | debug "Permitted successfully." 2342 | 2343 | done 2344 | 2345 | debug "Login permissions configured successfully." 2346 | 2347 | return 0 2348 | } 2349 | 2350 | 2351 | # Installs sudo package 2352 | # Arguments: 2353 | # None 2354 | # Returns: 2355 | # 0: success 2356 | # 1: failure 2357 | 2358 | install_sudo() 2359 | { 2360 | test_package "sudo" || install_package "sudo" || return 1 2361 | 2362 | return 0 2363 | } 2364 | 2365 | 2366 | # Configures sudo permissions for groups specified by user 2367 | # Arguments: 2368 | # 1: domain name 2369 | # Returns: 2370 | # 0: success 2371 | # 1: failure 2372 | 2373 | configure_sudo_permissions() 2374 | { 2375 | local domain_name="${1}" 2376 | 2377 | [[ -z "${domain_name}" ]] && return 0 2378 | 2379 | local sudo_config_file="${domain_name}" 2380 | local sudoers_lines="" 2381 | local line="" 2382 | 2383 | sudo_config_file="$(replace_chars "${sudo_config_file}" "." "_")" 2384 | sudo_config_file="${SUDO_CONFIG_DIR}/${sudo_config_file}" 2385 | 2386 | debug "Configure sudo permissions." 2387 | debug "Sudoers file: '${sudo_config_file}'." 2388 | 2389 | while : ; do 2390 | 2391 | read -p "${SUDO_GROUP_PROMPT}" group_name || break 2392 | [[ -z "${group_name}" ]] && break 2393 | 2394 | group_name="$(print_lowercase "${group_name}")" || continue 2395 | 2396 | if is_true "${SSSD_USE_FQDN_NAMES}" ; then 2397 | group_name="$(print_group_fqdn "${group_name}" "${domain_name}")" || continue 2398 | fi 2399 | 2400 | test_group "${group_name}" || continue 2401 | 2402 | line="$(replace_string "${group_name}" ' ' '\\ ')" 2403 | line="%${line} ALL=(ALL:ALL) ALL" 2404 | 2405 | debug "Add line to sudoers file: '${line}'." 2406 | 2407 | sudoers_lines="$(add_line "${sudoers_lines}" "${line}")" 2408 | 2409 | done 2410 | 2411 | write_to_file "${sudoers_lines}" "${sudo_config_file}" || return 1 2412 | 2413 | chown "root:root" "${sudo_config_file}" || return 1 2414 | chmod '0440' "${sudo_config_file}" || return 1 2415 | 2416 | stop_service "${SUDO_SERVICE_NAME}" && start_service "${SUDO_SERVICE_NAME}" || return 1 2417 | 2418 | debug "Sudo permissions configured successfully." 2419 | 2420 | return 0 2421 | } 2422 | 2423 | 2424 | # Installs openssh-server package 2425 | # Arguments: 2426 | # None 2427 | # Returns: 2428 | # 0: success 2429 | # 1: failure 2430 | 2431 | install_ssh_server() 2432 | { 2433 | test_package "openssh-server" || install_package "openssh-server" || return 1 2434 | 2435 | return 0 2436 | } 2437 | 2438 | 2439 | # Change value of the parameter in the ssh configuration file 2440 | # Arguments: 2441 | # 1: path to configuration file 2442 | # 2: name of the parameter 2443 | # 3: value of the parameter 2444 | # Returns: 2445 | # 0: success 2446 | # 1: failure 2447 | 2448 | set_sshd_config_parameter() 2449 | { 2450 | local file_name="${1}" 2451 | local param_name="${2}" 2452 | local param_value="${3}" 2453 | 2454 | [[ -z "${file_name}" ]] && return 0 2455 | [[ -z "${param_name}" ]] && return 0 2456 | [[ -z "${param_value}" ]] && return 0 2457 | 2458 | if [[ -f "${file_name}" ]] ; then 2459 | 2460 | if grep "${param_name} " "${file_name}" | grep -v "^#" &>/dev/null ; then 2461 | debug "Change the value of the existing parameter '${param_name}' to '${param_value}', file: '${file_name}'." 2462 | sed --in-place "/${param_name} .*/ {/^#/! s/${param_name} .*/${param_name} ${param_value}/}" "${file_name}" || return 1 2463 | return 0 2464 | fi 2465 | 2466 | if grep "${param_name} " "${file_name}" | grep "^#" &>/dev/null; then 2467 | debug "Uncomment and change the value of the parameter '${param_name}' to '${param_value}', file: '${file_name}'." 2468 | sed --in-place --regexp-extended "0,/^# *${param_name} / s/^# *${param_name} .*/${param_name} ${param_value}/" "${file_name}" || return 1 2469 | return 0 2470 | fi 2471 | 2472 | fi 2473 | 2474 | debug "Add new parameter '${param_name}' with value '${param_value}', file: '${file_name}'." 2475 | echo -en "\n${param_name} ${param_value}" >>"${file_name}" || return 1 2476 | 2477 | return 0 2478 | } 2479 | 2480 | 2481 | # Configures GSSAPI for sshd 2482 | # Arguments: 2483 | # None 2484 | # Returns: 2485 | # 0: success 2486 | # 1: failure 2487 | 2488 | configure_ssh_gssapi() 2489 | { 2490 | local os_name="$(print_os_info "name")" 2491 | local os_version="$(print_os_info "version_id")" 2492 | 2493 | [[ -f "${SSHD_CONFIG_FILE}" ]] || return 0 2494 | 2495 | debug "Configure SSH GSSAPI." 2496 | 2497 | debug "Copy file '${SSHD_CONFIG_FILE}' to '${SSHD_TMP_CONFIG_FILE}'." 2498 | cp "${SSHD_CONFIG_FILE}" "${SSHD_TMP_CONFIG_FILE}" || return 1 2499 | 2500 | set_sshd_config_parameter "${SSHD_TMP_CONFIG_FILE}" "GSSAPIAuthentication" "yes" || return 1 2501 | set_sshd_config_parameter "${SSHD_TMP_CONFIG_FILE}" "GSSAPICleanupCredentials" "yes" || return 1 2502 | 2503 | if [[ "${os_name}" == "Debian GNU/Linux" ]] && [[ "${os_version}" == 8* ]] ; then 2504 | 2505 | # < adcli bug description > 2506 | # https://bugs.freedesktop.org/show_bug.cgi?id=84749 2507 | # https://bugzilla.redhat.com/show_bug.cgi?id=1267319 2508 | set_sshd_config_parameter "${SSHD_TMP_CONFIG_FILE}" "GSSAPIStrictAcceptorCheck" "no" || return 1 2509 | 2510 | else 2511 | 2512 | # Reset to the default value 2513 | set_sshd_config_parameter "${SSHD_TMP_CONFIG_FILE}" "GSSAPIStrictAcceptorCheck" "yes" || return 1 2514 | 2515 | fi 2516 | 2517 | write_to_file "$(cat "${SSHD_TMP_CONFIG_FILE}")" "${SSHD_CONFIG_FILE}" || return 1 2518 | rm "${SSHD_TMP_CONFIG_FILE}" 2519 | 2520 | stop_service "${SSHD_SERVICE_NAME}" && start_service "${SSHD_SERVICE_NAME}" || return 1 2521 | 2522 | debug "SSH GSSAPI configured successfully." 2523 | 2524 | return 0 2525 | } 2526 | 2527 | 2528 | # Installs bash-completion package 2529 | # Arguments: 2530 | # None 2531 | # Returns: 2532 | # 0: success 2533 | # 1: failure 2534 | 2535 | install_bash_completion() 2536 | { 2537 | test_package "bash-completion" || install_package "bash-completion" || return 1 2538 | 2539 | return 0 2540 | } 2541 | 2542 | 2543 | # Configures auto completion for bash 2544 | # Enables auto completion for root's interactive shell 2545 | # Arguments: 2546 | # None 2547 | # Returns: 2548 | # 0: success 2549 | # 1: failure 2550 | 2551 | configure_bash_completion() 2552 | { 2553 | [[ -f "${BASH_SYSTEM_STARTUP_FILE}" ]] || return 0 2554 | 2555 | debug "Configure bash completion." 2556 | 2557 | debug "Copy file '${BASH_SYSTEM_STARTUP_FILE}' to '${BASH_TMP_SYSTEM_STARTUP_FILE}'." 2558 | 2559 | # tr '\n' '\r' join multiple lines at one. 2560 | # sed patterns use '\r' instead of '\n'. 2561 | # tr '\r' '\n' split lines as it was. 2562 | 2563 | cat "${BASH_SYSTEM_STARTUP_FILE}" | tr '\n' '\r' | \ 2564 | sed "s/${BASH_COMPLETION_FROM_PATTERN}/${BASH_COMPLETION_TO_PATTERN}/" | \ 2565 | tr '\r' '\n' > "${BASH_TMP_SYSTEM_STARTUP_FILE}" || return 1 2566 | 2567 | write_to_file "$(cat "${BASH_TMP_SYSTEM_STARTUP_FILE}")" "${BASH_SYSTEM_STARTUP_FILE}" || return 1 2568 | rm "${BASH_TMP_SYSTEM_STARTUP_FILE}" 2569 | 2570 | debug "Bash completion configured successfully." 2571 | 2572 | return 0 2573 | } 2574 | 2575 | 2576 | # Prints help information to the stdout. 2577 | # Arguments: 2578 | # None 2579 | # Returns: 2580 | # 0: success 2581 | 2582 | print_help() 2583 | { 2584 | echo "Usage:" 2585 | echo " ${PROGNAME} [-hq] [-s hostname] [-d domainname] [-u username]" 2586 | echo "" 2587 | echo "Configures the environment and joins the machine to the Active Directory domain." 2588 | echo "" 2589 | echo "Options:" 2590 | echo " -h Show this message." 2591 | echo " -q Suppress debug messages." 2592 | echo " -s hostname Specifies available domain controller. Can be specified multiple times." 2593 | echo " -d domainname Specifies domain name." 2594 | echo " -u username Specifies domain user name that will be used for joining to the domain." 2595 | 2596 | return 0 2597 | } 2598 | 2599 | 2600 | # Main function 2601 | 2602 | main() { 2603 | 2604 | local domain_name="${DEFAULT_DOMAIN_NAME}" 2605 | local server_list="${DEFAULT_SERVER_LIST}" 2606 | local user_name="${DEFAULT_USER_NAME}" 2607 | 2608 | local server="" 2609 | local ldap_server_list="" 2610 | 2611 | while getopts "hqs:d:u:" arg; do 2612 | case "${arg}" in 2613 | h) 2614 | print_help 2615 | exit 0 2616 | ;; 2617 | q) 2618 | DEBUG_ENABLED=0 2619 | ;; 2620 | d) 2621 | domain_name="${OPTARG}" 2622 | ;; 2623 | u) 2624 | user_name="${OPTARG}" 2625 | ;; 2626 | s) 2627 | server="$(lookup_hostname "${OPTARG}")" || exit 1 2628 | server_list="$(add_line "${server_list}" "${server}")" 2629 | ;; 2630 | *) 2631 | eexit "${E_ARGS_INVALID}" 2632 | ;; 2633 | esac 2634 | done 2635 | 2636 | test_root || exit 1 2637 | lock || exit 1 2638 | debug "The ${PROGNAME} script started." || exit 1 2639 | 2640 | if [[ -z "${domain_name}" ]] ; then 2641 | domain_name="$(print_domain_name)" || exit 1 2642 | fi 2643 | 2644 | install_dnsutils || exit 1 2645 | test_dns_settings "${domain_name}" || exit 1 2646 | 2647 | if [[ -z "${server_list}" ]] ; then 2648 | server_list="$(print_domain_controllers "${domain_name}")" || exit 1 2649 | fi 2650 | 2651 | install_dnsmasq && configure_dnsmasq "${domain_name}" "${server_list}" || exit 1 2652 | test_dns_settings "${domain_name}" || exit 1 2653 | 2654 | install_ntp || exit 1 2655 | configure_ntp "${server_list}" || exit 1 2656 | 2657 | install_kerberos && configure_kerberos "${domain_name}" "${server_list}" || exit 1 2658 | 2659 | user_name="$(init_user_name "${domain_name}" "${user_name}")" || exit 1 2660 | 2661 | install_realm && install_sssd || exit 1 2662 | ldap_server_list="$(print_ldap_server "${server_list}")" || exit 1 2663 | join_realm "${domain_name}" "${ldap_server_list}" || exit 1 2664 | 2665 | configure_sssd "${domain_name}" "${ldap_server_list}" || exit 1 2666 | 2667 | install_pam_modules && configure_pam || exit 1 2668 | 2669 | configure_login_permissions "${domain_name}" || exit 1 2670 | install_sudo && configure_sudo_permissions "${domain_name}" || exit 1 2671 | 2672 | install_ssh_server && configure_ssh_gssapi || exit 1 2673 | install_bash_completion && configure_bash_completion || exit 1 2674 | 2675 | debug "All configuration changes by ${PROGNAME} was finished successfully." 2676 | 2677 | exit 0 2678 | } 2679 | 2680 | main "${@}" --------------------------------------------------------------------------------