pax_global_header00006660000000000000000000000064146702453110014515gustar00rootroot0000000000000052 comment=893ef5c676c5f81ea947c8e3dc6c164ced8acbbd msktutil-1.2.2/000077500000000000000000000000001467024531100133735ustar00rootroot00000000000000msktutil-1.2.2/.github/000077500000000000000000000000001467024531100147335ustar00rootroot00000000000000msktutil-1.2.2/.github/dependabot.yml000066400000000000000000000002561467024531100175660ustar00rootroot00000000000000--- version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" time: "09:00" timezone: "Europe/Berlin" msktutil-1.2.2/.github/workflows/000077500000000000000000000000001467024531100167705ustar00rootroot00000000000000msktutil-1.2.2/.github/workflows/codeql-analysis.yml000066400000000000000000000052251467024531100226070ustar00rootroot00000000000000# For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ master ] pull_request: # The branches below must be a subset of the branches above branches: [ master ] schedule: - cron: '16 5 * * 2' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'cpp' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - name: Checkout repository uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Install msktutil build dependencies - name: Install dependencies shell: sh env: DEBIAN_FRONTEND: noninteractive run: | sudo apt-get update || true sudo apt-get install -q -y libldap2-dev libkrb5-dev libsasl2-dev # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 msktutil-1.2.2/.github/workflows/linters.yml000066400000000000000000000022151467024531100211730ustar00rootroot00000000000000name: linters on: push: branches: [ master ] pull_request: branches: [ master ] jobs: # see https://github.com/koalaman/shellcheck shellcheck: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install linters on ubuntu run: | sudo apt-get update -q -y sudo apt-get install shellcheck - name: run Shellcheck run: | shellcheck --version find . -name "*.sh" | xargs shellcheck # see https://github.com/danmar/cppcheck cppcheck: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install linters on ubuntu run: | sudo apt-get update -q -y sudo apt-get install cppcheck - name: run cppcheck run: | # cppcheck if ! [ -x "$(command -v cppcheck)" ]; then echo 'Error: cppcheck is not installed.' >&2 exit 1 fi CPPCHECK_OPTS='--error-exitcode=0 --force --quiet' echo "$(cppcheck --version):"; cppcheck $CPPCHECK_OPTS .; msktutil-1.2.2/.github/workflows/msktutil-archs.yml000066400000000000000000000043261467024531100224720ustar00rootroot00000000000000name: msktutil architectures on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest name: Build on ${{ matrix.distro }} ${{ matrix.arch }} with gcc # Run steps on a matrix of 4 arch/distro combinations strategy: fail-fast: false matrix: include: - arch: aarch64 distro: ubuntu22.04 - arch: ppc64le distro: ubuntu22.04 - arch: riscv64 distro: ubuntu22.04 - arch: s390x distro: ubuntu22.04 - arch: armv7 distro: ubuntu22.04 steps: - name: Checkout repository uses: actions/checkout@v4 - uses: uraimo/run-on-arch-action@v2 name: Build artifact id: build with: arch: ${{ matrix.arch }} distro: ${{ matrix.distro }} # Pass some environment variables to the container env: | CC: gcc CXX: g++ DEBIAN_FRONTEND: noninteractive # The shell to run commands with in the container shell: /bin/sh # Install some dependencies in the container. This speeds up builds if # you are also using githubToken. Any dependencies installed here will # be part of the container image that gets cached, so subsequent # builds don't have to re-install them. The image layer is cached # publicly in your project's package repository, so it is vital that # no secrets are present in the container state or logs. install: | case "${{ matrix.distro }}" in ubuntu*) cat /etc/debian_version apt-get update -q -y apt-get install -q -y gcc g++ gcc --version # basic packages apt-get install -q -y \ autoconf libtool make pkg-config # install dependencies apt-get install -q -y \ libldap2-dev libkrb5-dev libsasl2-dev ;; esac run: | ${CC} --version ./autogen.sh ./configure && make && ./msktutil --version msktutil-1.2.2/.github/workflows/msktutil-freebsd.yml000066400000000000000000000022641467024531100230030ustar00rootroot00000000000000name: msktutil FreeBSD on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest name: Build on FreeBSD strategy: fail-fast: false matrix: cfg: - { cc-version: gcc } - { cc-version: clang } steps: - name: Checkout repository uses: actions/checkout@v4 - name: Build msktutil with ${{ matrix.cfg.cc-version }} id: build uses: vmactions/freebsd-vm@v1 with: mem: 2048 usesh: true prepare: | freebsd-version # basic dependencies pkg install -y autotools autoconf cyrus-sasl-gssapi gmake openldap26-client run: | case "${{ matrix.cfg.cc-version }}" in gcc) pkg install -y gcc export CC=gcc export CXX=g++ ;; clang) export CC=clang export CXX=clang++ export CPPFLAGS=-I/usr/local/include export LDFLAGS=-L/usr/local/lib ;; esac ${CC} --version ./autogen.sh ./configure && make && ./msktutil --version msktutil-1.2.2/.github/workflows/msktutil-macos.yml000066400000000000000000000020011467024531100224600ustar00rootroot00000000000000name: msktutil macOS on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: name: Build on macOS runs-on: macos-latest strategy: fail-fast: false matrix: cfg: - { cc-version: gcc } - { cc-version: clang } steps: - name: Checkout repository uses: actions/checkout@v4 - name: Build msktutil ${{ matrix.cfg.cc-version }} shell: sh run: | # silence Apple deprecation notices brew install krb5 openldap case "${{ matrix.cfg.cc-version }}" in gcc) brew install gcc@12 export CC=gcc-12 export CXX=g++-12 ;; clang) export CC=clang export CXX=clang++ ;; esac ${CC} --version ./autogen.sh ./configure \ --with-ldapdir=/usr/local/opt/openldap \ --with-krb5-config=/usr/local/opt/krb5/bin/krb5-config && make && ./msktutil --version msktutil-1.2.2/.github/workflows/msktutil-solaris.yml000066400000000000000000000017771467024531100230550ustar00rootroot00000000000000name: msktutil Solaris on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest name: Build on Solaris strategy: fail-fast: false matrix: cfg: - { cc-version: gcc } steps: - name: Checkout repository uses: actions/checkout@v4 - name: Build msktutil with ${{ matrix.cfg.cc-version }} id: build uses: vmactions/solaris-vm@v1 with: release: "11.4-gcc" mem: 2048 usesh: true prepare: | # basic dependencies pkgutil -y -i autoconf sasl_gssapi gmake libkrb5_dev openldap_dev sasl_dev run: | case "${{ matrix.cfg.cc-version }}" in gcc) pkgutil -y -i gcc5g++ export PATH=/opt/csw/bin:$PATH export CC=gcc export CXX=g++ ;; esac ${CC} --version ./autogen.sh ./configure && make && ./msktutil --version msktutil-1.2.2/.github/workflows/msktutil.yml000066400000000000000000000114221467024531100213670ustar00rootroot00000000000000name: msktutil on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: name: Build on ${{ matrix.cfg.container }} - ${{ matrix.cfg.cc-version }} runs-on: ubuntu-latest container: ${{ matrix.cfg.container }} strategy: fail-fast: false matrix: cfg: - { container: 'ubuntu:20.04', cc-version: gcc } - { container: 'ubuntu:20.04', cc-version: clang } - { container: 'ubuntu:22.04', cc-version: gcc } - { container: 'ubuntu:22.04', cc-version: clang } - { container: 'debian:stable', cc-version: gcc } - { container: 'debian:stable', cc-version: clang } - { container: 'debian:sid', cc-version: gcc } - { container: 'debian:sid', cc-version: clang } - { container: 'quay.io/centos/centos:stream8', cc-version: gcc } - { container: 'quay.io/centos/centos:stream8', cc-version: clang } - { container: 'quay.io/centos/centos:stream9', cc-version: gcc } - { container: 'quay.io/centos/centos:stream9', cc-version: clang } - { container: 'fedora:latest', cc-version: gcc } - { container: 'fedora:latest', cc-version: clang } - { container: 'registry.access.redhat.com/ubi9/ubi:latest', cc-version: gcc } - { container: 'registry.access.redhat.com/ubi9/ubi:latest', cc-version: clang } steps: - name: Install compiler ${{ matrix.cfg.cc-version }} shell: sh env: DEBIAN_FRONTEND: noninteractive run: | case "${{ matrix.cfg.container }}" in ubuntu*|debian*) cat /etc/debian_version apt-get update -q -y apt-get install -q -y ${{ matrix.cfg.cc-version }} ${{ matrix.cfg.cc-version }} --version case "${{ matrix.cfg.cc-version }}" in gcc) apt-get install -q -y g++ ;; clang) apt-get install -q -y build-essential esac ;; fedora*) cat /etc/fedora-release dnf -y update dnf -y install ${{ matrix.cfg.cc-version }} ${{ matrix.cfg.cc-version }} --version ;; */centos:7) cat /etc/centos-release rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 yum -y update yum -y install ${{ matrix.cfg.cc-version }} ${{ matrix.cfg.cc-version }} --version ;; */centos:*) cat /etc/centos-release rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial dnf -y update dnf -y install ${{ matrix.cfg.cc-version }} ${{ matrix.cfg.cc-version }} --version ;; */ubi:*) cat /etc/redhat-release dnf -y install ${{ matrix.cfg.cc-version }} ${{ matrix.cfg.cc-version }} --version esac - name: Install msktutil dependencies ${{ matrix.cfg.cc-version }} shell: sh env: DEBIAN_FRONTEND: noninteractive run: | case "${{ matrix.cfg.container }}" in ubuntu*|debian*) # basic packages apt-get install -q -y \ autoconf libtool make pkg-config # install dependencies apt-get install -q -y \ libldap2-dev libkrb5-dev libsasl2-dev ;; fedora*) # basic packages dnf -y install \ autoconf automake gcc-c++ libtool make pkgconfig which redhat-rpm-config # install dependencies dnf -y install \ cyrus-sasl-devel krb5-devel openldap-devel ;; */centos:7) # basic packages yum -y install \ autoconf automake gcc-c++ libtool make pkgconfig which # install dependencies yum -y install \ cyrus-sasl-devel krb5-devel openldap-devel ;; */centos:*|*/ubi:*) # basic packages dnf -y install \ autoconf automake gcc-c++ libtool make pkgconfig which # install dependencies dnf -y install \ cyrus-sasl-devel krb5-devel openldap-devel ;; esac - name: Checkout repository uses: actions/checkout@v4 - name: Build msktutil with ${{ matrix.cfg.cc-version }} shell: sh env: DEBIAN_FRONTEND: noninteractive run: | case "${{ matrix.cfg.cc-version }}" in gcc) export CC=gcc export CXX=g++ ;; clang) export CC=clang export CXX=clang++ ;; esac ${CC} --version ./autogen.sh ./configure && make && ./msktutil --version msktutil-1.2.2/.gitignore000066400000000000000000000002001467024531100153530ustar00rootroot00000000000000*.o msktutil msktutil.1 msktutil-*.tar* *~ Makefile TAGS config.h config.h.in config.status config.log autom4te.cache configure msktutil-1.2.2/ChangeLog000066400000000000000000000176521467024531100151600ustar00rootroot00000000000000Release 1.2.2 - Improve CI - configure.ac: more robust krb5 autodetection - configure.ac: Replace obsoleted AC_CONFIG_HEADER with AC_CONFIG_HEADERS - configure.ac: fix linking with minimal LIBS - Use getnameinfo(3) instead of inet_ntop(3) - dns_lookup_kdc setting in create_fake_krb5_conf causes issue when using a trusted domain user to create computer account (#208) - Fix several messages - Style fixes Release 1.2.1 - Bugfix: correct AUTHORS section of manpage - Revert installation to $PREFIX/bin Release 1.2 - New co-maintainer Michael Osipov - Improvement: allow to delete and reset computer account - Improvement: prefer SASL mechanism GSS-SPNEGO over GSSAPI for LDAP connections to domain controllers (thanks, James Ralston!) - Improvement: allow custom script to be called for Samba callouts (thanks, Jarek Polok!) - Improvement: install executable to 'bin' rather than 'sbin' by default - Improvement: consistent qualification of default SPN entries - Improvement: query domain controller for proper salting information (feature available with MIT Kerberos 1.17 or later) - Improvement: consistent and improved log messages - Improvement: documentation updates - Bugfix: Ignore errors from Samba callout Release 1.1 - Improvement: Add paragraph regarding autogen.sh to INSTALL Release 1.1rc3: - Improvement: Adapt dist target to previous naming Release 1.1rc2: - Bugfix: various improvement in Makefile - Bugfix: Fix for Heimdal: Keyblock - Bugfix: Makefile.in: explicitly set permissions on install - Improvement: Silence warning from autotools Release 1.1rc1: - Bugfix: keytab entries generated with wrong salt - Bugfix: try_machine_keytab_princ is not called when keytab is not explicitly given via --keytab - Bugfix: failure to write keytab entries for more than one principal - Bugfix: duplicate entries when using service account - New Option --dont-update-dnshostname - Create service keytabs without changing the password - Delete account renamed to delete mode - Improved AD SRV lookups - Fixed compilation Warnings - Kerberos flavor incorrectly detected on FreeBSD Release 1.0: - Fixes for "#59 Kerberos flavor incorrectly detected on FreeBSD" Release 1.0rc2: - New co-maintainer, Daniel Kobras - Skip LDAP replication check by default - Fix segfault - rewrite ldap_check_account() Release 1.0rc1: - New cleanup mode: remove old keytab entries based on time stamp or encryption type [Ticket #32] - New command line syntax: modes (i.e. create, update, ..) can be given on the command line without leading dashes (--create, --update is still working). - New option "-n": disable reverse lookups on client hostname [Ticket #50] - Restructured manual page - set LDAP_OPT_X_SASL_SSF_MIN to 56 [Ticket #36] - Restore compatibility with OpenLDAP 2.3 [Ticket #38] - disable LDAPS [Ticket #46] - Re-factor msktldap.cpp [Ticket #53] - AIX does not compile std::lower [Ticket #42] - Compiler Error on AIX com_err.h needs extern "C" [Ticket #35] - Add support for udns dns resolver library (--with-udns) - Many fixes for memory management - New work flow for keytab updates - Avoid endless recursion in set_password due to slow replication [Ticket #40] - fixed permission problems with --upn [Ticket #47] - Add compatibility for keytabs that have been created by other tools [Ticket #48] Release 0.5.1: - Add --keytab-auth-as option (thanks Andrew Deason) - Add --allow-weak-crypto switch, to support single DES (thanks Andrew Deason and Mark Pröhl) - If servicePrincipalName begins with "HOST/", rewrite to "host/" (thanks Boleslaw Tokarski for the report) - msktutil manual page fixes (thanks Andrew Deason and Mark Pröhl) - Fix possible samAccountName corruption bug with uniniatialized variables (thanks Jaroslaw Polok for the report) - Adjust --precreate to match ADUC's behavior with long account names (thanks Erik de Vries) - Build fixes for HPUX and NetBSD - Fix issue with private glibc function on RHEL5 (thanks Daniel Kobras) - Incorporate hardening patches from Debian (thanks Tony Mancill) - Delete "debian" directory (this will be maintained downstream) Release 0.5: - New co-maintainer, Olaf Flebbe - Support service accounts in addition to computer accounts - Add option to set the samba secret password - Add option ("--realm") to specify a custom realm - Various build fixes - Add support for clients behind a NAT firewall Release 0.4.2: - New co-maintainer, Mark Pröhl - Increase computer name character limit from 18 to 19 characters, matching AD's own limits. - Add option ("-N") to disable reverse lookups on DCs - Add option ("--old-account-password") to use the old computer account password to create a new keytab on a host. - Return the proper error code when krb5_change_password fails. - Better autodetection for krb5-config location. - Compatibility with autoconf >= 2.68. - Build fixes for Red Hat and Ubuntu. - Update documentation for single-DES and AFS. Release 0.4.1: - Ken Dreyer took over maintenance, based upon master at http://repo.or.cz/w/msktutil.git - Build fixes for Red Hat Release 0.4: - James Y Knight took over maintenance, based upon msktutil_0.3.16-7 downloaded from: http://download.systemimager.org/~finley/msktutil/ - Made most functionality work properly with only the machine account credentials. - Adds COMPUTERNAME$ to the keytab, and authenticates with that, so that setting userPrincipalName to host/COMPUTERNAME.DOMAIN@REALM isn't necessary. (since userPrincipalName isn't settable without admin perms) - Now attempts to authenticate with the default machine account password so that AD "reset account" is functional. - Gets the default LDAP OU to create new machines in from the magic GUID from AD, instead of assuming CN=Computers. - Added --precreate option to allow an administrator to script creation of accounts without touching a local keytab. - Added --auto-update for use from a crontab to auto-rotate password. - No longer attempts to disable password expiry by default: So note, you need to either run --auto-update from cron or else pass the (new) argument --dont-expire-password when creating the account. - Added --remove-service argument. - Fixed old kvno expiration policy so that it keeps old principals around in the keytab for a week, instead of just keeping the immediately-prior kvno. - Disabled use of DES keys by default. You will have to explicitly request them with --enctypes if you want them. - Removed --des-only option, you can use --enctypes if you really want to use single DES. (which, of course, you shouldn't, given that it's now 2010 and Single DES was known to be utterly broken for over 10 years by now!) - Fixed salting to lowercase the account name, as the AD server does. - Switched languages from C to C++. - Lots of other cleanup and various bugfixes. **** Changelog of non-packaging changes from previous releases: msktutil 0.3.16-7 * fix keytab bug in 0.3.16-6 -- Doug Engert Fri, 17 Apr 2009 10:48:00 -0500 msktutil 0.3.16-6 * Work with W2008 without hotfix 951191 * SASL ssf varied depending on TLS to circumvent another W2008 bug * added --enctypes N where N is defined with W2008 http://msdn.microsoft.com/en-us/library/cc223853(PROT.10).aspx msDs-supportedEncrtptionTypes. 1=DES, 2=DES, 4=RC4, 8=AES128 16=AES256. N is sum of these. * Use /dev/urandom and 63 character password. * --verbose --verbose turns on LDAP debugging * #ifdef for use with Solairs LDAP * Cleanup of other LDAP code and error handing * msktutil.interactive updated to work on Solaris and use msktutil from same directory. -- Doug Engert Tue, 14 Apr 2009 11:16:53 -0500 msktutil (0.3.16-5) * Updated msktutil.interactive example script. -- Brian Elliott Finley Mon, 07 Aug 2006 16:59:24 -0500 msktutil (0.3.16-4) * Updated msktutil.interactive example script. -- Brian Elliott Finley Thu, 27 Jul 2006 16:31:17 -0500 msktutil-1.2.2/INSTALL000066400000000000000000000030161467024531100144240ustar00rootroot00000000000000msktutil (C) 2004-2006 Dan Perry (C) 2006 Brian Elliott Finley (finley@anl.gov) (C) 2009-2010 Doug Engert (deengert@anl.gov) (C) 2010 James Knight (C) 2010-2013 Ken Dreyer (C) 2012-2017 Mark Proehl (C) 2012-2017 Olaf Flebbe (C) 2013-2017 Daniel Kobras ------------------------------------------------------------------------------- Generate files not checked into source control: When building from a pristine source run './autogen.sh' (i.e. the tools autoconf and autoheader) to generate the files 'configure' and 'configure.h.in'. Building from source release: Msktutil uses a standard autoconf system to build. Just run './configure' followed by 'make' and (as root) 'make install'. Note that in order to build this you will need development headers and libraries for MIT Kerberos, LDAP, and SASL. If these packages are installed elsewhere, you can use the following options to the configure script: --with-krb5dir=/path/to/kerberos --with-ldapdir=/path/to/ldap --with-sasldir=/path/to/sasl Note the paths specified are expected to a base path. For example, th configure script expects that an 'include' and 'lib' directory exists in each of the base paths with the appropriate headers and libraries. You also can set the LDFLAGS and CPPFLAGS environment variables to specify other paths for these headers and libraries. msktutil-1.2.2/LICENSE000066400000000000000000000431331467024531100144040ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. msktutil-1.2.2/Makefile.in000066400000000000000000000037431467024531100154470ustar00rootroot00000000000000PROG=@PACKAGE_NAME@ SHELL=/bin/sh srcdir=@srcdir@ VPATH=@srcdir@ prefix=@prefix@ exec_prefix=@exec_prefix@ datarootdir=@datarootdir@ sbindir=@sbindir@ mandir=@mandir@ CXX=@CXX@ CC=@CC@ CPPFLAGS=-I. @CPPFLAGS@ CXXFLAGS=@CXXFLAGS@ @WARNFLAGS@ CFLAGS=@CFLAGS@ @WARNFLAGS@ LDFLAGS=@LDFLAGS@ LIBS=@LIBS@ INSTALL=@INSTALL@ RM=@RM@ -rf CP=@CP@ -f SED=@SED@ CAT=@CAT@ ECHO=@ECHO@ MKDIR=@MKDIR@ TAR=@TAR@ MAN=@PACKAGE_NAME@.1 SOURCES_CPP=krb5wrap.cpp msktutil.cpp msktkrb5.cpp msktldap.cpp msktname.cpp msktpass.cpp msktconf.cpp ldapconnection.cpp SOURCES_C=strtoll.c SOURCES=$(SOURCES_CPP) $(SOURCES_C) HEADERS=msktutil.h msktname.h krb5wrap.h ldapconnection.h OBJECTS=$(SOURCES_CPP:.cpp=.o) $(SOURCES_C:.c=.o) DISTARCHIVE=@PACKAGE_NAME@-@PACKAGE_VERSION@ EXTRA_DIST=README LICENSE ChangeLog INSTALL Makefile.in $(MAN).in configure.ac autogen.sh .SUFFIXES: .c .cpp .o all: $(PROG) $(PROG): $(OBJECTS) $(CXX) $(LDFLAGS) $(OBJECTS) $(LIBS) -o $(PROG) .cpp.o: $(HEADERS) config.h $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ .c.o: config.h $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ clean: $(RM) $(PROG) $(OBJECTS) distclean: clean $(RM) Makefile $(MAN) config.h config.log config.cache config.status autom4te.cache \ config.h~ config.h.in~ config.h.in \ $(DISTARCHIVE).tar.gz $(DISTARCHIVE).tar.bz2 configure config.h.in: configure.ac ./autogen.sh install: all $(MKDIR) -p $(DESTDIR)$(sbindir) $(MKDIR) -p $(DESTDIR)$(mandir)/man1 $(INSTALL) -m 0755 $(PROG) $(DESTDIR)$(sbindir) $(INSTALL) -m 0644 $(MAN) $(DESTDIR)$(mandir)/man1 distcheck: @$(ECHO) @PACKAGE_VERSION@ $(DISTARCHIVE).tar.gz: $(SOURCES) $(HEADERS) config.h.in configure $(EXTRA_DIST) $(TAR) --transform=s+^+$(DISTARCHIVE)/+ -zcf $@ $(SOURCES) $(HEADERS) config.h.in configure $(EXTRA_DIST) $(DISTARCHIVE).tar.bz2: $(SOURCES) $(HEADERS) config.h.in configure $(EXTRA_DIST) $(TAR) --transform=s+^+$(DISTARCHIVE)/+ -jcf $@ $(SOURCES) $(HEADERS) config.h.in configure $(EXTRA_DIST) dist: $(DISTARCHIVE).tar.gz $(DISTARCHIVE).tar.bz2 msktutil-1.2.2/README000066400000000000000000000044341467024531100142600ustar00rootroot00000000000000msktutil (C) 2004-2006 Dan Perry (C) 2006 Brian Elliott Finley (finley@anl.gov) (C) 2009-2010 Doug Engert (deengert@anl.gov) (C) 2010 James Knight (C) 2010-2013 Ken Dreyer (C) 2012-2021 Olaf Flebbe (C) 2012-2022 Mark Proehl (C) 2013-2017 Daniel Kobras (C) 2017-2022 Michael Osipov (C) 2018-2022 Daniel Kobras This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ---------------------------------------------------------------------------- Msktutil is a program for interoperability with Active Directory that can: - Create a computer account in Active Directory - Create a service account in Active Directory - Create a system Kerberos keytab - Create a Kerberos keytab for a dedicated service - Add and remove principals to and from keytabs - Change the account's password See the man page for more information on how to use it. ---------------------------------------------------------------------------- Early history: > This software was originally written by Dan Perry. Brian Elliott Finley > debianized it in Mar 2006. Doug Engbert made a number of changes to it > during 2009. Since Feb 2010, when James Knight downloaded the Debian > package, history and contributions have been tracked via Git. The msktutil package is currently maintained by Mark Proehl, Michael Osipov, and Daniel Kobras. It is available from . msktutil-1.2.2/autogen.sh000077500000000000000000000000361467024531100153730ustar00rootroot00000000000000#!/bin/sh autoheader autoconf msktutil-1.2.2/configure.ac000066400000000000000000000240401467024531100156610ustar00rootroot00000000000000# Process this file with autoconf to produce a configure script. AC_PREREQ(2.53) AC_INIT(msktutil, 1.2.2) AC_CONFIG_HEADERS([config.h]) PACKAGE_DATE="2024-09-11" PACKAGE_AUTHOR="Mark Pröhl, Olaf Flebbe, Daniel Kobras, Michael Osipov" AC_SUBST(PACKAGE_DATE) AC_SUBST(PACKAGE_AUTHOR) # Checks for programs. AC_PROG_CC AC_PROG_CXX AC_LANG(C++) AC_PATH_PROGS(INSTALL, install) AC_PATH_PROGS(CAT, cat) AC_PATH_PROGS(RM, rm) AC_PATH_PROGS(CP, cp) AC_PATH_PROGS(ECHO, echo) AC_PATH_PROGS(SED, sed) AC_PATH_PROGS(MKDIR, mkdir) AC_PATH_PROGS(TAR, gnutar gtar tar) AC_SUBST(WARNFLAGS) AC_ARG_WITH(mandir, [ --with-mandir=DIR Where to put man pages ($mandir)], [ mandir="$withval" ]) AC_ARG_WITH(krb5, [ --with-krb5= Which krb5 implementation to use (default: auto-detect)], [ krb5="$withval" ], [ krb5="auto" ] ) AC_ARG_WITH(krb5dir, [ --with-krb5dir=DIR Where to find the Kerberos 5 includes and libraries], [ krb5dir="$withval" ]) AC_ARG_WITH(krb5-config, [ --with-krb5-config=DIR Where to find the Kerberos 5 configuration script], [ krb5config="$withval" ]) AC_ARG_WITH(ldapdir, [ --with-ldapdir=DIR Where to find the LDAP includes and libraries], [ ldapdir="$withval" ]) AC_ARG_WITH(sasldir, [ --with-sasldir=DIR Where to find the SASL includes and libraries], [ sasldir="$withval" ]) AC_ARG_WITH(tmpdir, [ --with-tmpdir=DIR What temporary directory to use], [ tmpdir="$withval" ]) AC_ARG_WITH(udns, [ --with-udns= Whether to use and where to find the udns includes and libraries (default: check)], [ udns="$withval" ], [ udns=check ] ) AC_ARG_WITH(static-udns, [ --with-static-udns Whether to link udns statically (default: no)], [ udnsstatic=yes ], [ udnsstatic=no ] ) # If krb5-config was not specified, try to find it ourselves. AS_IF([test -z "$krb5config"], [AC_PATH_PROG([PATH_KRB5_CONFIG], [krb5-config] [], [${PATH}:/usr/kerberos/bin]) [ krb5config="$PATH_KRB5_CONFIG"] ]) if test x$GCC = xyes ; then WARNFLAGS="-Wall -Wextra -Wno-write-strings" fi if test -x "$krb5config"; then KRB5_CPPFLAGS=`$krb5config --cflags 2>/dev/null` KRB5_LIBS=`$krb5config --libs 2>/dev/null` # Older versions of krb5-config do not support --vendor, yet. # The resulting error message is not sent to stderr but stdout, though! # So just check the exit code first, and only call --vendor for real if # it is actually supported. This avoids spilling an error message into # the KRB5_VENDOR variable, and just keeps it empty in this case. KRB5_VENDOR=`$krb5config --vendor >/dev/null 2>&1 && $krb5config --vendor 2>/dev/null || :` CPPFLAGS="$CPPFLAGS $KRB5_CPPFLAGS" LIBS="$LIBS $KRB5_LIBS" else AC_MSG_WARN([Could not find a krb5-config program.]) fi if test "$krb5dir" != ""; then if test -d "$krb5dir/include"; then CPPFLAGS="$CPPFLAGS -I$krb5dir/include" fi if test -d "$krb5dir/lib"; then LDFLAGS="$LIBDIRS -L$krb5dir/lib" fi fi if test "$ldapdir" != ""; then if test "$ldapdir" != "$krb5dir"; then CPPFLAGS="-I$ldapdir/include $CPPFLAGS" LDFLAGS="-L$ldapdir/lib $LDFLAGS" fi fi if test "$sasldir" != ""; then if test "$sasldir" != "$krb5dir"; then if test "$sasldir" != "$ldapdir"; then CPPFLAGS="-I$sasldir/include $CPPFLAGS" LDFLAGS="-L$sasldir/lib $LDFLAGS" fi fi fi if test "$tmpdir" != ""; then CPPFLAGS="$CPPFLAGS -DTMP_DIR=\\\"$tmpdir\\\"" fi case "$udns" in no|yes|check) ;; *) if test "$udns" != "$krb5dir" && test "$udns" != "$ldapdir" && test "$udns" != "$sasldir" ; then if test -d "$udns/include"; then CPPFLAGS="-I$udns/include $CPPFLAGS" else # udns source dir, put include path extension last since otherwise # udns's config.h will be found first and msktutil's build will fail CPPFLAGS="-I$udns $CPPFLAGS" fi if test -d "$udns/lib"; then LDFLAGS="-L$udns/lib $LDFLAGS" else LDFLAGS="-L$udns $LDFLAGS" fi fi ;; esac AC_CHECK_HEADERS([krb5.h com_err.h]) if test "$ac_cv_header_krb5_h" = "no"; then AC_MSG_ERROR([This program cannot be built without krb5.h]) fi # Fedora, RHEL, Centos has com_err.h in /usr/include/et AC_CHECK_HEADERS([et/com_err.h]) if test "$ac_cv_header_et_com_err_h" = "yes"; then CPPFLAGS="-I/usr/include/et $CPPFLAGS" AC_DEFINE(HAVE_COM_ERR_H, 1) fi # We only ever directly include krb5.h, so the only reason to check for # Heimdal-specific heim_err.h is to distinguish which implementation we use # in case we came across a krb5-config that doesn't support the --vendor option, # yet. We can entirely skip this check if the krb5 implementation has been given # explicitly, ie. krb5 is set to anything but "auto". case "$krb5" in mit|heimdal) ;; auto) case "$KRB5_VENDOR" in Massachusetts*) krb5="mit" ;; Heimdal) krb5="heimdal" ;; "") krb5="mit"; AC_CHECK_HEADERS([heim_err.h heimdal/heim_err.h], [krb5="heimdal"; break]) ;; *) AC_MSG_ERROR([krb5-config reports unknown vendor $KRB5_VENDOR]) esac ;; *) AC_MSG_ERROR([invalid parameter for --with-krb5]) ;; esac AC_MSG_CHECKING([krb5 implementation]) AC_MSG_RESULT([$krb5]) if test "$krb5" = "heimdal"; then AC_DEFINE(HEIMDAL, 1, [defined if the Kerberos implementation is Heimdal]) fi AC_CHECK_HEADERS([sasl.h]) if test "$ac_cv_header_sasl_h" = "no"; then AC_CHECK_HEADERS([sasl/sasl.h]) if test "$ac_cv_header_sasl_sasl_h" = "no"; then AC_MSG_ERROR([This program cannot be built without sasl.h]) fi fi if test "$udns" != "no"; then AC_CHECK_HEADERS([udns.h]) if test "$ac_cv_header_udns_h" = "no"; then if test "$udns" != "check"; then AC_MSG_ERROR([udns requested but udns.h not found]) fi udns=no fi fi if test "$udns" != "no"; then if test "$udnsstatic" != "no"; then # FIXME: Only works with GNU ld :-( LIBS="$LIBS -Wl,-Bstatic -ludns -Wl,-Bdynamic" AC_MSG_CHECKING([for dns_new in statically linked -ludns]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include ]],[[ dns_new(0); ]])], [ AC_DEFINE(HAVE_LIBUDNS, 1) AC_MSG_RESULT([yes])], [ if test "$udns" != "check"; then AC_MSG_ERROR([static link to udns requested but static libudns not found]) fi udns=no]) else AC_CHECK_LIB([udns], [dns_new], , [ if test "$udns" != "check"; then AC_MSG_ERROR([udns requested but libudns not found]) fi udns=no]) fi fi if test "$udns" = "no"; then # look for resolver functions # see if ns_initparse is simply in libc or libresolv AC_SEARCH_LIBS([ns_initparse], [resolv], AC_DEFINE(HAVE_NS_INITPARSE, 1, [defined if ns_initparse can be used]) , [ # On Linux it actually is in libresolv but prefixed with __ when # linking dynamically. This is taken care of by actually including the # right headers and trying again. AC_MSG_CHECKING([for ns_initparse]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include #include #include ]],[[ ns_initparse(0, 0, 0); ]])], [AC_DEFINE(HAVE_NS_INITPARSE,1) AC_MSG_RESULT([yes])], [ # now try libresolv again AC_MSG_RESULT([no]) saved_LIBS="$LIBS" LIBS="$LIBS -lresolv" AC_MSG_CHECKING([for ns_initparse in -lresolv]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include #include #include ]],[[ ns_initparse(0, 0, 0); ]])], [AC_DEFINE(HAVE_NS_INITPARSE,1) AC_MSG_RESULT([yes])], [ # glibc versions prior to 2.9 include ns_initparse in # libresolve but do not export it. Try to circumvent # this restriction by linking the missing bits statically. AC_MSG_RESULT([no]) LIBS="$saved_LIBS -lresolv -Wl,-Bstatic -lresolv -Wl,-Bdynamic" AC_MSG_CHECKING([for ns_initparse in statically linked -lresolv]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include #include #include ]],[[ ns_initparse(0, 0, 0); ]])], [AC_DEFINE(HAVE_NS_INITPARSE,1) AC_MSG_RESULT([yes])], [ LIBS="$saved_LIBS" AC_MSG_RESULT([no])])])])]) # finally check if res_search is present as well just as a sanity check AC_MSG_CHECKING([for res_search]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[ #include #include #include ]],[[ res_search(0, 0, 0, 0, 0); ]])], [ AC_DEFINE(HAVE_RES_SEARCH, 1, [defined if res_search can be used]) AC_MSG_RESULT([yes])], AC_MSG_RESULT([no])) fi # Solaris support, http://stackoverflow.com/questions/1630225/what-is-the-canonical-way-to-make-autoconf-link-in-solaris-network-libraries AC_SEARCH_LIBS([gethostbyname], [nsl]) AC_SEARCH_LIBS([socket], [socket], [], [ AC_CHECK_LIB([socket], [socket], [LIBS="-lsocket -lnsl $LIBS"], [], [-lnsl])]) # Checks for libraries. AC_CHECK_LIB([ldap], [ldap_search_ext_s], , [AC_MSG_ERROR([libldap not found])]) # # OpenLDAP's LDAP_OPT_ON macro uses ber_pvt_opt_on. In newer OpenLDAP # versions, we have to explicitly link with lber. # AC_CHECK_LIB([lber], [ber_pvt_opt_on], , [AC_MSG_ERROR([liblber not found])]) AC_CHECK_LIB([com_err], [error_message], , [AC_MSG_ERROR([libcom_err not found])]) # # LDAP_OPT_DIAGNOSTIC_MESSAGE is present from OpenLDAP 2.4 upwards, only. AC_CHECK_DECLS([LDAP_OPT_DIAGNOSTIC_MESSAGE], [], [], [[#include ]]) if test "$ac_cv_header_com_err_h"; then AC_MSG_CHECKING([whether com_err.h needs extern "C"]); AC_LINK_IFELSE([AC_LANG_SOURCE([ #include #include int main(void) { error_message(0); } ])], [AC_MSG_RESULT(no); com_err_needs_extern_c=no], [ AC_LINK_IFELSE([AC_LANG_SOURCE([ #include extern "C" { #include } int main(void) { error_message(0); } ])], [AC_MSG_RESULT(yes); com_err_needs_extern_c=yes], [AC_MSG_ERROR([Couldn't get error_message to work.])])]) if test "$com_err_needs_extern_c=yes"; then AC_DEFINE(COM_ERR_NEEDS_EXTERN_C, 1, [Does com_err.h need extern "C" around it?]) fi fi # Check for functions AC_CHECK_FUNCS(vasprintf vsnprintf setenv strtoll ldap_initialize krb5_get_etype_info) # Check that the AES enctypes are found AC_CHECK_DECLS([ENCTYPE_AES256_CTS_HMAC_SHA1_96, ENCTYPE_AES128_CTS_HMAC_SHA1_96], [], [], [[#include ]]) AC_CONFIG_FILES([Makefile msktutil.1]) AC_OUTPUT msktutil-1.2.2/krb5wrap.cpp000066400000000000000000000271341467024531100156430ustar00rootroot00000000000000/* *---------------------------------------------------------------------------- * * krb5wrap.cpp * * (C) 2004-2006 Dan Perry (dperry@pppl.gov) * (C) 2006 Brian Elliott Finley (finley@anl.gov) * (C) 2009-2010 Doug Engert (deengert@anl.gov) * (C) 2010 James Y Knight (foom@fuhm.net) * (C) 2010-2013 Ken Dreyer * (C) 2012-2017 Mark Proehl * (C) 2012-2017 Olaf Flebbe * (C) 2013-2017 Daniel Kobras * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *----------------------------------------------------------------------------- */ #include "msktutil.h" void krb5_error_exit( const char *func, int err_code) { v_error_exit("error_exit: krb5 function %s failed: %s", func, error_message(err_code)); } void krb5_warn( const char *func, int err_code) { fprintf( stderr, "Warning: krb5 function %s failed: %s", func, error_message(err_code)); } #ifdef HEIMDAL krb5_error_code krb5_free_keytab_entry_contents(krb5_context context, krb5_keytab_entry *entry) { if (entry) { krb5_free_principal(context, entry->principal); if (entry->keyblock.keyvalue.data) { memset(entry->keyblock.keyvalue.data, 0, entry->keyblock.keyvalue.length); free(entry->keyblock.keyvalue.data); } return 0; } return -1; } #endif void initialize_g_context() { VERBOSE("Creating Kerberos context"); krb5_error_code ret = krb5_init_context(&g_context); if (ret) { krb5_error_exit("krb5_init_context", ret); } } void destroy_g_context() { VERBOSE("Destroying Kerberos context"); krb5_free_context(g_context); g_context = 0; } void KRB5CCache::initialize(KRB5Principal &principal) { krb5_error_code ret = krb5_cc_initialize(g_context, m_ccache, principal.get()); if (ret) { throw KRB5Exception("krb5_cc_initialize", ret); } } void KRB5CCache::store(KRB5Creds &creds) { krb5_error_code ret = krb5_cc_store_cred(g_context, m_ccache, creds.get()); if (ret) { throw KRB5Exception("krb5_cc_store_cred", ret); } } KRB5Creds::KRB5Creds(KRB5Principal &principal, KRB5Keytab &keytab, const char *tkt_service) : m_creds() { krb5_error_code ret = krb5_get_init_creds_keytab(g_context, &m_creds, principal.get(), keytab.get(), 0, const_cast(tkt_service), NULL); if (ret) { throw KRB5Exception("krb5_get_init_creds_keytab", ret); } } KRB5Creds::KRB5Creds(KRB5Principal &principal, const std::string &password, const char *tkt_service) : m_creds() { krb5_error_code ret = krb5_get_init_creds_password(g_context, &m_creds, principal.get(), const_cast(password.c_str()), NULL, NULL, 0, const_cast(tkt_service), NULL); if (ret) { throw KRB5Exception("krb5_get_init_creds_keytab", ret); } } std::string KRB5Principal::name() { char *principal_string; krb5_error_code ret = krb5_unparse_name(g_context, m_princ, &principal_string); if (ret) { throw KRB5Exception("krb5_unparse_name", ret); } std::string result(principal_string); #ifdef HEIMDAL krb5_xfree(principal_string); #else krb5_free_unparsed_name(g_context, principal_string); #endif return result; } void KRB5Keytab::addEntry(const KRB5Principal &princ, krb5_kvno kvno, krb5_keyblock &keyblock) { krb5_keytab_entry entry; entry.principal = princ.get(); entry.vno = kvno; #ifdef HEIMDAL entry.keyblock = keyblock; #else entry.key = keyblock; #endif // avoid duplicate entries (void) krb5_kt_remove_entry(g_context, m_keytab, &entry); krb5_error_code ret = krb5_kt_add_entry(g_context, m_keytab, &entry); if (ret) { if (errno != 0) { fprintf(stderr,"Error: keytab write error: %s\n", strerror(errno)); } throw KRB5Exception("krb5_kt_add_entry failed", ret); } } void KRB5Keytab::addEntry(const KRB5Principal &princ, krb5_kvno kvno, krb5_enctype enctype, const std::string &password, const std::string &salt) { krb5_keyblock keyblock; #ifdef HEIMDAL krb5_data pass_data; krb5_salt salt_data; salt_data.salttype = KRB5_PW_SALT; salt_data.saltvalue.data = const_cast(salt.c_str()); salt_data.saltvalue.length = salt.length(); pass_data.data = const_cast(password.c_str()); pass_data.length = password.length(); krb5_error_code ret = krb5_string_to_key_data_salt(g_context, enctype, pass_data, salt_data, &keyblock); if (ret) { throw KRB5Exception("krb5_string_to_key_data_salt", ret); } #else krb5_error_code ret; krb5_data salt_data, pass_data; krb5_data *saltparam = &salt_data; krb5_data *s2kparams = NULL; salt_data.data = const_cast(salt.c_str()); salt_data.length = salt.length(); pass_data.data = const_cast(password.c_str()); pass_data.length = password.length(); /* MIT Kerberos v1.17+ allows us to replace the supplied salt (guessed by * heuristics) with proper salt info as returned by the KDC. We'll always * fall back to the previous behaviour for backward compatibility. */ # if HAVE_KRB5_GET_ETYPE_INFO krb5_get_init_creds_opt *opt = NULL; ret = krb5_get_init_creds_opt_alloc(g_context, &opt); if (ret) { throw KRB5Exception("krb5_get_init_creds_opt_alloc", ret); } krb5_get_init_creds_opt_set_etype_list(opt, &enctype, 1); krb5_enctype kdc_enctype; krb5_data kdc_salt; krb5_data kdc_s2kparams; ret = krb5_get_etype_info(g_context, princ.get(), opt, &kdc_enctype, &kdc_salt, &kdc_s2kparams); krb5_get_init_creds_opt_free(g_context, opt); /* We query info for a single enctype, so this test should only ever fail * if the KDC returns ENCTYPE_NULL, indicating that the requested enctype * is not supported on its end. At this point, we could refuse to add * the requested enctype to the keytab. For consistency with previous * behaviour, though, we just keep going and add an entry with the salt * value supplied by the caller. */ if (!ret && kdc_enctype == enctype) { s2kparams = (kdc_s2kparams.length > 0) ? &kdc_s2kparams : NULL; saltparam = &kdc_salt; } # endif ret = krb5_c_string_to_key_with_params(g_context, enctype, &pass_data, saltparam, s2kparams, &keyblock); if (s2kparams) { krb5_free_data_contents(g_context, s2kparams); } if (saltparam != &salt_data) { krb5_free_data_contents(g_context, saltparam); } if (ret) { throw KRB5Exception("krb5_c_string_to_key_with_params", ret); } #endif addEntry(princ, kvno, keyblock); } void KRB5Keytab::removeEntry(const KRB5Principal &princ, krb5_kvno kvno, krb5_enctype enctype) { krb5_keytab_entry entry; entry.principal = princ.get(); entry.vno = kvno; #ifdef HEIMDAL entry.keyblock.keytype = enctype; #else entry.key.enctype = enctype; #endif krb5_error_code ret = krb5_kt_remove_entry(g_context, m_keytab, &entry); if (ret) { if (errno != 0) { fprintf(stderr,"Error: keytab write error: %s\n", strerror(errno)); } throw KRB5Exception("krb5_kt_remove_entry", ret); } } KRB5Keytab::cursor::cursor(KRB5Keytab &keytab) : m_keytab(keytab), m_cursor(), m_entry(), m_princ(), m_ok(true) { memset(&m_entry, 0, sizeof(m_entry)); krb5_error_code ret = krb5_kt_start_seq_get(g_context, m_keytab.m_keytab, &m_cursor); if (ret) { m_ok = false; } } KRB5Keytab::cursor::~cursor() { if (!m_ok) { m_princ.reset_no_free(NULL); return; } krb5_free_keytab_entry_contents(g_context, &m_entry); memset(&m_entry, 0, sizeof(m_entry)); /* Tell m_princ to not free its contents! */ m_princ.reset_no_free(NULL); krb5_error_code ret = krb5_kt_end_seq_get(g_context, m_keytab.m_keytab, &m_cursor); if (ret) { krb5_warn("krb5_kt_end_seq_get", ret); } } void KRB5Keytab::cursor::reset() { if (!m_ok) { return; } krb5_error_code ret = krb5_kt_start_seq_get(g_context, m_keytab.m_keytab, &m_cursor); if (ret) { m_ok = false; } } bool KRB5Keytab::cursor::next() { if (!m_ok) { return false; } krb5_free_keytab_entry_contents(g_context, &m_entry); memset(&m_entry, 0, sizeof(m_entry)); krb5_error_code ret = krb5_kt_next_entry(g_context, m_keytab.m_keytab, &m_entry, &m_cursor); m_princ.reset_no_free(m_entry.principal); return ret == 0; } krb5_context g_context = NULL; msktutil-1.2.2/krb5wrap.h000066400000000000000000000206501467024531100153040ustar00rootroot00000000000000/* *---------------------------------------------------------------------------- * * krb5wrap.h * * (C) 2004-2006 Dan Perry (dperry@pppl.gov) * (C) 2006 Brian Elliott Finley (finley@anl.gov) * (C) 2009-2010 Doug Engert (deengert@anl.gov) * (C) 2010 James Y Knight (foom@fuhm.net) * (C) 2010-2013 Ken Dreyer * (C) 2012-2017 Mark Proehl * (C) 2012-2017 Olaf Flebbe * (C) 2013-2017 Daniel Kobras * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *----------------------------------------------------------------------------- */ extern krb5_context g_context; void krb5_error_exit( const char *func, int err_code); void krb5_warn( const char *func, int err_code); void initialize_g_context(); void destroy_g_context(); class KRB5Principal; class KRB5Keytab { krb5_keytab m_keytab; // make it non copyable KRB5Keytab(const KRB5Keytab&); const KRB5Keytab& operator=(const KRB5Keytab&); public: KRB5Keytab(const std::string &keytab_name) : m_keytab() { krb5_error_code ret = krb5_kt_resolve(g_context, keytab_name.c_str(), &m_keytab); if (ret) { throw KRB5Exception("krb5_kt_resolve", ret); } } ~KRB5Keytab() { krb5_error_code ret = krb5_kt_close(g_context, m_keytab); if (ret) { krb5_warn("krb5_kt_close", ret); } } void addEntry(const KRB5Principal &princ, krb5_kvno kvno, krb5_keyblock &keyblock); void addEntry(const KRB5Principal &princ, krb5_kvno kvno, krb5_enctype enctype, const std::string &password, const std::string &salt); void removeEntry(const KRB5Principal &princ, krb5_kvno kvno, krb5_enctype enctype); krb5_keytab get() { return m_keytab; } /* Defined below... */ class cursor; }; class KRB5Creds { krb5_creds m_creds; // make it non copyable KRB5Creds(const KRB5Creds&); const KRB5Creds& operator=(const KRB5Creds&); public: KRB5Creds() : m_creds() {} KRB5Creds(KRB5Principal &principal, KRB5Keytab &keytab, const char *tkt_service=NULL); KRB5Creds(KRB5Principal &principal, const std::string &password, const char *tkt_service=NULL); ~KRB5Creds() { krb5_free_cred_contents(g_context, &m_creds); memset(&m_creds, 0, sizeof(m_creds)); } krb5_creds *get() { return &m_creds; } void move_from(KRB5Creds &other) { m_creds = other.m_creds; memset(&other.m_creds, 0, sizeof(m_creds)); } }; class KRB5CCache { krb5_ccache m_ccache; // make it non copyable KRB5CCache(const KRB5CCache&); const KRB5CCache& operator=(const KRB5CCache&); public: static const char *defaultName() { return krb5_cc_default_name(g_context); } KRB5CCache(const char *cc_name) : m_ccache() { krb5_error_code ret = krb5_cc_resolve(g_context, cc_name, &m_ccache); if (ret) throw KRB5Exception("krb5_cc_resolve", ret); } ~KRB5CCache() { krb5_cc_close(g_context, m_ccache); } krb5_ccache get() { return m_ccache; } void initialize(KRB5Principal &principal); void store(KRB5Creds &creds); }; class KRB5Principal { friend class KRB5Keytab::cursor; krb5_principal m_princ; KRB5Principal() : m_princ() {} void reset_no_free(krb5_principal princ) { m_princ = princ; } // make it non copyable KRB5Principal(const KRB5Principal&); const KRB5Principal& operator=(const KRB5Principal&); public: KRB5Principal(krb5_principal princ_raw) : m_princ(princ_raw) {} KRB5Principal(KRB5CCache &ccache) : m_princ() { krb5_error_code ret = krb5_cc_get_principal(g_context, ccache.get(), &m_princ); if (ret) throw KRB5Exception("krb5_cc_get_principal", ret); } KRB5Principal(std::string principal_name) : m_princ() { krb5_error_code ret = krb5_parse_name(g_context, principal_name.c_str(), &m_princ); if (ret) throw KRB5Exception("krb5_parse_name", ret); } ~KRB5Principal() { if (m_princ) krb5_free_principal(g_context, m_princ); } krb5_principal get() const { return m_princ; } std::string name(); }; class KRB5Keytab::cursor { KRB5Keytab &m_keytab; krb5_kt_cursor m_cursor; krb5_keytab_entry m_entry; /* Duplicates part of entry, but oh well. */ KRB5Principal m_princ; // make it non copyable cursor(const cursor&); const cursor& operator=(const cursor&); bool m_ok; public: cursor(KRB5Keytab &keytab); ~cursor(); bool next(); void reset(); KRB5Principal &principal() { return m_princ; } krb5_kvno kvno() const { return m_entry.vno; } krb5_enctype enctype() const { #ifdef HEIMDAL return static_cast(m_entry.keyblock.keytype); #else return m_entry.key.enctype; #endif } krb5_timestamp timestamp() const { return m_entry.timestamp; } krb5_keyblock key() const { #ifdef HEIMDAL return m_entry.keyblock; #else return m_entry.key; #endif } }; class KRB5KeytabEntry { private: std::string m_principal; krb5_timestamp m_timestamp; krb5_kvno m_kvno; krb5_enctype m_enctype; krb5_keyblock m_keyblock; public: KRB5KeytabEntry(krb5_principal principal, krb5_timestamp timestamp, krb5_kvno kvno, krb5_enctype enctype, krb5_keyblock keyblock) : m_principal(KRB5Principal(principal).name()), m_timestamp(timestamp), m_kvno(kvno), m_enctype(enctype), m_keyblock(keyblock) { krb5_error_code ret = krb5_copy_keyblock_contents(g_context, &keyblock, &m_keyblock); if (ret) { throw KRB5Exception("krb5_copy_keyblock_contents", ret); } }; KRB5KeytabEntry(KRB5Keytab::cursor& cursor) : m_principal(cursor.principal().name()), m_timestamp(cursor.timestamp()), m_kvno(cursor.kvno()), m_enctype(cursor.enctype()), m_keyblock(cursor.key()) { krb5_keyblock tmp = cursor.key(); krb5_error_code ret = krb5_copy_keyblock_contents(g_context, &tmp, &m_keyblock); if (ret) { throw KRB5Exception("krb5_copy_keyblock_contents", ret); } }; const KRB5KeytabEntry& operator=(const KRB5KeytabEntry& keytab_entry) { m_principal = keytab_entry.m_principal; m_timestamp = keytab_entry.m_timestamp; m_kvno = keytab_entry.m_kvno; m_enctype = keytab_entry.m_enctype; m_keyblock = keytab_entry.m_keyblock; krb5_error_code ret = krb5_copy_keyblock_contents(g_context, &keytab_entry.m_keyblock, &m_keyblock); if (ret) { throw KRB5Exception("krb5_copy_keyblock_contents", ret); } return *this; }; KRB5KeytabEntry(const KRB5KeytabEntry &keytab_entry) { (void) operator=(keytab_entry); }; ~KRB5KeytabEntry() { krb5_free_keyblock_contents(g_context, &m_keyblock); }; std::string principal() { return m_principal; }; krb5_timestamp timestamp() { return m_timestamp; }; krb5_kvno kvno() { return m_kvno; }; krb5_enctype enctype() { return m_enctype; }; krb5_keyblock keyblock() { return m_keyblock; }; bool operator < (const KRB5KeytabEntry& other) const { return m_timestamp < other.m_timestamp; }; }; msktutil-1.2.2/ldapconnection.cpp000066400000000000000000000276251467024531100171130ustar00rootroot00000000000000/* *---------------------------------------------------------------------------- * * ldapconnection.cpp * * (C) 2004-2006 Dan Perry (dperry@pppl.gov) * (C) 2006 Brian Elliott Finley (finley@anl.gov) * (C) 2009-2010 Doug Engert (deengert@anl.gov) * (C) 2010 James Y Knight (foom@fuhm.net) * (C) 2010-2013 Ken Dreyer * (C) 2012-2017 Mark Proehl * (C) 2012-2017 Olaf Flebbe * (C) 2013-2017 Daniel Kobras * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *----------------------------------------------------------------------------- */ #include #include "msktutil.h" #include "config.h" #ifdef HAVE_SASL_H #include #else #include #endif #define VERBOSEldap(text...) if (g_verbose > 1) { fprintf(stderr, " ###### %s: ", __FUNCTION__); fprintf(stderr, ## text); fprintf(stderr, "\n"); } static int sasl_interact(ATTRUNUSED LDAP *ld, ATTRUNUSED unsigned flags, ATTRUNUSED void *defaults, void *in) { char *dflt = NULL; sasl_interact_t *interact = (sasl_interact_t *) in; while (interact->id != SASL_CB_LIST_END) { dflt = (char *) interact->defresult; interact->result = (dflt && *dflt) ? dflt : (void *) ""; interact->len = (dflt && *dflt) ? strlen(dflt) : 0; interact++; } return LDAP_SUCCESS; } LDAPConnection::LDAPConnection(const std::string &server, const std::string &sasl_mechanisms, bool no_reverse_lookups) : m_ldap() { int ret = 0; #ifdef HAVE_LDAP_INITIALIZE std::string ldap_url = "ldap://" + server; VERBOSEldap("Calling ldap_initialize"); ret = ldap_initialize(&m_ldap, ldap_url.c_str()); #else VERBOSEldap("Calling ldap_init"); m_ldap = ldap_init(server.c_str(), LDAP_PORT); if (m_ldap) { ret = LDAP_SUCCESS; } else { ret = LDAP_OTHER; } #endif if (ret) { throw LDAPException("ldap_initialize", ret); } #ifdef LDAP_OPT_DEBUG_LEVEL int debug = 0xffffff; if (g_verbose > 1) { ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &debug); } #endif int version = LDAP_VERSION3; VERBOSE("Connecting to LDAP server: %s", server.c_str()); set_option(LDAP_OPT_PROTOCOL_VERSION, &version); set_option(LDAP_OPT_REFERRALS, LDAP_OPT_OFF); sasl_ssf_t sasl_gssapi_minssf = 56; set_option(LDAP_OPT_X_SASL_SSF_MIN, &sasl_gssapi_minssf); #ifdef LDAP_OPT_X_SASL_NOCANON if (no_reverse_lookups) { try { set_option(LDAP_OPT_X_SASL_NOCANON, LDAP_OPT_ON); } catch (LDAPException &e) { VERBOSE("Could not disable reverse lookups in LDAP"); } } #else VERBOSE("Your LDAP version does not support the option to disable " "reverse lookups"); #endif VERBOSEldap("Calling ldap_sasl_interactive_bind_s with mechs: %s", sasl_mechanisms.c_str()); ret = ldap_sasl_interactive_bind_s(m_ldap, NULL, sasl_mechanisms.c_str(), NULL, NULL, #ifdef LDAP_SASL_QUIET g_verbose ? 0 : LDAP_SASL_QUIET, #else 0, #endif sasl_interact, NULL); if (ret) { print_diagnostics("ldap_sasl_interactive_bind_s failed", ret); m_ldap = NULL; } } void LDAPConnection::print_diagnostics(const char *msg, int err) { fprintf(stderr, "Error: %s (%s)\n", msg, ldap_err2string(err)); #if HAVE_DECL_LDAP_OPT_DIAGNOSTIC_MESSAGE char *opt_message = NULL; ldap_get_option(m_ldap, LDAP_OPT_DIAGNOSTIC_MESSAGE, &opt_message); if (opt_message) { fprintf(stderr, "\tadditional info: %s\n", opt_message); } ldap_memfree(opt_message); #endif } void LDAPConnection::set_option(int option, const void *invalue) { int ret = ldap_set_option(m_ldap, option, invalue); if (ret) { std::stringstream ss; ss << "ldap_set_option (option=" << option << ") "; throw LDAPException(ss.str(), ret); } } void LDAPConnection::get_option(int option, void *outvalue) { int ret = ldap_get_option(m_ldap, option, outvalue); if (ret) { std::stringstream ss; ss << "ldap_get_option (option=" << option << ") "; throw LDAPException(ss.str(), ret); } } LDAPConnection::~LDAPConnection() { ldap_unbind_ext(m_ldap, NULL, NULL); } class MessageVals { berval** m_vals; public: MessageVals(berval **vals) : m_vals(vals) { } ~MessageVals() { if (m_vals) ldap_value_free_len(m_vals); } BerValue *& operator *() { return *m_vals; } BerValue *& operator [](size_t off) { return m_vals[off]; } operator bool() { return m_vals; } }; LDAPMessage * LDAPConnection::search(const std::string &base_dn, int scope, const std::string &filter, const std::string& attr) { const char *attrs[] = { attr.c_str(), NULL }; return search(base_dn, scope, filter, attrs); } LDAPMessage * LDAPConnection::search(const std::string &base_dn, int scope, const std::string &filter, const std::vector& attr) { std::vector v_chptr; for (unsigned int i = 0; i < attr.size(); i++) { char *p = const_cast(attr[i].c_str()); v_chptr.push_back(p); } v_chptr.push_back(NULL); char **vattr = &v_chptr[0]; return search(base_dn, scope, filter, const_cast(vattr)); } LDAPMessage * LDAPConnection::search(const std::string &base_dn, int scope, const std::string &filter, const char *attrs[]) { LDAPMessage * mesg; VERBOSEldap("Calling ldap_search_ext_s"); VERBOSEldap("ldap_search_ext_s base context: %s", base_dn.c_str()); VERBOSEldap("ldap_search_ext_s filter: %s", filter.c_str()); int ret = ldap_search_ext_s(m_ldap, base_dn.c_str(), scope, filter.c_str(), const_cast(attrs), 0, NULL, NULL, NULL, -1, &mesg); if (ret) { print_diagnostics("ldap_search_ext_s failed", ret); throw LDAPException("ldap_search_ext_s", ret); } return mesg; } LDAPMessage *LDAPConnection::first_entry(LDAPMessage *mesg) { return mesg = ldap_first_entry(m_ldap, mesg); } std::string LDAPConnection::get_one_val(LDAPMessage *mesg, const std::string& name) { MessageVals vals = ldap_get_values_len(m_ldap, mesg, name.c_str()); if (vals) { if (vals[0]) { berval *val = vals[0]; return std::string(val->bv_val, val->bv_len); } } return ""; } std::vector LDAPConnection::get_all_vals(LDAPMessage *mesg, const std::string& name) { MessageVals vals = ldap_get_values_len(m_ldap, mesg, name.c_str()); std::vector < std::string > ret; if (vals) { size_t i = 0; while (berval *val = vals[i]) { ret.push_back(std::string(val->bv_val, val->bv_len)); i++; } } return ret; } int LDAPConnection::count_entries(LDAPMessage *mesg) { return ldap_count_entries(m_ldap, mesg); } int LDAPConnection::modify_ext(const std::string &dn, const std::string& type, char *vals[], int op, bool check) { LDAPMod *mod_attrs[2]; LDAPMod attr; int ret; mod_attrs[0] = &attr; attr.mod_op = op; attr.mod_type = const_cast(type.c_str()); attr.mod_values = vals; mod_attrs[1] = NULL; VERBOSEldap("Calling ldap_modify_ext_s"); ret = ldap_modify_ext_s(m_ldap, dn.c_str(), mod_attrs, NULL, NULL); if (check && ret != LDAP_SUCCESS) { VERBOSE("ldap_modify_ext_s failed: %s", ldap_err2string(ret)); } return ret; } int LDAPConnection::remove_attr(const std::string &dn, const std::string& type, const std::string& val) { char *vals_name[] = { NULL, NULL }; vals_name[0] = const_cast(val.c_str()); return modify_ext(dn, type, vals_name, LDAP_MOD_DELETE, true); } int LDAPConnection::add_attr(const std::string &dn, const std::string& type, const std::string& val) { char *vals_name[] = { NULL, NULL }; vals_name[0] = const_cast(val.c_str()); return modify_ext(dn, type, vals_name, LDAP_MOD_ADD, true); } int LDAPConnection::simple_set_attr(const std::string &dn, const std::string &type, const std::string &val) { char *vals_name[] = { NULL, NULL }; vals_name[0] = const_cast(val.c_str()); return modify_ext(dn, type, vals_name, LDAP_MOD_REPLACE, true); } int LDAPConnection::flush_attr_no_check(const std::string &dn, const std::string &type) { char *vals[] = { NULL }; return modify_ext(dn, type, vals, LDAP_MOD_REPLACE, false); } int LDAPConnection::add(const std::string &dn, const LDAP_mod& mod) { std::vector tmp = mod.get(); tmp.push_back(NULL); int ret = ldap_add_ext_s(m_ldap, dn.c_str(), const_cast(&tmp[0]), NULL, NULL); if (ret) { print_diagnostics("ldap_add_ext_s failed", ret); throw LDAPException("ldap_add_ext_s", ret); } return ret; } int LDAPConnection::del(const std::string &dn) { int ret = ldap_delete_ext_s(m_ldap, dn.c_str(), NULL, NULL); if (ret) { print_diagnostics("ldap_delete_ext_s failed", ret); throw LDAPException("ldap_delete_ext_s", ret); } return ret; } void LDAP_mod::add(const std::string& type, const std::string& val, bool ucs) { LDAPMod *lm = new LDAPMod; lm->mod_type = strdup(type.c_str()); if (ucs == false) { char **mv = new char *[2]; mv[0] = strdup(val.c_str()); mv[1] = NULL; lm->mod_values = mv; lm->mod_op = LDAP_MOD_ADD; } else { lm->mod_op = LDAP_MOD_ADD | LDAP_MOD_BVALUES; lm->mod_bvalues = new BerValue *[2]; lm->mod_bvalues[0] = new BerValue; lm->mod_bvalues[0]->bv_val = new char[(val.length()) * 2]; memset(lm->mod_bvalues[0]->bv_val, 0, val.length() * 2); for (unsigned int i = 0; i < val.length(); i++) { lm->mod_bvalues[0]->bv_val[i * 2] = val[i]; } lm->mod_bvalues[0]->bv_len = (val.length()) * 2; lm->mod_bvalues[1] = NULL; } attrs.push_back(lm); } void LDAP_mod::add(const std::string& type, const std::vector& val) { LDAPMod *lm = new LDAPMod; lm->mod_op = LDAP_MOD_ADD; lm->mod_type = strdup(type.c_str()); char **mv = new char *[val.size() + 1]; for (unsigned int i = 0; i < val.size(); i++) { mv[i] = strdup(val[i].c_str()); } mv[val.size()] = NULL; lm->mod_values = mv; attrs.push_back(lm); } LDAP_mod::~LDAP_mod() { for (std::vector::iterator ptr = attrs.begin(); ptr != attrs.end(); ptr++) { if (*ptr) { LDAPMod *lm = *ptr; free(lm->mod_type); if (lm->mod_op & LDAP_MOD_BVALUES) { BerValue **p = lm->mod_bvalues; while (*p != NULL) { delete[] (*p)->bv_val; p++; } delete[] lm->mod_bvalues; } else { char **p = lm->mod_values; while (*p != NULL) { free(*p++); } delete[] lm->mod_values; } } } attrs.clear(); } std::vector LDAP_mod::get() const { return attrs; } msktutil-1.2.2/ldapconnection.h000066400000000000000000000066371467024531100165600ustar00rootroot00000000000000/* *---------------------------------------------------------------------------- * * ldapconnection.h * * (C) 2004-2006 Dan Perry (dperry@pppl.gov) * (C) 2006 Brian Elliott Finley (finley@anl.gov) * (C) 2009-2010 Doug Engert (deengert@anl.gov) * (C) 2010 James Y Knight (foom@fuhm.net) * (C) 2010-2013 Ken Dreyer * (C) 2012-2017 Mark Proehl * (C) 2012-2017 Olaf Flebbe * (C) 2013-2017 Daniel Kobras * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *----------------------------------------------------------------------------- */ #ifndef LDAPCONNECTION_H #define LDAPCONNECTION_H 1 #include #include #include #ifdef HAVE_SASL_H #include #else #include #endif class LDAP_mod { std::vector attrs; public: void add(const std::string& type); void add(const std::string& type, const std::string& val, bool ucs = false); void add(const std::string& type, const std::vector& val); std::vector get() const; ~LDAP_mod(); }; class LDAPConnection { private: LDAP *m_ldap; int modify_ext(const std::string &dn, const std::string& type, char *vals[], int op, bool check); public: LDAPConnection(const std::string &server, const std::string &sasl_mechanisms, bool no_reverse_lookups = false); void set_option(int option, const void *invalue); void get_option(int option, void *outvalue); bool is_connected() const { return m_ldap != NULL; }; LDAPMessage *search( const std::string &base_dn, int scope, const std::string &filter, const std::string& attr); LDAPMessage *search( const std::string &base_dn, int scope, const std::string &filter, const char *attr[]); LDAPMessage *search( const std::string &base_dn, int scope, const std::string &filter, const std::vector& attr); LDAPMessage *first_entry(LDAPMessage *mesg); int add_attr(const std::string &dn, const std::string &attrName, const std::string &val); int simple_set_attr(const std::string &dn, const std::string &attrName, const std::string &val); int remove_attr(const std::string &dn, const std::string& type, const std::string& name); int flush_attr_no_check(const std::string &dn, const std::string& type); int add(const std::string &dn, const LDAP_mod& mod); int del(const std::string &dn); void print_diagnostics(const char *msg, int err); std::string get_one_val(LDAPMessage *msg, const std::string& name); int count_entries(LDAPMessage *msg); std::vector get_all_vals(LDAPMessage *msg, const std::string& name); ~LDAPConnection(); }; #endif msktutil-1.2.2/msktconf.cpp000066400000000000000000000306341467024531100157310ustar00rootroot00000000000000/* *---------------------------------------------------------------------------- * * msktconf.cpp * * (C) 2004-2006 Dan Perry (dperry@pppl.gov) * (C) 2006 Brian Elliott Finley (finley@anl.gov) * (C) 2009-2010 Doug Engert (deengert@anl.gov) * (C) 2010 James Y Knight (foom@fuhm.net) * (C) 2010-2013 Ken Dreyer * (C) 2012-2017 Mark Proehl * (C) 2012-2017 Olaf Flebbe * (C) 2013-2017 Daniel Kobras * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *----------------------------------------------------------------------------- */ #include "msktutil.h" #include #include std::string create_default_machine_password(const std::string &sAMAccountName) { std::string machine_password(sAMAccountName); /* Default machine password after 'reset account' is created with the * following algorithm: * * 1) Remove trailing $ from sAMAcountName * 2) Truncate to first 14 characters * 3) Convert all characters to lowercase * */ /* Remove trailing '$' */ if (machine_password[machine_password.size() - 1] == '$') { machine_password.resize(machine_password.size() - 1); } /* Truncate to first 14 characters */ if (machine_password.size() > MAX_DEF_MACH_PASS_LEN) { machine_password.resize(MAX_DEF_MACH_PASS_LEN); } /* Convert all characters to lowercase */ for (size_t i = 0; i < machine_password.size(); i++) { machine_password[i] = std::tolower(machine_password[i]); } VERBOSE("Default machine password for %s is %s", sAMAccountName.c_str(), machine_password.c_str()); return machine_password; } /* Filenames to delete on exit (temporary config / ccaches) */ static std::string g_config_filename; static std::string g_ccache_filename; std::string get_tempfile_name(const char *name) { std::string full_template = sform("%s/%s-XXXXXX", TMP_DIR, name); char *template_arr = strdup(full_template.c_str()); int fd = mkstemp(template_arr); if (fd < 0) { error_exit("mkstemp failed"); } /* Didn't need an fd, just to have the filename created securely. */ close(fd); std::string tempfile_name = std::string(template_arr); free(template_arr); return tempfile_name; } void create_fake_krb5_conf(msktutil_flags *flags) { g_config_filename = get_tempfile_name(".msktkrb5.conf"); std::ofstream file(g_config_filename.c_str()); file << "[libdefaults]\n" << " default_realm = " << flags->realm_name << "\n" << " dns_lookup_kdc = true\n" << " udp_preference_limit = 1\n" << " default_ccache_name = " << KRB5CCache::defaultName() << "\n"; if (flags->allow_weak_crypto) { file << " allow_weak_crypto = true\n"; } if (flags->enctypes == VALUE_ON) { file << " default_tkt_enctypes ="; if (flags->supportedEncryptionTypes & 0x1) { file << " des-cbc-crc"; } if (flags->supportedEncryptionTypes & 0x2) { file << " des-cbc-md5"; } if (flags->supportedEncryptionTypes & 0x4) { file << " arcfour-hmac-md5"; } if (flags->supportedEncryptionTypes & 0x8) { file << " aes128-cts"; } if (flags->supportedEncryptionTypes & 0x10) { file << " aes256-cts"; } file << "\n"; } if ((flags->no_reverse_lookups) || (flags->no_canonical_name)) { file << " rdns = false\n"; } file << "[realms]\n" << " " << flags->realm_name << " = {\n" << " kdc = " << flags->server << "\n" << " admin_server = " << flags->server << "\n" << " }\n"; file.close(); #ifdef HAVE_SETENV int ret = setenv("KRB5_CONFIG", g_config_filename.c_str(), 1); if (ret) { error_exit("setenv failed"); } #else int ret = putenv(strdup((std::string("KRB5_CONFIG=") + g_config_filename).c_str())); if (ret) { error_exit("putenv failed"); } #endif VERBOSE("Created fake krb5.conf file: %s", g_config_filename.c_str()); destroy_g_context(); initialize_g_context(); } void remove_fake_krb5_conf() { if (!g_config_filename.empty()) { unlink(g_config_filename.c_str()); g_config_filename.clear(); } } void remove_ccache() { if (!g_ccache_filename.empty()) { unlink(g_ccache_filename.c_str()); g_ccache_filename.clear(); } } void switch_default_ccache(const char *ccache_name) { VERBOSE("Using the local credential cache: %s", ccache_name); /* Is this setenv really necessary given krb5_cc_set_default_name? * ...answer: YES, because LDAP's SASL won't be using our context * object, and may in fact be using a different implementation of * Kerberos entirely! */ #ifdef HAVE_SETENV if (setenv("KRB5CCNAME", ccache_name, 1)) { error_exit("setenv failed"); } #else if (!putenv(strdup((std::string("KRB5CCNAME=")+ ccache_name).c_str()))) { error_exit("putenv failed"); } #endif krb5_cc_set_default_name(g_context, ccache_name); } bool try_machine_keytab_princ(msktutil_flags *flags, const std::string &principal_name, const char *ccache_name) { try { VERBOSE("Trying to authenticate %s from local keytab", principal_name.c_str()); KRB5Keytab keytab(flags->keytab_readname); KRB5Principal principal(principal_name); KRB5Creds creds(principal, keytab); KRB5CCache ccache(ccache_name); ccache.initialize(principal); ccache.store(creds); switch_default_ccache(ccache_name); return true; } catch (KRB5Exception &e) { VERBOSE("%s", e.what()); VERBOSE("Authentication with keytab failed"); return false; } } bool try_machine_password(msktutil_flags *flags, const char *ccache_name) { try { VERBOSE("Trying to authenticate %s with password", flags->sAMAccountName.c_str()); KRB5Principal principal(flags->sAMAccountName); KRB5Creds creds(principal, /*password:*/ create_default_machine_password(flags->sAMAccountName)); KRB5CCache ccache(ccache_name); ccache.initialize(principal); ccache.store(creds); switch_default_ccache(ccache_name); return true; } catch (KRB5Exception &e) { VERBOSE("%s", e.what()); VERBOSE("Authentication with password failed"); return false; } } bool try_machine_supplied_password(msktutil_flags *flags, const char *ccache_name) { try { VERBOSE("Trying to authenticate %s with supplied password", flags->sAMAccountName.c_str()); KRB5Principal principal(flags->sAMAccountName); KRB5Creds creds(principal, /*password:*/ flags->old_account_password); KRB5CCache ccache(ccache_name); ccache.initialize(principal); ccache.store(creds); switch_default_ccache(ccache_name); return true; } catch (KRB5Exception &e) { VERBOSE("%s", e.what()); if (e.err() == KRB5KDC_ERR_KEY_EXP) { VERBOSE("Password needs to be changed"); flags->password_expired = true; return false; } else { VERBOSE("Authentication with supplied password failed"); return false; } } } bool get_creds(msktutil_flags *flags) { g_ccache_filename = get_tempfile_name(".mskt_krb5_ccache"); std::string ccache_name = "FILE:" + g_ccache_filename; try { KRB5Principal principal(flags->sAMAccountName); KRB5Creds creds(principal, /*password:*/ flags->password); KRB5CCache ccache(ccache_name.c_str()); ccache.initialize(principal); ccache.store(creds); switch_default_ccache(ccache_name.c_str()); return true; } catch (KRB5Exception &e) { VERBOSE("%s", e.what()); VERBOSE("Authentication with password failed"); return false; } } bool try_user_creds() { try { VERBOSE("Checking if default ticket cache has tickets"); /* The following is for the side effect of throwing an * exception or not. */ KRB5CCache ccache(KRB5CCache::defaultName()); KRB5Principal princ(ccache); return true; } catch(KRB5Exception &e) { VERBOSE("%s", e.what()); VERBOSE("Default ticket cache was not valid"); return false; } } int find_working_creds(msktutil_flags *flags) { /* We try some different ways, in order: * 1) Use principal from keytab. Try both: * a) sAMAccountName * b) host/full-hostname (for compat with older msktutil which * didn't write the first). * 2) Use principal sAMAccountName with default password * (sAMAccountName_nodollar) * 3) Use supplied credentials (--old-account-password) * When the supplied password has expired (e.g. because * the service account has been newly created) we cannot find * any working credentials here and have to return * AUTH_FROM_SUPPLIED_EXPIRED_PASSWORD. * In this case working credentials need to be obtained * after changing password * 4) Calling user's existing credentials from their credential * cache. */ if (!flags->user_creds_only) { std::string host_princ = "host/" + flags->hostname; /* * NOTE: we have to use an actual file for the credential * cache, and not a MEMORY: type, because libsasl may be using * Heimdal, while this program may be compiled against MIT * Kerberos. So, while it's all in the same process and you'd * think an in-mem ccache would be the right thing, the two * Kerberos implementations cannot share an in-memory ccache, * so we have to use a file. Sigh. */ g_ccache_filename = get_tempfile_name(".mskt_krb5_ccache"); std::string ccache_name = "FILE:" + g_ccache_filename; if (!flags->keytab_auth_princ.empty() && access(flags->keytab_file.c_str(), R_OK) == 0 && try_machine_keytab_princ(flags, flags->keytab_auth_princ, ccache_name.c_str())) { return AUTH_FROM_EXPLICIT_KEYTAB; } if (access(flags->keytab_file.c_str(), R_OK) == 0 && try_machine_keytab_princ(flags, flags->sAMAccountName, ccache_name.c_str())) { return AUTH_FROM_SAM_KEYTAB; } if (access(flags->keytab_file.c_str(), R_OK) == 0 && try_machine_keytab_princ(flags, flags->sAMAccountName_uppercase, ccache_name.c_str())) { return AUTH_FROM_SAM_UPPERCASE_KEYTAB; } if (access(flags->keytab_file.c_str(), R_OK) == 0 && try_machine_keytab_princ(flags, host_princ, ccache_name.c_str())) { return AUTH_FROM_HOSTNAME_KEYTAB; } if (try_machine_password(flags, ccache_name.c_str())) { return AUTH_FROM_PASSWORD; } if (strlen(flags->old_account_password.c_str())) { if (try_machine_supplied_password(flags, ccache_name.c_str())) { return AUTH_FROM_SUPPLIED_PASSWORD; } if (flags->password_expired) { return AUTH_FROM_SUPPLIED_EXPIRED_PASSWORD; } } } if (try_user_creds()) { return AUTH_FROM_USER_CREDS; } return AUTH_NONE; } msktutil-1.2.2/msktkrb5.cpp000066400000000000000000000377301467024531100156530ustar00rootroot00000000000000/* *---------------------------------------------------------------------------- * * msktkrb5.cpp * * (C) 2004-2006 Dan Perry (dperry@pppl.gov) * (C) 2006 Brian Elliott Finley (finley@anl.gov) * (C) 2009-2010 Doug Engert (deengert@anl.gov) * (C) 2010 James Y Knight (foom@fuhm.net) * (C) 2010-2013 Ken Dreyer * (C) 2012-2017 Mark Proehl * (C) 2012-2017 Olaf Flebbe * (C) 2013-2017 Daniel Kobras * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *----------------------------------------------------------------------------- */ #include "msktutil.h" #include #include void get_default_keytab(msktutil_flags *flags) { char keytab_name[MAX_KEYTAB_NAME_LEN]; if (flags->keytab_file.empty()) { /* Only set the field to a default if it's empty */ krb5_error_code ret = krb5_kt_default_name(g_context, keytab_name, MAX_KEYTAB_NAME_LEN); if (ret) { throw KRB5Exception("krb5_kt_default_name (get_default_keytab)", ret); } flags->keytab_readname = std::string(keytab_name); #ifdef HEIMDAL ret = krb5_kt_default_modify_name(g_context, keytab_name, MAX_KEYTAB_NAME_LEN); if (ret) { throw KRB5Exception("krb5_kt_default_modify_name " "(get_default_keytab)", ret); } flags->keytab_writename = std::string(keytab_name); if (!strncmp(keytab_name, "FILE:", 5)) { /* Ignore opening FILE: part */ flags->keytab_file = std::string(keytab_name + 5); } else { /* untyped or unsupported--just try to use it as * a filename for now */ flags->keytab_file = std::string(keytab_name); } #else if (!strncmp(keytab_name, "FILE:", 5)) { /* Ignore opening FILE: part */ flags->keytab_writename = "WRFILE:" + std::string(keytab_name + 5); } else if (!strncmp(keytab_name, "WRFILE:", 7)) { /* Already includes the opening WRFILE: part */ flags->keytab_writename = std::string(keytab_name); } else { /* No prefix to the keytab path */ flags->keytab_writename = "WRFILE:" + std::string(keytab_name); } /* Ignore opening WRFILE: part */ flags->keytab_file = flags->keytab_writename.substr(7); #endif VERBOSE("Obtaining the default keytab name: %s", flags->keytab_readname.c_str()); } else { flags->keytab_writename = "WRFILE:" + flags->keytab_file; flags->keytab_readname = "FILE:" + flags->keytab_file; } } /* Salt creation -- see windows-salt.txt */ std::string get_salt(msktutil_flags *flags) { std::string salt; if (flags->use_service_account) { if (flags->userPrincipalName.empty()) { salt = sform("%s%s", flags->realm_name.c_str(), flags->sAMAccountName.c_str()); } else { std::string upnsalt = flags->userPrincipalName; upnsalt.erase(std::remove(upnsalt.begin(), upnsalt.end(), '/'), upnsalt.end()); salt = sform("%s%s", flags->realm_name.c_str(), upnsalt.c_str()); } } else { std::string lower_accountname = flags->sAMAccountName_nodollar; for(std::string::iterator it = lower_accountname.begin(); it != lower_accountname.end(); ++it) { *it = std::tolower(*it); } salt = sform("%shost%s.%s", flags->realm_name.c_str(), lower_accountname.c_str(), flags->lower_realm_name.c_str()); } VERBOSE("Using salt: %s", salt.c_str()); return(salt); } int flush_keytab(msktutil_flags *flags) { VERBOSE("Flushing the keytab"); KRB5Keytab keytab(flags->keytab_writename); std::vector keytab_entries; /* Extract a vector of keytab entries */ for (KRB5Keytab::cursor cursor(keytab); cursor.next(); ) { keytab_entries.push_back(cursor); } for (std::vector::iterator it = keytab_entries.begin(); it != keytab_entries.end(); it++) { KRB5Principal principal(it->principal()); std::string principal_name(principal.name()); size_t first_chr = principal_name.find('/') + 1; size_t last_chr = principal_name.rfind('@'); std::string host = principal_name.substr(first_chr, last_chr - first_chr); if (host != flags->hostname) { continue; } VERBOSE("Deleting %s (kvno=%d, enctype=%d) from keytab", principal_name.c_str(), it->kvno(), it->enctype()); keytab.removeEntry(principal, it->kvno(), it->enctype()); } return ldap_flush_principals(flags); } void cleanup_keytab(msktutil_flags *flags) { VERBOSE("Cleaning the keytab"); KRB5Keytab keytab(flags->keytab_writename); std::vector keytab_entries; /* Extract a vector of keytab entries */ for (KRB5Keytab::cursor cursor(keytab); cursor.next(); ) { keytab_entries.push_back(cursor); } /* cleanup all entries that match --remove-enctype */ for (std::vector::iterator it = keytab_entries.begin(); it != keytab_entries.end(); it++) { if (it->enctype() != flags->cleanup_enctype) { continue; } KRB5Principal principal(it->principal()); VERBOSE("Deleting %s with kvno=%d, enctype=%d from keytab", principal.name().c_str(), it->kvno(), it->enctype()); keytab.removeEntry(principal, it->kvno(), it->enctype()); } /* stop further processing unless --remove-old was given */ if (flags->cleanup_days == -1) { return; } /* Sort vector by timestamp in descending order */ std::sort(keytab_entries.rbegin(), keytab_entries.rend()); std::vector::iterator it = keytab_entries.begin(); /* Empty list? Nothing to do */ if (it == keytab_entries.end()) return; /* kvno of the first (== most recent) entry */ /* FIXME this will work nicely for multiple principals derived from the * same account/password, but what about multiple independent principals * within the same keytab? We could run the cleanup separately for each * principal, but then there's no way to get rid of truly obsolete * principals. */ krb5_kvno keep_kvno = it->kvno(); time_t min_keep_timestamp = time(0) - flags->cleanup_days * 60 * 60 * 24; for (; it != keytab_entries.end(); it++) { if (it->timestamp() > min_keep_timestamp || it->kvno() == keep_kvno) { continue; } KRB5Principal principal(it->principal()); VERBOSE("Deleting %s (kvno=%d, enctype=%d) from keytab", principal.name().c_str(), it->kvno(), it->enctype()); keytab.removeEntry(principal, it->kvno(), it->enctype()); } } void remove_keytab_entries(msktutil_flags *flags, std::vector remove_principals) { KRB5Keytab keytab(flags->keytab_writename); VERBOSE("Trying to remove entries for %s from keytab", flags->sAMAccountName.c_str()); std::vector keytab_entries; /* Extract a vector of keytab entries */ for (KRB5Keytab::cursor cursor(keytab); cursor.next(); ) { keytab_entries.push_back(cursor); } for (std::vector::iterator it = keytab_entries.begin(); it != keytab_entries.end(); it++) { KRB5Principal principal(it->principal()); std::string principal_name(principal.name()); for (size_t i = 0; i < remove_principals.size(); ++i) { std::string remove_principal = remove_principals[i] + "@" + flags->realm_name; if (principal_name.compare(remove_principal) == 0) { VERBOSE("Deleting %s (kvno=%d, enctype=%d) from keytab", principal.name().c_str(), it->kvno(), it->enctype()); keytab.removeEntry(principal, it->kvno(), it->enctype()); } } } } void add_keytab_entries(msktutil_flags *flags) { KRB5Keytab keytab(flags->keytab_writename); VERBOSE("Trying to add missing entries for %s to keytab", flags->sAMAccountName.c_str()); std::vector keytab_entries; std::vector sam_entries; std::string template_principal = flags->sAMAccountName + "@" + flags->realm_name; /* Extract a vector of keytab entries */ for (KRB5Keytab::cursor cursor(keytab); cursor.next(); ) { keytab_entries.push_back(cursor); if (cursor.principal().name().compare(template_principal) == 0) { sam_entries.push_back(cursor); } } for (size_t i = 0; i < flags->ad_principals.size(); ++i) { /* We look at all keytab entries that match the account name * and fetch their kvno, enctype and key. If an entry for the * principal that needs to be added already exists, we do * nothing. If not, we are adding it by using the fetched keys * from the account name entry. Doing it this way we are able * to add service principals without changing the account * password. */ VERBOSE("Checking if %s needs to be added to keytab", flags->ad_principals[i].c_str()); std::string add_principal = flags->ad_principals[i] + "@" + flags->realm_name; for (std::vector::iterator sam = sam_entries.begin(); sam != sam_entries.end(); sam++) { std::vector::iterator it = keytab_entries.begin(); for (; it != keytab_entries.end(); it++) { if (sam->kvno() != it->kvno() || sam->enctype() != it->enctype()) { continue; } if (add_principal.compare(KRB5Principal(it->principal()).name()) == 0) { break; } } if (it != keytab_entries.end()) { /* Matching entry already present for this kvno and enctype */ continue; } VERBOSE("Adding %s (kvno=%d, enctype=%d) to keytab", add_principal.c_str(), sam->kvno(), sam->enctype()); KRB5Principal princ(add_principal); krb5_keyblock keyblock(sam->keyblock()); keytab.addEntry(princ, sam->kvno(), keyblock); } } } void update_keytab(msktutil_flags *flags) { VERBOSE("Updating all entries for %s", flags->sAMAccountName.c_str()); add_principal_keytab(flags->sAMAccountName, flags); if (!flags->use_service_account) { add_principal_keytab(flags->sAMAccountName_uppercase, flags); } /* add upn */ if (!flags->userPrincipalName.empty()) { add_principal_keytab(flags->userPrincipalName, flags); } for (size_t i = 0; i < flags->ad_principals.size(); ++i) { if ((flags->userPrincipalName.empty()) || flags->userPrincipalName.compare(flags->ad_principals[i]) != 0) { add_principal_keytab(flags->ad_principals[i], flags); } else { VERBOSE("Entries for SPN %s have already been added. Skipping ...", flags->ad_principals[i].c_str() ); } } } static void prune_keytab(KRB5Keytab& keytab, KRB5Principal &principal, krb5_timestamp min_keep_timestamp) { std::vector keytab_entries; /* Extract a vector of keytab entries for this principal */ for (KRB5Keytab::cursor cursor(keytab); cursor.next(); ) { if (cursor.principal().name() != principal.name()) continue; keytab_entries.push_back(cursor); } /* Sort vector by timestamp in descending order */ std::sort(keytab_entries.rbegin(), keytab_entries.rend()); /* Now find the first entry older than min_keep_timestamp */ std::vector::iterator it = keytab_entries.begin(); for (; it != keytab_entries.end(); it++) { if (it->timestamp() < min_keep_timestamp) break; } if (it == keytab_entries.end()) return; /* Keys for this kvno may still be valid, but any older entries for * different keys (different kvno) have definitely been stale for more * than min_keep_timestamp, and can therefore be pruned. */ krb5_kvno keep_kvno = it->kvno(); for (; it != keytab_entries.end(); it++) { if (it->kvno() == keep_kvno) continue; KRB5Principal principal(it->principal()); VERBOSE("Deleting %s (kvno=%d, enctype=%d) from keytab", principal.name().c_str(), it->kvno(), it->enctype()); keytab.removeEntry(principal, it->kvno(), it->enctype()); } } void add_principal_keytab(const std::string &principal, msktutil_flags *flags) { std::string principal_string = ""; if (principal.find("@") != std::string::npos) { principal_string = sform("%s", principal.c_str()); } else { principal_string = sform("%s@%s", principal.c_str(), flags->realm_name.c_str()); } VERBOSE("Adding principal to keytab: %s", principal_string.c_str()); VERBOSE("Using supportedEncryptionTypes: %d", flags->ad_supportedEncryptionTypes); /* FIXME: Why do we use a fixed magic number instead of reusing * flags->cleanup_days for update as well? */ krb5_timestamp min_keep_timestamp = time(NULL) - (7*24*60*60); KRB5Principal princ(principal_string); KRB5Keytab keytab(flags->keytab_writename); prune_keytab(keytab, princ, min_keep_timestamp); std::vector enc_types; if (flags->ad_supportedEncryptionTypes & MS_KERB_ENCTYPE_DES_CBC_CRC) { enc_types.push_back(ENCTYPE_DES_CBC_CRC); } if (flags->ad_supportedEncryptionTypes & MS_KERB_ENCTYPE_DES_CBC_MD5) { enc_types.push_back(ENCTYPE_DES_CBC_MD5); } if (flags->ad_supportedEncryptionTypes & MS_KERB_ENCTYPE_RC4_HMAC_MD5) { enc_types.push_back(ENCTYPE_ARCFOUR_HMAC); } #if HAVE_DECL_ENCTYPE_AES128_CTS_HMAC_SHA1_96 if (flags->ad_supportedEncryptionTypes & MS_KERB_ENCTYPE_AES128_CTC_HMAC_SHA1_96) { enc_types.push_back(ENCTYPE_AES128_CTS_HMAC_SHA1_96); } #endif #if HAVE_DECL_ENCTYPE_AES256_CTS_HMAC_SHA1_96 if (flags->ad_supportedEncryptionTypes & MS_KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96) { enc_types.push_back(ENCTYPE_AES256_CTS_HMAC_SHA1_96); } #endif std::string salt = get_salt(flags); std::string password = flags->dont_change_password ? flags->old_account_password : flags->password; if (password.empty()) { VERBOSE("No password available, skipping creation " "of password-based keytab entries"); } else { for(size_t i = 0; i < enc_types.size(); ++i) { VERBOSE(" Adding entry of enctype 0x%x", enc_types[i]); keytab.addEntry(princ, flags->kvno, static_cast(enc_types[i]), password, salt); } } } msktutil-1.2.2/msktldap.cpp000066400000000000000000000644021467024531100157240ustar00rootroot00000000000000/* *---------------------------------------------------------------------------- * * msktldap.cpp * * (C) 2004-2006 Dan Perry (dperry@pppl.gov) * (C) 2006 Brian Elliott Finley (finley@anl.gov) * (C) 2009-2010 Doug Engert (deengert@anl.gov) * (C) 2010 James Y Knight (foom@fuhm.net) * (C) 2010-2013 Ken Dreyer * (C) 2012-2017 Mark Proehl * (C) 2012-2017 Olaf Flebbe * (C) 2013-2017 Daniel Kobras * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *----------------------------------------------------------------------------- */ #include "msktutil.h" #include #include #include /* Check if string ends with string */ static bool endswith(std::string const &s, std::string const &suffix) { if (s.length() < suffix.length()) return false; return s.compare(s.length() - suffix.length(), suffix.length(), suffix) == 0; } void get_default_ou(msktutil_flags *flags) { /* If OU was given explicitly, we just need to make sure it's a * valid dn below our base dn. */ if (!flags->ldap_ou.empty()) { if (!endswith(flags->ldap_ou, flags->base_dn)) flags->ldap_ou = flags->ldap_ou + "," + flags->base_dn; VERBOSE("Using OU: %s", flags->ldap_ou.c_str()); return; } /* Otherwise, probe AD for its default OU */ LDAPConnection *ldap = flags->ldap; std::string wkguid; if (flags->use_service_account) { wkguid = sform("", flags->base_dn.c_str()); } else { wkguid = sform("", flags->base_dn.c_str()); } LDAPMessage *mesg = ldap->search(wkguid, LDAP_SCOPE_BASE, "objectClass=*", "distinguishedName"); std::string dn; if (ldap->count_entries(mesg) == 1) { mesg = ldap->first_entry(mesg); dn = ldap->get_one_val(mesg, "distinguishedName"); } ldap_msgfree(mesg); if (dn.empty()) { fprintf(stderr, "Warning: could not get default computer OU from AD.\n" ); std::string default_ou = flags->use_service_account ? "CN=Users," : "CN=Computers,"; flags->ldap_ou = default_ou + flags->base_dn; } else { flags->ldap_ou = dn; } VERBOSE("Determined default OU: %s", flags->ldap_ou.c_str()); } void ldap_get_base_dn(msktutil_flags *flags) { if (flags->base_dn.empty()) { std::string out; bool first = true; std::string &base = flags->realm_name; size_t last_pos = 0; do { size_t pos = base.find('.', last_pos); if (first) { out.append("dc="); first = false; } else out.append(",dc="); out.append(base.substr(last_pos, pos - last_pos)); last_pos = pos + 1; } while (last_pos != 0); flags->base_dn = out; VERBOSE("Determined default LDAP base: %s", flags->base_dn.c_str()); } } void ldap_cleanup(msktutil_flags *flags) { VERBOSE("Disconnecting from LDAP server"); delete flags->ldap; flags->ldap = NULL; } LDAPMessage* ldap_get_account_attrs(const msktutil_flags* flags, const char **attrs) { std::string filter = sform("(&(|(objectCategory=Computer)" "(objectCategory=User))(sAMAccountName=%s))", flags->sAMAccountName.c_str()); return flags->ldap->search(flags->base_dn, LDAP_SCOPE_SUBTREE, filter, attrs); } LDAPMessage* ldap_get_account_attrs(const msktutil_flags* flags, const std::string& attr) { std::string filter = sform("(&(|(objectCategory=Computer)" "(objectCategory=User))" "(sAMAccountName=%s))", flags->sAMAccountName.c_str()); return flags->ldap->search(flags->base_dn, LDAP_SCOPE_SUBTREE, filter, attr); } int ldap_flush_principals(msktutil_flags *flags) { std::string dn; int ret; VERBOSE("Flushing principals from LDAP entry"); LDAPMessage *mesg = ldap_get_account_attrs(flags, "distinguishedName"); if (flags->ldap->count_entries(mesg) == 1) { mesg = flags->ldap->first_entry(mesg); dn = flags->ldap->get_one_val(mesg, "distinguishedName"); } ldap_msgfree(mesg); if (dn.empty()) { fprintf(stderr, "Error: an account for %s was not found\n", flags->sAMAccountName.c_str() ); return -1; } ret = flags->ldap->flush_attr_no_check(dn, "servicePrincipalName"); /* Ignore if the attribute doesn't exist, that just means that * it's already empty */ if (ret != LDAP_SUCCESS && ret != LDAP_NO_SUCH_ATTRIBUTE) { flags->ldap->print_diagnostics("ldap_modify_ext_s failed", ret); return -1; } return 0; } krb5_kvno ldap_get_kvno(msktutil_flags *flags) { krb5_kvno kvno = KVNO_FAILURE; LDAPConnection *ldap = flags->ldap; LDAPMessage *mesg = ldap_get_account_attrs(flags, "msDS-KeyVersionNumber"); if (ldap->count_entries(mesg) == 1) { mesg = flags->ldap->first_entry(mesg); std::string kvno_str = flags->ldap->get_one_val(mesg, "msDS-KeyVersionNumber"); if (!kvno_str.empty()) kvno = (krb5_kvno) atoi(kvno_str.c_str()); else { /* This must be a Windows 2000 domain, which does support * have kvno's. */ kvno = KVNO_WIN_2000; VERBOSE("Unable to find kvno attribute on domain controller " "%s - This must be running Windows 2000", flags->server.c_str()); } } ldap_msgfree(mesg); VERBOSE("Found kvno: %d", kvno); return kvno; } std::string ldap_get_pwdLastSet(msktutil_flags *flags) { std::string pwdLastSet; const char *attrs[] = {"pwdLastSet", NULL}; LDAPConnection *ldap = flags->ldap; LDAPMessage *mesg = ldap_get_account_attrs(flags, attrs); if (ldap->count_entries(mesg) == 1) { mesg = ldap->first_entry(mesg); pwdLastSet = ldap->get_one_val(mesg, "pwdLastSet"); VERBOSE("pwdLastSet is %s", pwdLastSet.c_str()); } ldap_msgfree(mesg); return pwdLastSet; } int ldap_simple_set_attr(const std::string &dn, const std::string &attrName, const std::string &val, msktutil_flags *flags) { int ret = flags->ldap->simple_set_attr(dn, attrName, val); if (ret != LDAP_SUCCESS) { fprintf(stderr, "WARNING: LDAP modification of %s\n", dn.c_str()); fprintf(stderr, " failed while trying to change %s to %s.\n", attrName.c_str(), val.c_str()); fprintf(stderr, " Error was: %s\n", ldap_err2string(ret)); fprintf(stderr, " --> Do you have enough privileges?\n"); fprintf(stderr, " --> You might try re-\"kinit\"ing.\n"); if (!flags->user_creds_only) { fprintf(stderr, " --> Maybe you should try again with " "--user-creds-only?\n"); } if (attrName == "userPrincipalName") { fprintf(stderr, "ERROR: Can't continue with wrong UPN\n"); exit(1); } else { fprintf(stderr, " Continuing anyway ...\n"); } } return ret; } int ldap_set_supportedEncryptionTypes(const std::string &dn, msktutil_flags *flags) { int ret; if (flags->ad_supportedEncryptionTypes != flags->supportedEncryptionTypes) { std::string supportedEncryptionTypes = sform("%d", flags->supportedEncryptionTypes); ret = ldap_simple_set_attr(dn, "msDs-supportedEncryptionTypes", supportedEncryptionTypes, flags); if (ret == LDAP_SUCCESS) { flags->ad_enctypes = VALUE_ON; flags->ad_supportedEncryptionTypes = flags->supportedEncryptionTypes; } } else { VERBOSE("No need to change msDs-supportedEncryptionTypes they " "are %d", flags->ad_supportedEncryptionTypes); ret = LDAP_SUCCESS; } return ret; } int ldap_set_userAccountControl_flag(const std::string &dn, int mask, msktutil_val value, msktutil_flags *flags) { int ret; unsigned new_userAcctFlags; /* Skip this value if its not to change */ if (value == VALUE_IGNORE) { return 0; } new_userAcctFlags = flags->ad_userAccountControl; switch (value) { case VALUE_ON: VERBOSE("Setting userAccountControl bit at 0x%x to 0x%x", mask, value); new_userAcctFlags |= mask; break; case VALUE_OFF: VERBOSE("Setting userAccountControl bit at 0x%x to 0x%x", mask, value); new_userAcctFlags &= ~mask; break; case VALUE_IGNORE: /* Unreachable */ break; } if (new_userAcctFlags != flags->ad_userAccountControl) { std::string new_userAcctFlags_string = sform("%d", new_userAcctFlags); ret = flags->ldap->simple_set_attr(dn, "userAccountControl", new_userAcctFlags_string); if (ret == LDAP_SUCCESS) { flags->ad_userAccountControl = new_userAcctFlags; } } else { VERBOSE("userAccountControl not changed 0x%x", new_userAcctFlags); ret = LDAP_SUCCESS; } return ret; } int ldap_add_principal(const std::string &principal, msktutil_flags *flags) { int ret; LDAPConnection *ldap = flags->ldap; std::string dn = flags->ad_computerDn; VERBOSE("Checking that adding principal %s to %s won't cause a conflict", principal.c_str(), flags->sAMAccountName.c_str()); std::string filter = sform("(servicePrincipalName=%s)", principal.c_str()); LDAPMessage *mesg = ldap->search(flags->base_dn, LDAP_SCOPE_SUBTREE, filter, "distinguishedName"); int num_entries = ldap->count_entries(mesg); switch (num_entries) { case 0: VERBOSE("Adding principal %s to LDAP entry", principal.c_str()); ret = ldap->add_attr(dn, "servicePrincipalName", principal); if (ret != LDAP_SUCCESS) { fprintf(stderr, "WARNING: LDAP modification of %s\n", dn.c_str()); fprintf(stderr, " failed while trying to add servicePrincipalName %s.\n", principal.c_str()); fprintf(stderr, " Error was: %s\n", ldap_err2string(ret)); fprintf(stderr, " --> Do you have enough privileges?\n"); fprintf(stderr, " --> You might try re-\"kinit\"ing.\n"); if (!flags->user_creds_only) { fprintf(stderr, " --> Maybe you should try again with --user-creds-only?\n"); } } else { flags->ad_principals.push_back(principal); } break; case 1: { /* Check if we are the owner of the this principal or * not */ mesg = ldap->first_entry(mesg); std::string found_dn = flags->ldap->get_one_val(mesg, "distinguishedName"); if (found_dn.empty()) { fprintf(stderr, "Error: inconsistent LDAP entry: No DN value present\n" ); ret = -1; } else if (dn != found_dn) { fprintf(stderr, "Error: another computer account (%s) has the " "principal %s\n", found_dn.c_str(), principal.c_str() ); ret = -1; } else ret = 0; break; } default: fprintf(stderr, "Error: multiple (%d) LDAP entries were found containing " "the principal %s\n", num_entries, principal.c_str() ); ret = num_entries; } ldap_msgfree(mesg); return ret; } int ldap_remove_principal(const std::string &principal, msktutil_flags *flags) { VERBOSE("Removing servicePrincipalName %s from %s", principal.c_str(), flags->ad_computerDn.c_str()); int ret = flags->ldap->remove_attr(flags->ad_computerDn, "servicePrincipalName", principal); if (ret == LDAP_SUCCESS) { flags->ad_principals.erase( std::remove(flags->ad_principals.begin(), flags->ad_principals.end(), principal), flags->ad_principals.end() ); } return ret; } void ldap_check_account_strings(msktutil_flags *flags) { const std::string &dn = flags->ad_computerDn; LDAPConnection *ldap = flags->ldap; if (flags->use_service_account) { VERBOSE("Inspecting (and updating) service account attributes"); } else { VERBOSE("Inspecting (and updating) computer account attributes"); } /* NOTE: failures to set all the attributes in this function are * ignored, for better or worse... But failure to set * userPrincipalName is not ignored */ /* don't set dnsHostName on service accounts or if requested not to do it */ if (!flags->use_service_account && !flags->dont_update_dnshostname) { if (!flags->hostname.empty() && flags->hostname != flags->ad_dnsHostName) { ldap_simple_set_attr(dn, "dNSHostName", flags->hostname, flags); } } if (!flags->description.empty()) { ldap_simple_set_attr(dn, "description", flags->description, flags); } if (flags->set_userPrincipalName) { std::string userPrincipalName_string = ""; std::string upn_found = ""; const char *attrs[] = {"userPrincipalName", NULL}; if (flags->userPrincipalName.find("@") != std::string::npos) { userPrincipalName_string = sform("%s", flags->userPrincipalName.c_str()); } else { userPrincipalName_string = sform("%s@%s", flags->userPrincipalName.c_str(), flags->realm_name.c_str()); } /* let's see if userPrincipalName is already set to the * desired value in AD... */ LDAPMessage *mesg = ldap_get_account_attrs(flags, attrs); if (ldap->count_entries(mesg) == 1) { mesg = ldap->first_entry(mesg); upn_found = ldap->get_one_val(mesg, "userPrincipalName"); } ldap_msgfree(mesg); VERBOSE("Found userPrincipalName: %s", upn_found.c_str()); VERBOSE("userPrincipalName should be %s", userPrincipalName_string.c_str()); if (upn_found.compare(userPrincipalName_string)) { ldap_simple_set_attr(dn, "userPrincipalName", userPrincipalName_string, flags); } else { VERBOSE("Nothing to do"); } } ldap_set_supportedEncryptionTypes(dn, flags); msktutil_val des_only; if (flags->supportedEncryptionTypes == MS_KERB_DES_ENCTYPES) { des_only = VALUE_ON; } else { des_only = VALUE_OFF; } ldap_set_userAccountControl_flag(dn, UF_USE_DES_KEY_ONLY, des_only, flags); /* If msDS-supportedEncryptionTypes isn't set, ad_enctypes will be * VALUE_OFF. In that case, reset ad_supportedEncryptionTypes * according to the DES flag, in case we changed it. */ if (flags->ad_enctypes == VALUE_OFF) { flags->ad_supportedEncryptionTypes = MS_KERB_DES_ENCTYPES; if (!(flags->ad_userAccountControl & UF_USE_DES_KEY_ONLY)) { flags->ad_supportedEncryptionTypes |= MS_KERB_ENCTYPE_RC4_HMAC_MD5; } } ldap_set_userAccountControl_flag(dn, UF_NO_AUTH_DATA_REQUIRED, flags->no_pac, flags); ldap_set_userAccountControl_flag(dn, UF_TRUSTED_FOR_DELEGATION, flags->delegate, flags); ldap_set_userAccountControl_flag(dn, UF_DONT_EXPIRE_PASSWORD, flags->dont_expire_password, flags); ldap_set_userAccountControl_flag(dn, UF_ACCOUNT_DISABLE, flags->disable_account, flags); } template T * myend(T (&ra)[N]) { return ra + N; } bool ldap_check_account(msktutil_flags *flags) { LDAPMessage *mesg; const char *machine_attrs[] = {"distinguishedName", "dNSHostName", "msDs-supportedEncryptionTypes", "userAccountControl", "servicePrincipalName", "userPrincipalName", NULL}; const char *user_attrs[] = {"distinguishedName", "msDs-supportedEncryptionTypes", "userAccountControl", "servicePrincipalName", "userPrincipalName", "unicodePwd", NULL}; std::string dn; const char *vals_objectClass[] = {"top", "person", "organizationalPerson", "user"}; std::vector v_user_objectClass(vals_objectClass, myend(vals_objectClass)); std::vector v_machine_objectClass(vals_objectClass, myend(vals_objectClass)); v_machine_objectClass.push_back("computer"); LDAPConnection *ldap = flags->ldap; if (flags->use_service_account) { VERBOSE("Checking that a service account for %s exists", flags->sAMAccountName.c_str()); mesg = ldap_get_account_attrs(flags, user_attrs); } else { VERBOSE("Checking that a computer account for %s exists", flags->sAMAccountName.c_str()); mesg = ldap_get_account_attrs(flags, machine_attrs); } if (ldap->count_entries(mesg) == 0) { return false; } /* Account already exists */ if (flags->use_service_account) { VERBOSE("Found service account"); } else { VERBOSE("Found computer account"); } mesg = ldap->first_entry(mesg); flags->ad_computerDn = ldap->get_one_val(mesg, "distinguishedName"); std::string uac = ldap->get_one_val(mesg, "userAccountControl"); if (!uac.empty()) { flags->ad_userAccountControl = atoi(uac.c_str()); VERBOSE("Found userAccountControl: 0x%x", flags->ad_userAccountControl); } /* save the current msDs-supportedEncryptionTypes */ std::string supportedEncryptionTypes = flags->ldap->get_one_val(mesg, "msDs-supportedEncryptionTypes"); if (!supportedEncryptionTypes.empty()) { flags->ad_supportedEncryptionTypes = atoi(supportedEncryptionTypes.c_str()); flags->ad_enctypes = VALUE_ON; /* actual value found in AD */ VERBOSE("Found supportedEncryptionTypes: %d", flags->ad_supportedEncryptionTypes); } else { /* Not in current LDAP entry set defaults */ flags->ad_supportedEncryptionTypes = MS_KERB_DES_ENCTYPES; if (!(flags->ad_userAccountControl & UF_USE_DES_KEY_ONLY)) { flags->ad_supportedEncryptionTypes |= MS_KERB_ENCTYPE_RC4_HMAC_MD5; } flags->ad_enctypes = VALUE_OFF; /* this is the assumed default */ VERBOSE("Found default supportedEncryptionTypes: %d", flags->ad_supportedEncryptionTypes); } if (!flags->use_service_account) { /* Save current dNSHostName */ flags->ad_dnsHostName = ldap->get_one_val(mesg, "dNSHostName"); VERBOSE("Found dNSHostName: %s", flags->ad_dnsHostName.c_str()); } /* Save current servicePrincipalName and userPrincipalName * attrs */ if (ldap->count_entries(mesg) == 1) { mesg = ldap->first_entry(mesg); /* TODO Why first_entry ?? * already at first entry! */ std::vector vals = ldap->get_all_vals(mesg, "servicePrincipalName"); for (size_t i = 0; i < vals.size(); ++i) { /* translate HOST/ to host/ */ if (vals[i].compare(0, 5, "HOST/") == 0) { vals[i].replace(0, 5, "host/"); } flags->ad_principals.push_back(vals[i]); VERBOSE("Found servicePrincipalName: %s", vals[i].c_str()); } if (flags->set_userPrincipalName) { VERBOSE("User principal specified on command line"); } else { std::string upn = ldap->get_one_val(mesg, "userPrincipalName"); if (!upn.empty()) { VERBOSE("Found userPrincipalName: %s", upn.c_str()); size_t pos = upn.find('@'); if (pos != std::string::npos) { upn.erase(pos); } /* update userPrincipalName for salt generation */ flags->userPrincipalName = upn.c_str(); } } } ldap_msgfree(mesg); ldap_check_account_strings(flags); return true; } void ldap_create_account(msktutil_flags *flags) { const char *vals_objectClass[] = {"top", "person", "organizationalPerson", "user"}; std::vector v_user_objectClass(vals_objectClass, myend(vals_objectClass)); std::vector v_machine_objectClass(vals_objectClass, myend(vals_objectClass)); v_machine_objectClass.push_back("computer"); LDAPConnection *ldap = flags->ldap; /* No computer account found, so let's add one in the OU specified */ if (flags->use_service_account) { VERBOSE("Service account not found, creating one"); fprintf(stdout, "No service account for %s found, creating a new one.\n", flags->sAMAccountName.c_str() ); } else { VERBOSE("Computer account not found, create one"); fprintf(stdout, "No computer account for %s found, creating a new one.\n", flags->sAMAccountName_nodollar.c_str() ); } flags->ad_computerDn = sform("cn=%s,%s", flags->sAMAccountName_nodollar.c_str(), flags->ldap_ou.c_str()); LDAP_mod mod_attrs; if (flags->use_service_account) { mod_attrs.add("objectClass", v_user_objectClass); } else { mod_attrs.add("objectClass", v_machine_objectClass); } mod_attrs.add("cn", flags->sAMAccountName_nodollar); int userAcctFlags; if (flags->use_service_account) { userAcctFlags = UF_NORMAL_ACCOUNT; } else { userAcctFlags = UF_WORKSTATION_TRUST_ACCOUNT; } mod_attrs.add("userAccountControl", sform("%d", userAcctFlags)); mod_attrs.add("sAMAccountName", flags->sAMAccountName); mod_attrs.add("unicodePwd", "\"" + flags->password + "\"", true); ldap->add(flags->ad_computerDn, mod_attrs); /* Defaults, will attempt to reset later */ flags->ad_supportedEncryptionTypes = MS_KERB_DES_ENCTYPES | MS_KERB_ENCTYPE_RC4_HMAC_MD5; flags->ad_enctypes = VALUE_OFF; flags->ad_userAccountControl = userAcctFlags; ldap_check_account_strings(flags); } int ldap_delete_account(msktutil_flags *flags) { std::string dn; int ret; VERBOSE("Deleting LDAP entry of %s", flags->sAMAccountName.c_str()); LDAPMessage *mesg = ldap_get_account_attrs(flags, "distinguishedName"); if (flags->ldap->count_entries(mesg) == 1) { mesg = flags->ldap->first_entry(mesg); dn = flags->ldap->get_one_val(mesg, "distinguishedName"); } ldap_msgfree(mesg); if (dn.empty()) { fprintf(stderr, "Error: account for %s was not found\n", flags->sAMAccountName.c_str() ); return -1; } ret = flags->ldap->del(dn); return ret; } msktutil-1.2.2/msktname.cpp000066400000000000000000000332241467024531100157220ustar00rootroot00000000000000/* *---------------------------------------------------------------------------- * * msktname.cpp * * (C) 2004-2006 Dan Perry (dperry@pppl.gov) * (C) 2006 Brian Elliott Finley (finley@anl.gov) * (C) 2009-2010 Doug Engert (deengert@anl.gov) * (C) 2010 James Y Knight (foom@fuhm.net) * (C) 2010-2013 Ken Dreyer * (C) 2012-2017 Mark Proehl * (C) 2012-2017 Olaf Flebbe * (C) 2013-2017 Daniel Kobras * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *----------------------------------------------------------------------------- */ #include "msktutil.h" #include #include #include std::string complete_hostname(const std::string &hostname, bool no_canonical_name) { /* Ask the Kerberos library to canonicalize the hostname, and then * pull it out of the principal. */ int32_t type = KRB5_NT_SRV_HST; krb5_principal temp_princ_raw = NULL; /* do not canonicalize, use supplied hostname */ if (no_canonical_name) { type = KRB5_NT_UNKNOWN; } krb5_error_code ret = krb5_sname_to_principal(g_context, hostname.c_str(), "host", type, &temp_princ_raw); if (ret != 0) { fprintf(stderr, "Warning: hostname canonicalization for %s failed: %s\n", hostname.c_str(), error_message(ret) ); return hostname; } KRB5Principal temp_princ(temp_princ_raw); #ifdef HEIMDAL const char *comp = krb5_principal_get_comp_string(g_context, temp_princ.get(), 1); #else krb5_data *comp = krb5_princ_component(g_context, temp_princ.get(), 1); #endif if (comp == NULL) { std::string name(temp_princ.name()); fprintf(stderr, "Warning: hostname canonicalization for %s failed: returned " "unexpected principal %s\n", hostname.c_str(), name.c_str() ); return hostname; } #ifdef HEIMDAL return std::string(comp); #else return std::string(comp->data, comp->length); #endif } std::string get_default_hostname(bool no_canonical_name) { /* Ask the Kerberos library to canonicalize the hostname, and then * pull it out of the principal. */ int32_t type = KRB5_NT_SRV_HST; krb5_principal temp_princ_raw; /* do not canonicalize, use supplied hostname */ if (no_canonical_name) { type = KRB5_NT_UNKNOWN; } krb5_error_code ret = krb5_sname_to_principal(g_context, NULL, "host", type, &temp_princ_raw); if (ret != 0) { throw KRB5Exception("krb5_sname_to_principal (get_default_hostname)", ret); } KRB5Principal temp_princ(temp_princ_raw); #ifdef HEIMDAL const char *comp = krb5_principal_get_comp_string(g_context, temp_princ.get(), 1); #else krb5_data *comp = krb5_princ_component(g_context, temp_princ.get(), 1); #endif if (comp == NULL) { error_exit("get_default_hostname: couldn't determine " "hostname, strange value from " "krb5_sname_to_principal."); } #ifdef HEIMDAL return std::string(comp); #else return std::string(comp->data, comp->length); #endif } bool DnsSrvHost::validate(bool nocanon, std::string service) { int ret, sock = -1; /* used to call into C function, so we prefer char[] over std::string */ char host[NI_MAXHOST]; struct addrinfo *hostaddrinfo = NULL; // The order of the struct addrinfo members is not portable, // therefore it is not possible to use struct initialization here. // Use default initialization and set potentially nonzero members explicitly. // See issue #161 struct addrinfo hints = {}; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; if (!validated_name.empty()) { return true; } /* so far we don't require C++11, so no ::to_string(), yet */ if (service.empty()) { std::stringstream srvtmp; srvtmp << m_port; std::string service = srvtmp.str(); } ret = getaddrinfo(srvname.c_str(), service.c_str(), &hints, &hostaddrinfo); if (ret != 0) { VERBOSE("Error: getaddrinfo failed for %s: %s", srvname.c_str(), ret == EAI_SYSTEM ? strerror(errno) : gai_strerror(ret)); if (hostaddrinfo) { freeaddrinfo(hostaddrinfo); } return false; } VERBOSE("Found DC: %s. Checking availability...", srvname.c_str()); for (struct addrinfo *ai = hostaddrinfo; ai; ai = ai->ai_next) { /* Now let's try and open and close a socket to see if the domain controller is up or not */ if (sock != -1) { close(sock); } if ((sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1) { VERBOSE("Failed to create socket: %s", strerror(errno)); continue; } if (connect(sock, (struct sockaddr *) ai->ai_addr, ai->ai_addrlen) == -1) { int err = errno; char addrstr[NI_MAXHOST] = ""; (void) getnameinfo(ai->ai_addr, ai->ai_addrlen, addrstr, sizeof(addrstr), NULL, 0, NI_NUMERICHOST); VERBOSE("LDAP connection to %s failed: %s", addrstr, strerror(err)); continue; } /* See if this is the 'lowest' domain controller name... the idea is to always try to * use the same domain controller. Things may become inconsistent otherwise. * This optimization is only possible if we're told to canonify the hostname. Otherwise, * we're not allowed to touch it, but we can make sure that it resolves to at least * one working IP address. */ if (nocanon) { validated_name = srvname; break; } ret = getnameinfo(ai->ai_addr, ai->ai_addrlen, host, sizeof(host), NULL, 0, NI_NAMEREQD); if (ret != 0) { int err = errno; char addrstr[NI_MAXHOST] = ""; (void) getnameinfo(ai->ai_addr, ai->ai_addrlen, addrstr, sizeof(addrstr), NULL, 0, NI_NUMERICHOST); VERBOSE("Error: getnameinfo failed for %s: %s", addrstr, ret == EAI_SYSTEM ? strerror(err) : gai_strerror(ret)); continue; } if (!validated_name.empty() && std::string(host) > validated_name) { VERBOSE("Connection to DC %s ok, but we already prefer %s", host, validated_name.c_str()); continue; } validated_name = std::string(host); } if (sock != -1) { close(sock); } if (hostaddrinfo) { freeaddrinfo(hostaddrinfo); } return ! validated_name.empty(); }; DnsSrvQuery::DnsSrvQuery(const std::string& domain, const std::string& service, const std::string& protocol) { #if defined(HAVE_LIBUDNS) struct dns_ctx *nsctx = NULL; dns_reset(NULL); if ((nsctx = dns_new(NULL)) != NULL) { if (dns_init(nsctx, 1) >= 0) { struct dns_rr_srv *srv; if ((srv = dns_resolve_srv(nsctx, domain.c_str(), service.c_str(), protocol.c_str(), DNS_NOSRCH)) != NULL) { for (int i = 0; i < srv->dnssrv_nrr; i++) { m_results.push_back(DnsSrvHost(srv->dnssrv_srv[i])); } free(srv); } } dns_close(nsctx); free(nsctx); } #elif defined(HAVE_NS_INITPARSE) && defined(HAVE_RES_SEARCH) unsigned char response[NS_MAXMSG]; int len; ns_msg reshandle; ns_rr rr; const std::string krbdnsquery = "_" + service + "._" + protocol + "." + domain; VERBOSE("Running DNS SRV query for %s", krbdnsquery.c_str()); if ((len=res_search(krbdnsquery.c_str(), ns_c_in, ns_t_srv, response, sizeof(response))) > 0) { if (ns_initparse(response,len,&reshandle) >= 0) { if ((len=ns_msg_count(reshandle,ns_s_an)) > 0) { for (int i = 0; i::iterator it=dcsrvs.begin(); it != dcsrvs.end(); it++) { /* Don't validate host availability by checking the KRB5 port returned * from the SRV record, but the hard-coded, standard LDAP port (389). * This is a short cut that should work as long as each DC runs both * KRB5 and LDAP on default ports. */ if (it->validate(no_reverse_lookups, stringify(LDAP_PORT))) { bestdc = it->name(); break; } } VERBOSE("Found preferred domain controller: %s", bestdc.c_str()); return bestdc; } /* Return true if ends with , false otherwise */ static bool ends_with(std::string const &str, std::string const &suffix) { if (suffix.size() > str.size()) return false; return std::equal(suffix.rbegin(), suffix.rend(), str.rbegin()); } /* Default sAMAccountName for current host: Use lowercase FQDN, strip realm if applicable, convert remaining dots to dashes. Eg. foo.example.com in realm EXAMPLE.COM -> foo foo.subdomain1.example.com in realm EXAMPLE.COM -> foo-subdomain1 foo.subdomain1.example.com in realm OTHEREXAMPLE.COM -> foo-subdomain1-example-com */ std::string get_default_samaccountname(msktutil_flags *flags) { std::string long_hostname = flags->hostname; std::transform(long_hostname.begin(), long_hostname.end(), long_hostname.begin(), ::tolower); std::string samaccountname = long_hostname; if (ends_with(samaccountname, '.' + flags->lower_realm_name)) samaccountname.resize(samaccountname.length() - flags->lower_realm_name.length() - 1); /* Replace any remaining dots with dashes */ std::replace(samaccountname.begin(), samaccountname.end(), '.', '-'); VERBOSE("Determined sAMAccountName: %s", samaccountname.c_str()); return samaccountname; } /* Return first component of FQDN set in flags->hostname */ std::string get_short_hostname(msktutil_flags *flags) { std::string long_hostname = flags->hostname; std::string short_hostname = long_hostname.substr(0, long_hostname.find('.')); std::transform(short_hostname.begin(), short_hostname.end(), short_hostname.begin(), ::tolower); VERBOSE("Determined short hostname: %s", short_hostname.c_str()); return short_hostname; } msktutil-1.2.2/msktname.h000066400000000000000000000114611467024531100153660ustar00rootroot00000000000000/* *---------------------------------------------------------------------------- * * msktname.h * * (C) 2004-2006 Dan Perry (dperry@pppl.gov) * (C) 2006 Brian Elliott Finley (finley@anl.gov) * (C) 2009-2010 Doug Engert (deengert@anl.gov) * (C) 2010 James Y Knight (foom@fuhm.net) * (C) 2010-2013 Ken Dreyer * (C) 2012-2017 Mark Proehl * (C) 2012-2017 Olaf Flebbe * (C) 2013-2017 Daniel Kobras * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *----------------------------------------------------------------------------- */ #include "config.h" #if defined(HAVE_LIBUDNS) #include #define inet_ntop dns_ntop #elif defined(HAVE_NS_INITPARSE) && defined(HAVE_RES_SEARCH) #include #include #include #if !defined(NS_MAXMSG) #define NS_MAXMSG 65535 #endif #else #include #endif #ifndef stringify #define stringify(x) stringify_(x) #define stringify_(x) #x #endif #include #include #if defined(HAVE_NS_INITPARSE) /* A quirk in glibc < 2.9 makes us pick up a symbol marked GLIBC_PRIVATE * if we use ns_get16 from libresolv, leading to a broken RPM * that can only be installed with --nodeps. As a workaround, * use a private version of ns_get16--it's simple enough. */ static unsigned int msktutil_ns_get16(const unsigned char *src) { return (unsigned int) (((uint16_t)src[0] << 8) | ((uint16_t)src[1])); } #endif class DnsSrvHost { private: std::string srvname; std::string validated_name; unsigned int m_priority; unsigned int m_weight; unsigned int m_port; public: DnsSrvHost(const std::string& name, unsigned int priority, unsigned int weight, unsigned int port) : srvname(name), validated_name(""), m_priority(priority), m_weight(weight), m_port(port) {}; #if defined(HAVE_LIBUDNS) DnsSrvHost(const struct dns_srv& dnssrv) : srvname(dnssrv.name), validated_name(""), m_priority(dnssrv.priority), m_weight(dnssrv.weight), m_port(dnssrv.port) {}; #endif #if defined(HAVE_NS_INITPARSE) DnsSrvHost(ns_msg reshandle, ns_rr rr) : validated_name("") { if (ns_rr_class(rr) == ns_c_in && ns_rr_type(rr) == ns_t_srv) { char name[NS_MAXDNAME]; m_priority = msktutil_ns_get16(ns_rr_rdata(rr)); m_weight = msktutil_ns_get16(ns_rr_rdata(rr) + NS_INT16SZ); m_port = msktutil_ns_get16(ns_rr_rdata(rr) + 2*NS_INT16SZ); dn_expand(ns_msg_base(reshandle),ns_msg_base(reshandle)+ns_msg_size(reshandle), ns_rr_rdata(rr) + 3*NS_INT16SZ, name, sizeof(char)*NS_MAXDNAME); srvname = std::string(name); } }; #endif std::string name() { return validated_name; }; unsigned int priority() { return m_priority; }; unsigned int weight() { return m_weight; }; unsigned int port() { return m_port; }; /* Allow to sort by prio where a lower prio number means stronger * preference, ie. the lowest sorting item is the most preferred. */ bool operator<(const DnsSrvHost& other) const { /* for ultimate confusion, prio and weight go in opposite * directions, ie. we prefer lower prio, but higher weight. */ if (m_priority == other.m_priority) return m_weight > other.m_weight; return m_priority < other.m_priority; }; /* Check host availability by opening a TCP connection to the objects's * . If is non-empty, use its associated port instead. */ bool validate(bool nocanon, std::string service = ""); }; class DnsSrvQuery { private: std::vector m_results; public: DnsSrvQuery() {}; DnsSrvQuery(const DnsSrvHost& host) { m_results.push_back(host); }; DnsSrvQuery(const std::string& domain, const std::string& service, const std::string& protocol); bool empty() { return m_results.empty(); }; std::vector::iterator begin() { return m_results.begin(); }; std::vector::iterator end() { return m_results.end(); }; }; msktutil-1.2.2/msktpass.cpp000066400000000000000000000263611467024531100157540ustar00rootroot00000000000000/* *---------------------------------------------------------------------------- * * msktpass.cpp * * (C) 2004-2006 Dan Perry (dperry@pppl.gov) * (C) 2006 Brian Elliott Finley (finley@anl.gov) * (C) 2009-2010 Doug Engert (deengert@anl.gov) * (C) 2010 James Y Knight (foom@fuhm.net) * (C) 2010-2013 Ken Dreyer * (C) 2012-2017 Mark Proehl * (C) 2012-2017 Olaf Flebbe * (C) 2013-2017 Daniel Kobras * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *----------------------------------------------------------------------------- */ #include "msktutil.h" void init_password(msktutil_flags *flags) { if (flags->use_service_account) { VERBOSE("Wiping the password structure"); } else { VERBOSE("Wiping the computer password structure"); } std::fill(flags->password.begin(), flags->password.end(), '\0'); } int generate_new_password(msktutil_flags *flags) { int i; int curr; int have_lower = 0; int have_upper = 0; int have_symbol = 0; int have_number = 0; int chars_used = 0; FILE *fp; init_password(flags); flags->password.resize(PASSWORD_LEN); fp = fopen("/dev/urandom", "r"); if (!fp) { error_exit( "failed to open /dev/urandom"); } if (flags->use_service_account) { VERBOSE("Generating a new, random password for the service account"); } else { VERBOSE("Generating a new, random password for the computer account"); } while (!(have_symbol && have_number && have_lower && have_upper)) { have_symbol = 0; have_number = 0; have_upper = 0; have_lower = 0; for (i = 0; i < PASSWORD_LEN; i++) { curr = 0; while (curr < 33 || curr > 126) { if ((curr = getc(fp)) == EOF) { error_exit("Failed to read from /dev/urandom"); } curr &= 0x7f; chars_used++; } have_symbol |= (curr >= 33 && curr <= 47); have_symbol |= (curr >= 91 && curr <= 96); have_symbol |= (curr >= 123 && curr <= 126); have_symbol |= (curr >= 58 && curr <= 64); have_number |= (curr >= 48 && curr <= 57); have_upper |= (curr >= 65 && curr <= 90); have_lower |= (curr >= 97 && curr <= 122); flags->password[i] = (char) curr; } } fclose(fp); VERBOSE("Characters read from /dev/urandom: %d", chars_used); return 0; } /* Try to set the the new Samba secret to password>. */ static int set_samba_secret(msktutil_flags *flags) { VERBOSE("Setting samba machine trust account password using command: %s", flags->samba_cmd.c_str()); FILE *pipe; pipe = popen(flags->samba_cmd.c_str(), "w"); if (pipe == NULL) { fprintf(stdout, "Could not execute command: %s\n", flags->samba_cmd.c_str() ); return 1; } size_t len = flags->password.length(); if (fwrite(flags->password.c_str(), sizeof(char), len, pipe) != len) { fprintf(stdout, "Write error passing password to command: %s\n", flags->samba_cmd.c_str() ); return 1; } int rc = pclose(pipe); if (rc != 0) { fprintf(stderr, "WARNING: Setting samba secret failed with error code %d\n", rc ); return 1; } VERBOSE("Successfully set samba machine trust account password"); return 0; } int set_password(msktutil_flags *flags) { int ret; krb5_data resp_code_string; krb5_data resp_string; int response = 0; /* Zero out these data structures, because we attempt to free them * below, and sometimes, upon error conditions, the called API * hasn't set them itself. */ resp_string.data = NULL; resp_string.length = 0; resp_code_string.data = NULL; resp_code_string.length = 0; if (flags->use_service_account) { VERBOSE("Attempting to reset service account's password"); } else { VERBOSE("Attempting to reset computer's password"); } if (flags->auth_type == AUTH_FROM_USER_CREDS) { VERBOSE("Try change password using user's ticket cache"); KRB5CCache ccache(KRB5CCache::defaultName()); KRB5Principal principal(flags->sAMAccountName); ret = krb5_set_password_using_ccache(g_context, ccache.get(), const_cast(flags->password.c_str()), principal.get(), &response, &resp_code_string, &resp_string); krb5_free_data_contents(g_context, &resp_string); if (!ret && response) { fprintf(stderr, "Error: Unable to set machine password for %s: (%d) %s\n", flags->sAMAccountName.c_str(), response, (char *) resp_code_string.data); krb5_free_data_contents(g_context, &resp_code_string); return response; } krb5_free_data_contents(g_context, &resp_code_string); if (ret) { if (!response && ret == KRB5KRB_AP_ERR_BADADDR) { VERBOSE("krb5_set_password_using_ccache: password changed " "successfully, but sender IP in KRB-PRIV failed " "validation"); if (!flags->server_behind_nat) { fprintf(stderr, "Error: krb5_set_password_using_ccache " "failed: %s\n", error_message(ret) ); return ret; } } else { fprintf(stderr, "Error: krb5_set_password_using_ccache failed: %s\n", error_message(ret) ); return ret; } } } else { KRB5Creds creds; /* Use the machine's credentials */ if (flags->auth_type == AUTH_FROM_SAM_KEYTAB || flags->auth_type == AUTH_FROM_SAM_UPPERCASE_KEYTAB || flags->auth_type == AUTH_FROM_EXPLICIT_KEYTAB || flags->auth_type == AUTH_FROM_HOSTNAME_KEYTAB) { std::string princ_name; if (flags->auth_type == AUTH_FROM_SAM_KEYTAB) { princ_name = flags->sAMAccountName; } else if (flags->auth_type == AUTH_FROM_SAM_UPPERCASE_KEYTAB) { princ_name = flags->sAMAccountName_uppercase; } else if (flags->auth_type == AUTH_FROM_EXPLICIT_KEYTAB) { princ_name = flags->keytab_auth_princ; } else { princ_name = "host/" + flags->hostname; } VERBOSE("Trying to use keytab for %s to change password", princ_name.c_str()); KRB5Keytab keytab(flags->keytab_readname); KRB5Principal principal(princ_name); KRB5Creds local_creds(principal, keytab, "kadmin/changepw"); creds.move_from(local_creds); } else if (flags->auth_type == AUTH_FROM_PASSWORD) { VERBOSE("Trying to use default password for %s to change password", flags->sAMAccountName.c_str()); KRB5Principal principal(flags->sAMAccountName); KRB5Creds local_creds(principal, create_default_machine_password( flags->sAMAccountName), "kadmin/changepw"); creds.move_from(local_creds); } else if ((flags->auth_type == AUTH_FROM_SUPPLIED_PASSWORD) || (flags->auth_type == AUTH_FROM_SUPPLIED_EXPIRED_PASSWORD)) { VERBOSE("Trying to use supplied password for %s to change password", flags->sAMAccountName.c_str()); KRB5Principal principal(flags->sAMAccountName); KRB5Creds local_creds(principal, flags->old_account_password, "kadmin/changepw"); creds.move_from(local_creds); } else /* shouldn't happen */ throw Exception("Error: unknown auth_type"); ret = krb5_change_password(g_context, creds.get(), const_cast(flags->password.c_str()), &response, &resp_code_string, &resp_string); krb5_free_data_contents(g_context, &resp_string); if (response) { VERBOSE("krb5_change_password failed using keytab: (%d) %s", response, (char *) resp_code_string.data); krb5_free_data_contents(g_context, &resp_code_string); return response; } krb5_free_data_contents(g_context, &resp_code_string); if (ret) { if (!response && ret == KRB5KRB_AP_ERR_BADADDR) { VERBOSE("krb5_change_password: password changed " "successfully, but sender IP in KRB-PRIV " "failed validation"); if (!flags->server_behind_nat) { fprintf(stderr, "Error: krb5_change_password failed: %s\n", error_message(ret) ); return ret; } } else { fprintf(stderr, "Error: krb5_change_password failed: %s\n", error_message(ret) ); return ret; } } } VERBOSE("Successfully set password"); if (flags->set_samba_secret) { /* At this point, we've already set the new password in AD, and there's * no way to roll back for us because we don't know the old password. * Therefore, even if we fail to propagate the new password to Samba, * we still need to carry on, and write out the new keys to the keytab. * Failing here is not an option, so ignore the return value from * set_samba_secret(), and proceed normally. (The function has already * notified on stderr if a failure had occurred.) */ (void) set_samba_secret(flags); } return 0; } msktutil-1.2.2/msktutil.1.in000066400000000000000000000605201467024531100157410ustar00rootroot00000000000000.TH @PACKAGE_NAME@ 1 @PACKAGE_VERSION@ .SH NAME @PACKAGE_NAME@ \- fetches and manages Kerberos keytabs in an Active Directory environment .SH SYNOPSIS .B @PACKAGE_NAME@ [mode] [parameter 1] [parameter 2] ... .SH DESCRIPTION @PACKAGE_NAME@ is a Unix/Linux keytab utility for Microsoft Active Directory environments. This program is capable of creating accounts in Active Directory, adding service principals to those accounts, and creating local keytab files so that kerberizied services can utilize Active Directory as a Kerberos infrastructure. @PACKAGE_NAME@ will create and manage machine accounts by default. The --use-service-account option lets @PACKAGE_NAME@ operate on service accounts. @PACKAGE_NAME@ requires that the Kerberos client libraries are properly installed and configured to use Active Directory as a realm. .PP Whenever a principal is added or a keytab is updated, the secret password for the corresponding account is changed. By default, the password is not stored, so it needs to be reset each time @PACKAGE_NAME@ is executed. All entries in the keytab will be automatically updated whenever the password is reset. The previous entries will be left in the keytab, so sessions using the older key versions will not break. This behavior is similar to the way Windows hosts handle machine password changes. .SH CREDENTIALS .PP There are two common methods of using this program. The first is to "kinit" with Administrator-like credentials which have permission to create computer objects in your Active Directory server. If you invoke the program with such credentials, you can create a new computer account or service account from scratch. .PP The second is to pre-create the accounts with such credentials, and then invoke @PACKAGE_NAME@ on a machine without any special permissions. When the computer account or service account exists already, @PACKAGE_NAME@ will attempt to authenticate as that account using either the existing keytab, or if that fails, a default password. When that default password is not specified with the option --old-account-password, @PACKAGE_NAME@ will use the default machine password. It will then change the password and update the keytab appropriately. This is usually the more convenient option when joining many computers to the domain. .PP To pre-create a computer account, you may use the Active Directory Users and Computers GUI, select "new computer" from the right click menu, and type the short DNS name, then right click on the newly created object and select "Reset account" to set the password to the default value. Another alternative is to run @PACKAGE_NAME@ in the pre-create mode. Both methods accomplish the same thing. .PP To pre-create a service account, you may use the Active Directory Users and Computers GUI, select "new user" from the right click menu, fill in all required data, set the password to a specific value and use setspn.exe to set the desired servicePrincipalName(s). You may also select "must change password at next logon". .SH MODES .TP .B create Creates a keytab for the current host or a given service account. Equivalent to update --service host. .TP .B update Forces a password change and updates all related service principal entries from the servicePrincipalName and userPrincipalName attributes. Updates dNSHostName for machine accounts and always updates msDS-supportedEncryptionTypes attributes with current values, and applies other changes as specified. .TP .B auto-update Checks if the password is at least 30 days old (from pwdLastSet attribute), and that the account does not have password expiry disabled. If those conditions are met, acts just like @PACKAGE_NAME@ update. Will also update if the keytab failed to authenticate but the default password did work (e.g. after resetting the account in AD). Otherwise, exits without doing anything (even if attribute modifying options are given). This option is intended for use from a daily crontab to ensure that the password is rotated regularly. .TP .B pre-create Pre-create (or update) an account for the given host with default password. Does not use or update local keytab. Requires -h or --computer-name argument. Implies --user-creds-only. Generally requires administrator credentials. .TP .B flush Flushes out principals for the current accountname from the keytab, and makes corresponding changes to the machine or service account. .TP .B cleanup Deletes entries from the keytab that are no longer needed. .B delete Deletes the host or service account from Active Directory. .SH OPTIONS .SS COMMON OPTIONS .TP -v, --version Displays version information .TP --help Displays a help message .TP --verbose Enables verbose status messages. May be specified more then once to get LDAP debugging. .SS CONNECTION/SETUP OPTIONS .TP -b, --base Specifies an LDAP base OU when creating a new account. Unless the given string ends with the domain's base DN, it is assume to be a relative path: For example, specifying '-b OU=Unix' for a computer named SERVER in an Active Directory domain example.com would create a computer account in the LDAP path: CN=SERVER,OU=Unix,DC=EXAMPLE,DC=COM. This option can also be specified by setting the MSKTUTIL_LDAP_BASE environment variable to the desired value. If not specified, the default value is read from AD (and the default there, unless modified by an admin, is CN=Computers for machine accounts and CN=Users for service accounts). .TP --computer-name Specifies that the new account should use for the computer account name and the SAM Account Name. Note that a '$' will be automatically appended to the SAM Account Name. Defaults to the machine's hostname, excluding the realm, with dots replaced with dashes. That is: if the realm is EXAMPLE.COM, and the hostname is FOO.EXAMPLE.COM, the default computer name is FOO. If the hostname is FOO.BAR.EXAMPLE.COM, the default computer name is FOO-BAR. .TP --account-name An alias for --computer-name that can be used when operating on service accounts. Note that a '$' will not be automatically appended to the SAM Account Name when using service accounts. .TP --old-account-password Use supplied account password for authentication. This is useful if the keytab does not yet exist but the password of the computer account is known. This password will be changed by @PACKAGE_NAME@ in order to create or update the keytab .TP --password Specify the new account password instead of generating a random one. Consider the password policy settings when defining the string. .TP --dont-change-password Do not create a new password. Try to use existing keys when performing keytab updates or the old password when creating a new keytab. This is useful for adding new SPNs to a machine or service account. This option is only available in update or create mode. In create mode the old password needs to be specified with --old-account-password .TP -h, --hostname Overrides the current hostname to be used to be . If this is not specified, the local host name will be used. Note that the local name lookup service will be to qualify and resolve names into fully-qualified names, including a domain extension. This affects the default hostname for other arguments, and the default computer-name. The hostname is also used to set the dNSHostName attribute. .TP -k, --keytab Specifies to use for the keytab. This option can also be specified by setting the MSKTUTIL_KEYTAB environment variable to the name of the desired keytab file. This keytab is both read from, in order to authenticate as the given account, and written to, after updating the account password. Default: /etc/krb5.keytab .TP --keytab-auth-as Specifies which principal name we should try to use, when we authenticate from a keytab. Normally, @PACKAGE_NAME@ will try to use the account name or the host principal for the current host. If this option is specified, instead @PACKAGE_NAME@ will try to use the given principal name first, and only fall back to the default behavior if we fail to authenticate with the given name. This option can be useful if you do not know the current password for the relevant account, do not have a keytab with the account principal, but you do have a keytab with a service principal associated with that account. .TP --server Specifies to use as the domain controller. This affects both Kerberos and LDAP operations. The server can also be specified by setting the MSKTUTIL_SERVER environment variable. Default: looked up in DNS from the realm name. .TP --server-behind-nat When the server is behind a firewall that performs Network Address Translation, KRB-PRIV messages fail validation. This is because the IP address in the encrypted part of the message cannot be rewritten in the NAT process. This option ignores the resulting error for the password change process, allowing systems outside the NAT firewall to join the domain managed by servers inside the NAT firewall. .TP --realm Specifies to use as Kerberos realm. Default: use the default_realm from [libdefaults] section of krb5.conf. .TP --site Find and use domain controller in specific AD site. This option is ignored if option --server is used. .TP -N, --no-reverse-lookups Do not attempt to canonicalize the name of the domain controller via DNS reverse lookups. You may need to do this if your client cannot resolve the PTR records for a domain controller or your DNS servers store incorrect PTR records. Default: Use DNS reverse lookups to canonicalize DC names. .TP -n, --no-canonical-name Do not attempt to canonicalize the hostname while creating names of Kerberos principals. Instead use supplied hostname. This may be needed for systems where forward and reverse DNS lookups do not return the same (a dynamic DNS system for example where lookup for myhost.mydomain returns IP X.Y.Z.W , but lookup for IP X.Y.Z.W returns a name different than myhost.mydomain). .TP --user-creds-only Don't attempt to authenticate with a keytab: only use user's credentials (from e.g. kinit). You may need to do this to modify certain attributes that require Administrator credentials (description, userAccountControl, userPrincipalName, in a default AD setup). .TP --auto-update-interval Number of when @PACKAGE_NAME@ auto-update will change the account password. Defaults to 30 days. .TP -m, --sasl-mechanisms A space-separated list of candidate SASL mechanisms to use when performing the LDAP bind. The first mechanism in the list that is supported by both the client (the host running @PACKAGE_NAME@) and the server (Active Directory) will be used. If providing more than one candidate mechanism, make sure to quote the list to protect the whitespace from the shell. Default: "GSS-SPNEGO GSSAPI". .SS OBJECT TYPE/ATTRIBUTE-SETTING OPTIONS .TP --use-service-account Create and maintain service accounts instead of machine accounts. .TP --delegation Enables the account to be trusted for delegation. This option can also be enabled by setting the MSKTUTIL_DELEGATION environment variable. This modifies the userAccountControl attribute. Generally requires administrator credentials. .TP --description Sets the account's description attribute to the given text (or removes if text is ''). Generally requires administrator credentials. .TP --disable-delegation Disables the account from being trusted for delegation. This modifies the userAccountControl attribute. Generally requires administrator credentials. .TP --disable-no-pac Unsets the flag that disables the KDC's including of a PAC in the machine's service tickets. This modifies the userAccountControl attribute. Generally requires administrator credentials. .TP --dont-expire-password Sets the DONT_EXPIRE_PASSSWORD bit in the userAccountControl attribute, which disables password expiry for this account. If you don't run a cron job to periodically rotate the keytab, you will want to set this flag. Generally requires administrator credentials. .TP --do-expire-password Unsets the DONT_EXPIRE_PASSWORD flag in the userAccountControl attribute. Generally requires administrator credentials. .TP --dont-update-dnshostname Do not update dnsHostName attribute. In some AD installations modification of this attribute is not allowed (unless using administrator credentials), using this option will avoid constraint violation warning. .TP --enable Unsets the UF_ACCOUNT_DISABLE flag in the userAccountControl attribute. When a computer leaves the domain this flag is normally set. Generally requires administrator credentials. .TP --enctypes Sets the supported encryption types in the msDs-supportedEncryptionTypes field. You may OR together the following values: 0x1=des-cbc-crc 0x2=des-cbc-md5 0x4=rc4-hmac-md5 0x8=aes128-cts-hmac-sha1 0x10=aes256-cts-hmac-sha1 This value is used to determine which encryption types AD will offer to use, and which encryption types to put in the keytab. If the value is set to 0x3 (that is: only the two DES types), it also attempts to set the DES-only flag in userAccountControl. Note: Windows 2008R2 refuses to use DES by default; you thus cannot use DES-only keys unless you have enabled DES encryption for your domain first. Recent versions of MIT Kerberos clients similarly refuse to use DES by default. Default: sets the value to 0x1C: that is, use anything but DES. .TP --allow-weak-crypto Enables the usage of DES keys for authentication. This is equivalent to MIT's krb5.conf parameter allow_weak_crypto. .TP --no-pac Specifies that service tickets for this account should not contain a PAC. This modifies the userAccountControl attribute. See Microsoft Knowledge Base article #832575 for details. This option can also be specified by setting the MSKTUTIL_NO_PAC environment variable. Generally requires administrator credentials. .TP -s, --service Specifies a service principal to add to the account (and thus keytab, if appropriate). The service is of the form /. If the hostname is omitted, assumes current hostname. May be specified multiple times. When creating machine accounts, entries for the host service are created by default, unless --service is given. Unqualified service names (without a '/' component) are qualified with the full and the short hostnames, eg. host/hostname.example.com and host/hostname. .TP --remove-service Specifies a service principal to remove from the account (and keytab if appropriate). .TP --upn Sets the userPrincipalName attribute of the computer account or service account to be . The userPrincipalName can be used in addition to the sAMAccountName (e.g. computername$ for computer accounts) for kinit. can be provided in short form (e.g. host/hostname.example.com) or in long form (e.g. host/hostname.example.com@EXAMPLE.COM). In short form the default realm will automatically be appended. This operation requires administrator privileges. .TP --set-samba-secret Use Samba's net changesecretpw command to locally set the machine account password in Samba's secrets.tdb. $PATH need to include Samba's net command. Samba needs to be configured appropriately. .TP --use-samba-cmd Use supplied command instead of Samba's net changesecretpw command. command will be supplied machine account password on standard input and shall return 0 exit code on success. .TP --check-replication Wait until the password change is reflected in LDAP. By default, msktutil exits once a password update is successful and the new keytab is written. However, due to replication delays, LDAP queries might still return an older key version number. If --check-replication is given, msktutil waits until the key version number is updated on the queried LDAP server as well. Note that this is just a sanity check: The new password is supposed to be accepted on all domain controllers once the update succeeds, even if LDAP is not yet in sync. Turning on this option might substantially increase the runtime of msktutil in some environments due to replication delays (eg. 15 to 30 minutes for common AD configurations). The default is not to check LDAP replication. .SS CLEANUP OPTIONS .TP --remove-old Removes entries from the keytab that are older than days. The newest keytab entries will be kept to prevent a total cleanup. I.e. it is not possible to produce an empty keytab with the --remove-old option. .TP --remove-enctype Removes entries from the keytab with given encryption type. Warning: it is possible to produce empty keytabs with the --remove-empty option by successively removing all encryption types. Supported enctype strings are: des-cbc-crc,des-cbc-md5, arcfour, aes128 and aes256. .SH NOTES .SS PASSWORD EXPIRY .PP Be aware that Windows machines will, by default, automatically change their account password every 30 days, and thus many domains have a 90-day password expiry window, after which your keytab will stop working. There are two ways to deal with this: .PP a) (Preferred): Make sure you're running a daily cron job to run @PACKAGE_NAME@ auto-update, which will change the password automatically 30 days after it was last changed and update the keytab. .PP b) (Not preferred): disable password expiry for the account via the --dont-expire-password option (or otherwise setting DONT_EXPIRE_PASSWORD flag in userAccountControl in AD). .SS PASSWORD POLICY ISSUES .PP This section only applies to @PACKAGE_NAME@ --use-service-account. .PP While machine account passwords may be changed at any time, service accounts are user accounts and your Active Directory domain may have special password policies for those user accounts. E.g., "minimum password age" is typically set to 1 day, which means that you will have to wait for that time to pass until you may invoke @PACKAGE_NAME@ update --use-service-account. .SS OTHER NOTES .PP Unlike other Kerberos implementations, Active Directory has only a single key for all of the principals associated with an account. So, if you create a HTTP/hostname service principal, it will share the same key as the host/hostname principal. If you want to isolate (security-wise) different service principals, you may want to create a dedicated service account for them (with --use-service-account) and a separate keytab file (with --keytab). .PP Also note: kinit -k 'host/computername' *will not work*, by default, even when that is a valid service principal existing in your keytab. Active Directory does not allow you to authenticate as a service principal, so do not use that as a test of whether the service principal is working. If you actually want to authenticate as the computer account user, kinit -k 'computername$' instead. .PP If you really need to be able to authenticate as 'host/computername', you can also use the --upn argument to set the userPrincipalName attribute (generally requires administrator credentials, not computer account credentials). Both 'computername$' and the value of userPrincipalName are treated as valid account names to kinit as. .PP @PACKAGE_NAME@ will use kerberized LDAP operations to talk to domain controllers. To obtain a LDAP service ticket, the DNS service will be used to construct the domain controllers LDAP principal name. If DNS is misconfigured, this construction may fail. To work around this issue, you may specify the fully qualified DNS name of your domain controller with the --server option and additionally use the --no-reverse-lookups option. .PP Samba (www.samba.org) provides the net command that can be used to manage Kerberos keytabs as well. Using @PACKAGE_NAME@ and commands like "net ads join" or "net ads keytab" together can lead to trouble. With the --set-samba-secret option, @PACKAGE_NAME@ can be used as a replacement for net. .PP Active Directory includes authorization data (e.g. information about group memberships) in Kerberos tickets. This information is called PAC and may lead to very large ticket sizes. Especially HTTP services are known to produce failures if that size exceeds the HTTP header size. If your service does not make use of that PAC information (which is true for most Unix/Linux-services) you may just disable it with the --no-pac option. .SH EXAMPLES For unprivileged users the most common invocations are: .PP .nf @PACKAGE_NAME@ create .fi .PP This will create a computer account in Active Directory with a new password and write out a new keytab. .PP .nf @PACKAGE_NAME@ update --service host --service HTTP .fi .PP This will update a computer account in Active Directory with a new password, write out a new keytab, and ensure that it has both "host" and "HTTP" service principals are on it for the hostname. .PP .nf @PACKAGE_NAME@ update --dont-change-password --service host --service HTTP .fi .PP This will do the same as the last example but without changing the password. .PP .nf @PACKAGE_NAME@ auto-update .fi .PP This is useful in a daily cron job to check and rotate the password automatically when it's 30 days old. .PP .nf For users with admin privileges in AD, some common uses: .PP .nf @PACKAGE_NAME@ create --service host --service HTTP .fi .PP This will create a computer account in Active Directory with a new password, write out a new keytab, and ensure that it has both "host" and "HTTP" service principals are on it for the hostname. .PP .nf @PACKAGE_NAME@ pre-create --host computer1.example.com .fi .PP This will pre-create an account for computer1 with the default password using your credentials. This can be done on a central host, e.g. to script the addition of many hosts. You can then use @PACKAGE_NAME@ create on the hosts themselves (without special credentials) to join them to the domain. .PP .nf @PACKAGE_NAME@ create --host afs --service afs --enctypes 0x03 .fi .PP This will create an afs/cell.name@REALM principal, and associate that principal with a computer account called 'afs'. The principal will be marked as DES-only, which is required for AFS. .PP .nf @PACKAGE_NAME@ create --use-service-account --service HTTP/hostname.example.com --keytab /etc/apache/krb5.keytab --account-name srv-http --no-pac .fi .PP This will create an HTTP/hostname.example.com@REALM principal, and associate that principal with a service account called 'srv-http'. Corresponding Kerberos keys will be written to the keytab file /etc/apache/krb5.keytab. The size of Kerberos tickets for that service will stay small because no PAC information will be included. .PP .nf @PACKAGE_NAME@ create --keytab /etc/krb5/user/10123/client.keytab --use-service-account --account-name johndoe --dont-change-password --old-account-password .fi .PP This will create a keytab for johndoe without changing John Doe's password .PP .nf @PACKAGE_NAME@ create --service host/hostname --service host/hostname.example.com --set-samba-secret --enctypes 0x4 .fi .PP This will create a computer account in Active Directory that is compatible with Samba. The command creates a new password, write out a new keytab, and ensure that it includes both "host/hostname" and "host/hostname.example.com" as service principals (which is equivalent to what setspn.exe -R would do on Windows). The new computer password will be stored in Samba's secrets.tdb database to provide interoperability with Samba. As Samba (version 3) only supports arcfour encrypted Kerberos tickets the --enctypes option must be used to select only that encryption type. .PP .nf @PACKAGE_NAME@ cleanup --remove-old 10 .fi .PP Deletes all entries older than 10 days, keeping at least the last entry. .SH ENVIRONMENT .TP MSKTUTIL_LDAP_BASE Specifies a relative LDAP base when creating a new account (see --base), .TP MSKTUTIL_KEYTAB Specifies the keytab. Default: /etc/krb5.keytab (see --keytab), .TP MSKTUTIL_SERVER Specifies the domain controller (see --server). .TP MSKTUTIL_DELEGATION Enables the account to be trusted for delegation (see --delegation). .TP MSKTUTIL_NO_PAC Specifies that service tickets for this account should not contain a PAC (see --no-pac). .TP MSKTUTIL_SAMBA_CMD Specifies the command to be used to locally set the machine account password in Samba's secrets.tdb. .SH AUTHORS (C) 2004-2006 Dan Perry .PP (C) 2006 Brian Elliott Finley (finley at anl.gov) .PP (C) 2009-2010 Doug Engert (deengert at anl.gov) .PP (C) 2010 James Knight .PP (C) 2010-2013 Ken Dreyer .PP (C) 2012-2021 Olaf Flebbe .PP (C) 2012-2022 Mark Proehl .PP (C) 2013-2017 Daniel Kobras .PP (C) 2017-2022 Michael Osipov .PP (C) 2018-2022 Daniel Kobras msktutil-1.2.2/msktutil.cpp000066400000000000000000001506151467024531100157630ustar00rootroot00000000000000/* *---------------------------------------------------------------------------- * * msktutil.cpp * * (C) 2004-2006 Dan Perry (dperry@pppl.gov) * (C) 2006 Brian Elliott Finley (finley@anl.gov) * (C) 2009-2010 Doug Engert (deengert@anl.gov) * (C) 2010 James Y Knight (foom@fuhm.net) * (C) 2010-2013 Ken Dreyer * (C) 2012-2017 Mark Proehl * (C) 2012-2017 Olaf Flebbe * (C) 2013-2017 Daniel Kobras * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *----------------------------------------------------------------------------- */ #include "msktutil.h" #ifndef HAVE_STRTOLL #include "strtoll.h" #endif #include #include #include /* GLOBALS */ int g_verbose = 0; /* Fatal error */ void error_exit( const char *text) { v_error_exit("error_exit: %s: %s\n", text, strerror(errno)); } void v_error_exit(const char* format, ...) { va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); exit(1); } std::string sform(const char* format, ...) { va_list args; va_start(args, format); char *buf; #if !defined(HAVE_VASPRINTF) # ifdef HAVE_VSNPRINTF buf = (char *) malloc(10000); memset(buf, 0, 10000); int result = vsnprintf(buf, 10000-1, format, args); # else # error need either vasprintf or vsnprintf # endif #else int result = vasprintf(&buf, format, args); #endif if (result < 0) { error_exit("vasprintf failed"); } std::string outstr(buf, result); free(buf); va_end(args); return outstr; } void remove_files_at_exit() { remove_fake_krb5_conf(); remove_ccache(); } void catch_int(int) { remove_files_at_exit(); exit(1); } void set_supportedEncryptionTypes(msktutil_flags *flags, char * value) { flags->enctypes = VALUE_ON; flags->supportedEncryptionTypes = strtol(value, NULL, 0); } /* Parse string representation of enctype into numeric krb5 enctype * (not to be mistaken with the numeric AD enctype!) */ int parse_enctype(const std::string &value) { int enctype; if ("des-cbc-crc" == value) enctype = 1; else if ("des-cbc-md5" == value) enctype = 3; else if ("arcfour-hmac-md5" == value || "arcfour-hmac" == value || "arcfour" == value || "rc4-hmac-md5" == value || "rc4-hmac" == value || "rc4" == value) enctype = 23; else if ("aes128-cts-hmac-sha1-96" == value || "aes128-cts-hmac-sha1" == value || "aes128-cts-hmac" == value || "aes128-cts" == value || "aes128" == value) enctype = 17; else if ("aes256-cts-hmac-sha1-96" == value || "aes256-cts-hmac-sha1" == value || "aes256-cts-hmac" == value || "aes256-cts" == value || "aes256" == value) enctype = 18; else { fprintf(stderr, "Error: enctype %s not supported. " "Supported enctype strings are\n", value.c_str() ); fprintf(stderr, " des-cbc-crc\n"); fprintf(stderr, " des-cbc-md5\n"); fprintf(stderr, " arcfour\n"); fprintf(stderr, " aes128\n"); fprintf(stderr, " aes256\n"); exit(1); } return enctype; } void do_verbose() { g_verbose++; /* allow for LDAP debugging */ } void qualify_principal_vec(std::vector &principals, msktutil_flags *flags) { std::string short_hostname; std::string::size_type len = principals.size(); if (!flags->hostname.empty()) { short_hostname = get_short_hostname(flags); if (flags->hostname == short_hostname) short_hostname = ""; } for(size_t i = 0; i < len; ++i) { if (principals[i].find('/') != std::string::npos) { /* Nothing to do for principals that are already qualified */ continue; } if (flags->hostname.empty()) { fprintf(stderr, "Error: default hostname unspecified, " "and service argument missing hostname.\n" ); exit(1); } /* Modify unqualified entry in-place by appending (full) hostname * and add another entry to the back of the list qualified with * the short hostname (if different from long hostname) */ if (!short_hostname.empty()) principals.push_back(principals[i] + "/" + short_hostname); principals[i].append("/").append(flags->hostname); } } int finalize_exec(msktutil_exec *exec, msktutil_flags *flags) { int ret; char *temp_realm; if (flags->realm_name.empty()) { if (krb5_get_default_realm(g_context, &temp_realm)) { fprintf(stderr, "Error: krb5_get_default_realm failed\n"); exit(1); } flags->realm_name = std::string(temp_realm); #ifdef HEIMDAL krb5_xfree(temp_realm); #else krb5_free_default_realm(g_context, temp_realm); #endif } flags->lower_realm_name = flags->realm_name; for(std::string::iterator it = flags->lower_realm_name.begin(); it != flags->lower_realm_name.end(); ++it) { *it = std::tolower(*it); } if (exec->mode == MODE_CLEANUP) { VERBOSE("cleanup mode: don't need AD server"); flags->server = "dummy"; } else { if (flags->server.empty()) { flags->server = get_dc_host(flags->realm_name, flags->site, flags->no_reverse_lookups); if (flags->server.empty()) { fprintf(stderr, "Error: get_dc_host failed\n"); exit(1); } } } get_default_keytab(flags); signal(SIGINT, catch_int); atexit(remove_files_at_exit); create_fake_krb5_conf(flags); if (exec->mode == MODE_CLEANUP) { VERBOSE("cleanup mode: nothing more to do"); return (0); } if (exec->mode == MODE_PRECREATE && flags->hostname.empty()) { /* Don't set a default hostname if none provided in pre-create * mode. */ if (flags->sAMAccountName.empty()) { fprintf(stderr, "Error: You must supply either --computer-name " "or --hostname when using pre-create mode.\n" ); exit(1); } } else if (flags->hostname.empty()) { /* Canonicalize the hostname if need be */ flags->hostname = get_default_hostname(flags->no_canonical_name); } else { flags->hostname = complete_hostname(flags->hostname); } /* Determine the sAMAccountName, if not set */ if (flags->sAMAccountName.empty()) { if (flags->use_service_account) { fprintf(stderr, "Error: You must supply --account-name " "when using --use-service-account.\n" ); exit(1); } else { flags->sAMAccountName = get_default_samaccountname(flags) + "$"; } } /* Determine sAMAccountName_nodollar */ flags->sAMAccountName_nodollar = flags->sAMAccountName; if (flags->sAMAccountName_nodollar[ flags->sAMAccountName_nodollar.size()-1] == '$') { flags->sAMAccountName_nodollar.erase( flags->sAMAccountName_nodollar.size()-1); } /* Add a "$" to machine accounts */ if ((!flags->use_service_account) && (flags->sAMAccountName[flags->sAMAccountName.size()-1] != '$')) { flags->sAMAccountName += "$"; } /* Determine uppercase version of sAMAccountName */ flags->sAMAccountName_uppercase = flags->sAMAccountName; for (std::string::size_type i=0; isAMAccountName_uppercase.length(); ++i) { flags->sAMAccountName_uppercase[i] = toupper(flags->sAMAccountName_uppercase[i]); } /* The sAMAccountName will cause win 9x, NT problems if longer * than MAX_SAM_ACCOUNT_LEN characters */ if (flags->sAMAccountName.length() > MAX_SAM_ACCOUNT_LEN) { fprintf(stderr, "Error: The sAMAccountName %s for this host is longer " "than the maximum of MAX_SAM_ACCOUNT_LEN characters\n", flags->sAMAccountName.c_str() ); fprintf(stderr, "Error: You can specify a shorter name using " "--computer-name\n" ); exit(1); } VERBOSE("sAMAccountName: %s", flags->sAMAccountName.c_str()); if (exec->mode == MODE_CREATE && !flags->use_service_account) { exec->add_principals.push_back("host"); } /* Qualify entries in the principals list */ qualify_principal_vec(exec->add_principals, flags); qualify_principal_vec(exec->remove_principals, flags); /* Now, try to get Kerberos credentials in order to connect to * LDAP. */ flags->auth_type = find_working_creds(flags); if (flags->auth_type == AUTH_NONE) { fprintf(stderr, "Error: Could not find any credentials to authenticate with. " "Neither keytab,\n" "default machine password, nor calling user's tickets worked. " "Try\n\"kinit\"ing yourself some tickets with permission to " "create computer\nobjects, or pre-creating the computer " "object in AD and selecting\n'reset account'.\n" ); exit(1); } /* If we didn't get Kerberos credentials because the old password * has expired we need to change it now */ if (flags->auth_type == AUTH_FROM_SUPPLIED_EXPIRED_PASSWORD) { VERBOSE("Account password expired, changing it now..."); ret = set_password(flags); if (ret) { fprintf(stderr, "Error: failed to change password\n"); exit(1); } if (!get_creds(flags)) { fprintf(stderr, "Error: failed to get Kerberos credentials\n"); exit(1); } } VERBOSE("Authenticated using method %d", flags->auth_type); flags->ldap = new LDAPConnection(flags->server, flags->sasl_mechanisms, flags->no_reverse_lookups); if (!flags->ldap->is_connected()) { fprintf(stderr, "Error: ldap_connect failed\n"); /* Print a hint as to the likely cause: */ if (flags->auth_type == AUTH_FROM_USER_CREDS) { fprintf(stderr, "--> Is your Kerberos ticket expired? " "You might try re-\"kinit\"ing.\n" ); } if (flags->no_reverse_lookups == false) { fprintf(stderr, "--> Is DNS configured correctly? "); fprintf(stderr, "You might try options \"--server\" " "and \"--no-reverse-lookups\".\n" ); } exit(1); } ldap_get_base_dn(flags); get_default_ou(flags); return 0; } int add_and_remove_principals(msktutil_exec *exec) { int ret = 0; std::vector &cur_princs(Globals::flags()->ad_principals); for (size_t i = 0; i < exec->add_principals.size(); ++i) { std::string principal = exec->add_principals[i]; if (std::find(cur_princs.begin(), cur_princs.end(), principal) == cur_princs.end()) { /* Not already in the list, so add it. */ int loc_ret = ldap_add_principal(principal, Globals::flags()); if (loc_ret) { fprintf(stderr, "Error: ldap_add_principal failed\n"); ret = 1; continue; } } } for (size_t i = 0; i < exec->remove_principals.size(); ++i) { std::string principal = exec->remove_principals[i]; if (std::find(cur_princs.begin(), cur_princs.end(), principal) != cur_princs.end()) { int loc_ret = ldap_remove_principal(principal, Globals::flags()); if (loc_ret) { fprintf(stderr, "Error: ldap_remove_principal failed\n"); ret = 1; continue; } } else { fprintf(stderr, "Error: principal %s cannot be removed, was not in " "servicePrincipalName.\n", principal.c_str() ); for (size_t i = 0; i < cur_princs.size(); ++i) fprintf(stderr, " %s\n", cur_princs[i].c_str()); ret = 1; } } return ret; } void do_help() { fprintf(stdout, "Usage: %s [MODE] [OPTIONS]\n", PACKAGE_NAME); fprintf(stdout, "\n"); fprintf(stdout, "Modes: \n"); fprintf(stdout, "\n"); fprintf(stdout, " create Creates a keytab for the current host or a given service account.\n"); fprintf(stdout, " (same as update -s host).\n"); fprintf(stdout, "\n"); fprintf(stdout, " update Updates the keytab for the current host or service account. This\n"); fprintf(stdout, " changes the account's password and updates the keytab with entries\n"); fprintf(stdout, " for all principals in servicePrincipalName and userPrincipalName.\n"); fprintf(stdout, " It also updates LDAP attributes for msDS-supportedEncryptionTypes,\n"); fprintf(stdout, " dNSHostName, and applies other options you specify.\n"); fprintf(stdout, "\n"); fprintf(stdout, " auto-update Same as update, but only if keytab fails to authenticate, or\n"); fprintf(stdout, " the last password change was more than 30 days ago\n"); fprintf(stdout, " (see --auto-update-interval). Useful to run from a daily cron job.\n"); fprintf(stdout, "\n"); fprintf(stdout, " pre-create Pre-create an account for the given host with default password\n"); fprintf(stdout, " but do not update local keytab.\n"); fprintf(stdout, " Requires -h or --computer-name argument.\n"); fprintf(stdout, " Implies --user-creds-only.\n"); fprintf(stdout, "\n"); fprintf(stdout, " flush Flushes all principals for the current host or service account\n"); fprintf(stdout, " from the keytab, and deletes servicePrincipalName from AD.\n"); fprintf(stdout, "\n"); fprintf(stdout, " delete Deletes the host or service account from Active Directory.\n"); fprintf(stdout, "\n"); fprintf(stdout, " cleanup Deletes entries from the keytab that are no longer needed.\n"); fprintf(stdout, "\n"); fprintf(stdout, "Common options: \n"); fprintf(stdout, " --help Displays this message\n"); fprintf(stdout, " -v, --version Display the current version\n"); fprintf(stdout, " --verbose Enable verbose messages.\n"); fprintf(stdout, " More then once to get LDAP debugging.\n"); fprintf(stdout, "\n"); fprintf(stdout, "Connection/setup options: \n"); fprintf(stdout, " -b, --base Sets the LDAP base OU to use when creating an account.\n"); fprintf(stdout, " The default is read from AD (often CN=computers).\n"); fprintf(stdout, " --computer-name , --account-name \n"); fprintf(stdout, " Sets the computer account name or service account name\n"); fprintf(stdout, " to .\n"); fprintf(stdout, " --old-account-password \n"); fprintf(stdout, " Use supplied computer account password or service\n"); fprintf(stdout, " account password for authentication.\n"); fprintf(stdout, " This option is mutually exclusive with --user-creds-only.\n"); fprintf(stdout, " -h, --hostname Use as current hostname.\n"); fprintf(stdout, " --password \n"); fprintf(stdout, " Specify the new account password instead of generating\n"); fprintf(stdout, " a random one. Consider the password policy settings when\n"); fprintf(stdout, " defining the string.\n"); fprintf(stdout, " --dont-change-password Do not create a new password. Try to use existing keys\n"); fprintf(stdout, " when performing keytab updates (update and create mode only).\n"); fprintf(stdout, " -k, --keytab Use for the keytab (both read and write).\n"); fprintf(stdout, " --keytab-auth-as \n"); fprintf(stdout, " First try to authenticate to AD as principal , using\n"); fprintf(stdout, " creds from the keytab, instead of using the account name\n"); fprintf(stdout, " principal or the host principal, etc.\n"); fprintf(stdout, " --server
Use a specific domain controller instead of looking\n"); fprintf(stdout, " up in DNS based upon realm.\n"); fprintf(stdout, " --server-behind-nat Ignore server IP validation error caused by NAT.\n"); fprintf(stdout, " --realm Use a specific Kerberos realm instead of using\n"); fprintf(stdout, " default_realm from krb5.conf.\n"); fprintf(stdout, " --site Find and use domain controller in specific AD site.\n"); fprintf(stdout, " This option is ignored if option --server is used.\n"); fprintf(stdout, " -N, --no-reverse-lookups\n"); fprintf(stdout, " Don't reverse-lookup the domain controller.\n"); fprintf(stdout, " -n, --no-canonical-name\n"); fprintf(stdout, " Do not attempt to canonicalize hostname while\n"); fprintf(stdout, " creating Kerberos principal(s).\n"); fprintf(stdout, " --user-creds-only Don't attempt to authenticate with machine keytab:\n"); fprintf(stdout, " only use user's credentials (from e.g. kinit).\n"); fprintf(stdout, " --auto-update-interval \n"); fprintf(stdout, " Number of when auto-update will change the\n"); fprintf(stdout, " account password. Defaults to 30 days.\n"); fprintf(stdout, " -m, --sasl-mechanisms \n"); fprintf(stdout, " Candidate SASL mechanisms to use when performing\n"); fprintf(stdout, " the LDAP bind. Defaults to \"GSS-SPNEGO GSSAPI\".\n"); fprintf(stdout, "\n"); fprintf(stdout, "Object type/attribute-setting options:\n"); fprintf(stdout, " --use-service-account Create and maintain service account instead of\n"); fprintf(stdout, " machine account.\n"); fprintf(stdout, " --enable Enable the account.\n"); fprintf(stdout, " --delegation Set the account to be trusted for delegation.\n"); fprintf(stdout, " --disable-delegation Set the account to not be trusted for\n"); fprintf(stdout, " delegation.\n"); fprintf(stdout, " --description Sets the description field on the account.\n"); fprintf(stdout, " --dont-expire-password Disables password expiration for the account.\n"); fprintf(stdout, " --dont-update-dnshostname\n"); fprintf(stdout, " Do not update dNSHostName attribute.\n"); fprintf(stdout, " --do-expire-password Undisables (puts back to default) password expiration.\n"); fprintf(stdout, " --enctypes Sets msDs-supportedEncryptionTypes\n"); fprintf(stdout, " (OR of: 0x1=des-cbc-crc 0x2=des-cbc-md5\n"); fprintf(stdout, " 0x4=rc4-hmac-md5 0x8=aes128-cts-hmac-sha1\n"); fprintf(stdout, " 0x10=aes256-cts-hmac-sha1)\n"); fprintf(stdout, " Sets des-only in userAccountControl if set to 0x3.\n"); fprintf(stdout, " --allow-weak-crypto Enables the usage of DES keys for authentication\n"); fprintf(stdout, " --no-pac Sets the service principal to not include a PAC.\n"); fprintf(stdout, " --disable-no-pac Sets the service principal to include a PAC.\n"); fprintf(stdout, " -s, --service Adds the service for the current host or the\n"); fprintf(stdout, " given service account. The service is of the form\n"); fprintf(stdout, " /.\n"); fprintf(stdout, " If the hostname is omitted, assumes current hostname,\n"); fprintf(stdout, " and adds the short and the full hostname.\n"); fprintf(stdout, " Default for service accounts: None"); fprintf(stdout, " --remove-service Same, but removes instead of adds.\n"); fprintf(stdout, " --upn Set the user principal name to be .\n"); fprintf(stdout, " The realm name will be appended to this principal.\n"); fprintf(stdout, " --set-samba-secret Use the net changesecretpw command to locally set the\n"); fprintf(stdout, " machine account password in samba's secrets.tdb.\n"); fprintf(stdout, " $PATH need to include Samba's net command.\n"); fprintf(stdout, " --use-samba-cmd Use the supplied command instead of samba\n"); fprintf(stdout, " net changesecretpw.\n"); fprintf(stdout, " --check-replication Wait until password change is reflected in LDAP.\n"); fprintf(stdout, "\n"); fprintf(stdout, "Cleanup options:\n"); fprintf(stdout, " --remove-old Removes entries older than days\n"); fprintf(stdout, " --remove-enctype \n"); fprintf(stdout, " Removes entries with given . Supported enctype\n"); fprintf(stdout, " strings are: des-cbc-crc,des-cbc-md5, arcfour, aes128\n"); fprintf(stdout, " and aes256\n"); } void do_version() { fprintf(stdout, "%s version %s\n", PACKAGE_NAME, PACKAGE_VERSION); } static int wait_for_new_kvno(msktutil_flags *flags) { if (!flags->check_replication) { return 0; } if (flags->auth_type == AUTH_FROM_SUPPLIED_EXPIRED_PASSWORD) { VERBOSE("Warning: authenticated with expired password -- " "no way to verify the password change in LDAP."); return 0; } VERBOSE("Checking new kvno via LDAP"); /* Loop and wait for the account and password set to replicate */ for (int this_time = 0; ; this_time += 5) { krb5_kvno current_kvno = ldap_get_kvno(flags); if (current_kvno == flags->kvno) { return 0; } fprintf(stdout, "Waiting for account replication (%d seconds past)\n", this_time); sleep(5); } } int execute(msktutil_exec *exec, msktutil_flags *flags) { int ret = 0; if (flags->password_from_cmdline) { VERBOSE("Using password from command line"); } else if (flags->dont_change_password) { VERBOSE("Skipping creation of new password"); flags->password = flags->old_account_password; } else if (exec->mode == MODE_CLEANUP) { VERBOSE("cleanup mode: don't need a new password"); } else if (exec->mode == MODE_DELETE) { VERBOSE("delete mode: don't need a new password"); } else { /* Generate a random password and store it. */ ret = generate_new_password(flags); if (ret) { fprintf(stderr, "Error: generate_new_password failed\n"); return ret; } } ret = finalize_exec(exec, flags); if (ret) { fprintf(stderr, "Error: finalize_exec failed\n"); exit(ret); } if (exec->mode == MODE_FLUSH) { if (flags->use_service_account) { fprintf(stdout, "Flushing all entries for service account %s from keytab %s\n", flags->sAMAccountName.c_str(), flags->keytab_writename.c_str()); } else { fprintf(stdout, "Flushing all entries for %s from keytab %s\n", flags->hostname.c_str(), flags->keytab_writename.c_str()); } ret = flush_keytab(flags); return ret; } else if (exec->mode == MODE_DELETE) { ret = ldap_delete_account(flags); return ret; } else if (exec->mode == MODE_CREATE || exec->mode == MODE_UPDATE || exec->mode == MODE_AUTO_UPDATE) { if (exec->mode == MODE_AUTO_UPDATE) { if (flags->auth_type == AUTH_FROM_SAM_KEYTAB || flags->auth_type == AUTH_FROM_SAM_UPPERCASE_KEYTAB || flags->auth_type == AUTH_FROM_EXPLICIT_KEYTAB) { std::string pwdLastSet = ldap_get_pwdLastSet(flags); /* Windows timestamp is in * 100-nanoseconds-since-1601. (or, tenths of * microseconds) */ long long windows_timestamp = strtoll(pwdLastSet.c_str(), NULL, 10); long long epoch_bias_1601_to_1970 = 116444736000000000LL; /* Unix timestamp is seconds since 1970. */ long long unix_timestamp; if (windows_timestamp < epoch_bias_1601_to_1970) { unix_timestamp = 0; } else { unix_timestamp = (windows_timestamp - epoch_bias_1601_to_1970) / 10000000; } time_t current_unix_time = time(NULL); long long days_since_password_change = (current_unix_time - unix_timestamp) / 86400; VERBOSE("Password last set %lld days ago.", days_since_password_change); if (days_since_password_change < flags->auto_update_interval) { VERBOSE("Exiting because password was changed recently."); return 0; } } } /* Check if computer account exists, update if so, create if * not. */ if (! ldap_check_account(flags)) { if (flags->password.empty()) { fprintf(stderr, "Error: A new AD account needs to be created " "but there is no password."); if (flags->dont_change_password) { fprintf(stderr, " Please provide a password with " "--old-account-password "); } fprintf(stderr, "\n"); exit(1); } else { ldap_create_account(flags); flags->kvno = ldap_get_kvno(flags); } } else { /* We retrieve the kvno _before_ the password change and * increment it. */ flags->kvno = ldap_get_kvno(flags); if ((flags->auth_type != AUTH_FROM_SUPPLIED_EXPIRED_PASSWORD) && (!flags->dont_change_password)) { flags->kvno++; } if ((flags->auth_type != AUTH_FROM_SUPPLIED_EXPIRED_PASSWORD) && (!flags->dont_change_password)) { /* Set the password. */ ret = set_password(flags); if (ret) { fprintf(stderr, "Error: set_password failed\n"); if (flags->use_service_account) { fprintf(stderr, "Hint: Does your password policy allow to " "change %s's password?\n", flags->sAMAccountName.c_str() ); fprintf(stderr, " For example, there could be a " "\"Minimum password age\" policy preventing\n" ); fprintf(stderr, " passwords from being changed " "too frequently. If so, you can reset the\n" ); fprintf(stderr, " password instead of changing " "it using the --user-creds-only option.\n" ); fprintf(stderr, " Be aware that you need a " "ticket of a user with administrative " "privileges\n" ); fprintf(stderr, " for that.\n"); } return ret; } } } /* Add and remove principals to servicePrincipalName in LDAP.*/ add_and_remove_principals(exec); remove_keytab_entries(flags, exec->remove_principals); /* update keytab */ if (flags->use_service_account) { VERBOSE("Updating all entries for service account %s in keytab %s", flags->sAMAccountName.c_str(), flags->keytab_writename.c_str()); } else { VERBOSE("Updating all entries for computer account %s in keytab %s", flags->sAMAccountName.c_str(), flags->keytab_writename.c_str()); } update_keytab(flags); add_keytab_entries(flags); wait_for_new_kvno(flags); return ret; } else if (exec->mode == MODE_PRECREATE) { /* Change account password to default value: */ flags->password = create_default_machine_password( flags->sAMAccountName); /* Check if computer account exists, update if so, create if * not. */ if (! ldap_check_account(flags)) { ldap_create_account(flags); } /* Set the password. */ ret = set_password(flags); if (ret) { fprintf(stderr, "Error: set_password failed\n"); return ret; } /* And add and remove principals to servicePrincipalName in * LDAP. */ add_and_remove_principals(exec); wait_for_new_kvno(flags); return ret; } else if (exec->mode == MODE_RESET) { /* reset mode will only work for machine accounts:*/ if (flags->use_service_account) { fprintf(stderr, "Error: \"reset\" mode and " "\"--use-service-account\" are " "mutually exclusive\n"); return 1; } /* Change account password to default value: */ flags->password = create_default_machine_password( flags->sAMAccountName); /* Check if computer account exists, update if so, error if * not. */ if (!ldap_check_account(flags)) { fprintf(stderr, "Error: The account %s does " "not exist and cannot be " "reset\n", flags->sAMAccountName.c_str()); return 1; } /* Set the password. */ ret = set_password(flags); if (ret) { fprintf(stderr, "Error: set_password failed\n"); return ret; } wait_for_new_kvno(flags); return ret; } else if (exec->mode == MODE_CLEANUP) { fprintf(stdout, "Cleaning keytab: %s\n", flags->keytab_writename.c_str()); cleanup_keytab(flags); return 0; } return 0; } void msktutil_exec::set_mode(msktutil_mode mode) { if (this->mode != MODE_NONE) { fprintf(stderr, "Error: Only one mode argument may be provided.\n"); fprintf(stderr, "\nFor help, try running %s --help\n\n", PACKAGE_NAME); exit(1); } this->mode = mode; } Globals *Globals::instance; int main(int argc, char *argv []) { /* unbuffer stdout. */ setbuf(stdout, NULL); initialize_g_context(); int i; int start_i; start_i = 2; msktutil_exec *exec = Globals::exec(); msktutil_flags *flags = Globals::flags(); if (argc > 1) { /* determine MODE */ if (!strcmp(argv[1], "create")) { exec->set_mode(MODE_CREATE); } else if (!strcmp(argv[1], "update")) { exec->set_mode(MODE_UPDATE); } else if (!strcmp(argv[1], "auto-update")) { exec->set_mode(MODE_AUTO_UPDATE); } else if (!strcmp(argv[1], "pre-create")) { exec->set_mode(MODE_PRECREATE); } else if (!strcmp(argv[1], "flush")) { exec->set_mode(MODE_FLUSH); } else if (!strcmp(argv[1], "cleanup")) { exec->set_mode(MODE_CLEANUP); } else if (!strcmp(argv[1], "delete")) { exec->set_mode(MODE_DELETE); } else if (!strcmp(argv[1], "reset")) { exec->set_mode(MODE_RESET); } } if (exec->mode == MODE_NONE) { /* compatibility for old command line syntax (e.g. "--create" * or "-c" instead of "create") */ start_i = 1; } for (i = start_i; i < argc; i++) { /* Display Version Message and exit */ if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--version")) { do_version(); return 0; } /* Display Help Messages and exit */ if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "--usage")) { do_help(); return 0; } /* Flush the keytab */ if (!strcmp(argv[i], "--flush") || !strcmp(argv[i], "-f")) { exec->set_mode(MODE_FLUSH); continue; } /* Update All Principals */ if (!strcmp(argv[i], "--update") || !strcmp(argv[i], "-u")) { exec->set_mode(MODE_UPDATE); continue; } /* Update All Principals, if needed */ if (!strcmp(argv[i], "--auto-update")) { exec->set_mode(MODE_AUTO_UPDATE); continue; } /* Create 'Default' Keytab */ if (!strcmp(argv[i], "--create") || !strcmp(argv[i], "-c")) { exec->set_mode(MODE_CREATE); continue; } /* Pre-create computer account for another host */ if (!strcmp(argv[i], "--precreate")) { exec->set_mode(MODE_PRECREATE); flags->user_creds_only = true; continue; } /* Service Principal Name */ if (!strcmp(argv[i], "--service") || !strcmp(argv[i], "-s")) { if (++i < argc) { exec->add_principals.push_back(argv[i]); } else { fprintf(stderr, "Error: no service principal given after '%s'\n", argv[i - 1] ); goto error; } continue; } if (!strcmp(argv[i], "--remove-service")) { if (++i < argc) { exec->remove_principals.push_back(argv[i]); } else { fprintf(stderr, "Error: no service principal given after '%s'\n", argv[i - 1] ); goto error; } continue; } /* Host name */ if (!strcmp(argv[i], "--host") || !strcmp(argv[i], "--hostname") || !strcmp(argv[i], "-h")) { if (++i < argc) { flags->hostname = argv[i]; } else { fprintf(stderr, "Error: no name given after '%s'\n", argv[i - 1] ); goto error; } continue; } /* no canonical name */ if (!strcmp(argv[i], "--no-canonical-name") || !strcmp(argv[i], "-n")) { flags->no_canonical_name = true; continue; } /* computer password */ if (!strcmp(argv[i], "--old-account-password")) { if (++i < argc) { flags->old_account_password = argv[i]; } else { fprintf(stderr, "Error: no password given after '%s'\n", argv[i - 1] ); goto error; } continue; } if (!strcmp(argv[i], "--password")) { if (++i < argc) { flags->password_from_cmdline = true; flags->password = argv[i]; } else { fprintf(stderr, "Error: no password given after '%s'\n", argv[i - 1] ); goto error; } continue; } /* do not change the password */ if (!strcmp(argv[i], "--dont-change-password")) { flags->dont_change_password = true; continue; } /* site */ if (!strcmp(argv[i], "--site")) { if (++i < argc) { flags->site = argv[i]; } else { fprintf(stderr, "Error: no site given after '%s'\n", argv[i - 1] ); goto error; } continue; } /* W2008 msDs-supportedEncryptionTypes */ if (!strcmp(argv[i], "--enctypes")) { if (++i < argc) { set_supportedEncryptionTypes(flags, argv[i]); } else { fprintf(stderr, "Error: no enctype after '%s'\n", argv[i - 1] ); goto error; } continue; } /* Re-activate DES encryption in fake krb5.conf */ if (!strcmp(argv[i], "--allow-weak-crypto")) { flags->allow_weak_crypto = true; continue; } /* Enable the account */ if (!strcmp(argv[i], "--enable")) { flags->disable_account = VALUE_OFF; continue; } /* Disable the PAC ? */ if (!strcmp(argv[i], "--no-pac")) { flags->no_pac = VALUE_ON; continue; } if (!strcmp(argv[i], "--disable-no-pac")) { flags->no_pac = VALUE_OFF; continue; } /* Use service account */ if (!strcmp(argv[i], "--use-service-account")) { flags->use_service_account = true; continue; } /* Trust for delegation ? */ if (!strcmp(argv[i], "--delegation")) { flags->delegate = VALUE_ON; continue; } if (!strcmp(argv[i], "--disable-delegation")) { flags->delegate = VALUE_OFF; continue; } /* Password expiry (is rotation required?) */ if (!strcmp(argv[i], "--dont-expire-password")) { flags->dont_expire_password = VALUE_ON; continue; } /* Prevent dnsHostName attribute update */ if (!strcmp(argv[i], "--dont-update-dnshostname")) { flags->dont_update_dnshostname = VALUE_ON; continue; } if (!strcmp(argv[i], "--do-expire-password")) { flags->dont_expire_password = VALUE_OFF; continue; } /* Use a certain sam account name */ if (!strcmp(argv[i], "--computer-name") || !strcmp(argv[i], "--account-name")) { if (++i < argc) { flags->sAMAccountName = argv[i]; } else { fprintf(stderr, "Error: no name given after '%s'\n", argv[i - 1] ); goto error; } continue; } if (!strcmp(argv[i], "--upn")) { if (++i < argc) { flags->set_userPrincipalName = true; flags->userPrincipalName = argv[i]; } else { fprintf(stderr, "Error: no principal given after '%s'\n", argv[i - 1] ); goto error; } continue; } /* Use certain keytab file */ if (!strcmp(argv[i], "--keytab") || !strcmp(argv[i], "-k")) { if (++i < argc) { flags->keytab_file = argv[i]; } else { fprintf(stderr, "Error: no file given after '%s'\n", argv[i - 1] ); goto error; } continue; } /* Use a certain LDAP base OU ? */ if (!strcmp(argv[i], "--base") || !strcmp(argv[i], "-b")) { if (++i < argc) { flags->ldap_ou = argv[i]; } else { fprintf(stderr, "Error: no base given after '%s'\n", argv[i - 1] ); goto error; } continue; } /* Set the description on the computer account */ if (!strcmp(argv[i], "--description")) { if (++i < argc) { flags->description = argv[i]; } else { fprintf(stderr, "Error: no description given after '%s'\n", argv[i - 1] ); goto error; } continue; } /* Use a certain LDAP server */ if (!strcmp(argv[i], "--server")) { if (++i < argc) { flags->server = argv[i]; } else { fprintf(stderr, "Error: no server given after '%s'\n", argv[i - 1] ); goto error; } continue; } /* ignore server IP validation error caused by NAT */ if (!strcmp(argv[i], "--server-behind-nat")) { flags->server_behind_nat = true; continue; } /* Use a certain realm */ if (!strcmp(argv[i], "--realm")) { if (++i < argc) { flags->realm_name = argv[i]; } else { fprintf(stderr, "Error: no realm given after '%s'\n", argv[i - 1] ); goto error; } continue; } /* do not reverse lookup server names */ if (!strcmp(argv[i], "--no-reverse-lookups") || !strcmp(argv[i], "-N")) { flags->no_reverse_lookups = true; continue; } /* synchronize machine password with samba */ if (!strcmp(argv[i], "--set-samba-secret")) { flags->set_samba_secret = true; continue; } /* use supplied command instead of samba net */ if (!strcmp(argv[i], "--use-samba-cmd")) { if (++i < argc) { flags->samba_cmd = argv[i]; } else { fprintf(stderr, "Error: no command given after '%s'\n", argv[i -1] ); goto error; } continue; } /* Use user Kerberos credentials only */ if (!strcmp(argv[i], "--user-creds-only")) { flags->user_creds_only = true; continue; } if (!strcmp(argv[i], "--keytab-auth-as")) { if (++i < argc) { flags->keytab_auth_princ = argv[i]; } else { fprintf(stderr, "Error: no principal given after '%s'\n", argv[i - 1] ); goto error; } continue; } if (!strcmp(argv[i], "--auto-update-interval")) { if (++i < argc) { flags->auto_update_interval = atoi(argv[i]); } else { fprintf(stderr, "Error: no number given after '%s'\n", argv[i - 1] ); goto error; } continue; } if (!strcmp(argv[i], "--sasl-mechanisms") || !strcmp(argv[i], "-m")) { if (++i < argc) { flags->sasl_mechanisms = argv[i]; } else { fprintf(stderr, "Error: no SASL candidate mechanisms list given after '%s'\n", argv[i - 1] ); goto error; } continue; } if (!strcmp(argv[i], "--remove-old")) { if (++i < argc) { flags->cleanup_days = atoi(argv[i]); } else { fprintf(stderr, "Error: no number given after '%s'\n", argv[i - 1] ); goto error; } continue; } if (!strcmp(argv[i], "--remove-enctype")) { if (++i < argc) { flags->cleanup_enctype = parse_enctype(argv[i]); } else { fprintf(stderr, "Error: no number given after '%s'\n", argv[i - 1] ); goto error; } continue; } /* wait for LDAP replication */ if (!strcmp(argv[i], "--check-replication")) { flags->check_replication = true; continue; } /* Display Verbose Messages */ if (!strcmp(argv[i], "--verbose")) { do_verbose(); continue; } /* Unrecognized */ fprintf(stderr, "Error: unknown parameter: %s\n", argv[i]); goto error; } /* make --old-account-password and --user-creds-only mutually * exclusive: */ if (strlen(flags->old_account_password.c_str()) && flags->user_creds_only) { fprintf(stderr, "Error: --old-account-password and --user-creds-only " "are mutually exclusive\n"); goto error; } if (strcmp(flags->samba_cmd.c_str(),DEFAULT_SAMBA_CMD) && !flags->set_samba_secret) { fprintf(stderr, "Error: --use-samba-cmd (or MSKTUTIL_SAMBA_CMD " "environment variable) can only be used with " "--set-samba-secret\n"); goto error; } /* allow --dont-change-password only in update mode or when create * mode is called with --old-account-password */ if (flags->dont_change_password && !(exec->mode == MODE_UPDATE || exec->mode == MODE_CREATE) ) { fprintf(stderr, "Error: --dont-change-password can only be used in update or create mode\n" ); goto error; } if (flags->dont_change_password && exec->mode == MODE_CREATE && flags->old_account_password.empty()) { fprintf(stderr, "Error: --dont-change-password needs --old-account-password in create mode\n" ); goto error; } /* allow --remove-enctype only in cleanup mode */ if (exec->mode != MODE_CLEANUP && flags->cleanup_enctype != VALUE_IGNORE) { fprintf(stderr, "Error: --remove-enctype can only be used in cleanup mode\n" ); goto error; } /* allow --remove-old only in cleanup mode */ if (exec->mode != MODE_CLEANUP && flags->cleanup_days != -1) { fprintf(stderr, "Error: --remove-old can only be used in cleanup mode\n" ); goto error; } if (flags->enctypes == VALUE_ON) { if ((flags->supportedEncryptionTypes | ALL_MS_KERB_ENCTYPES) != ALL_MS_KERB_ENCTYPES) { fprintf(stderr, "Error: unsupported --enctypes must be integer that " "fits mask=0x%x\n", ALL_MS_KERB_ENCTYPES ); goto error; } if (flags->supportedEncryptionTypes == 0) { fprintf(stderr, "Error: --enctypes must not be zero\n"); goto error; } } if (exec->mode == MODE_NONE && !exec->add_principals.empty()) { exec->set_mode(MODE_UPDATE); } if (exec->mode == MODE_CLEANUP && flags->cleanup_days == -1 && flags->cleanup_enctype == VALUE_IGNORE) { fprintf(stderr, "Error: cleanup mode needs --remove-old or " "--remove-enctype\n" ); goto error; } if (exec->mode == MODE_NONE) { /* Default, no options present */ fprintf(stderr, "Error: no command given\n"); goto error; } /* delete mode will only work with admin credentials */ if (exec->mode == MODE_DELETE) { flags->user_creds_only = true; } /* reset mode will only work with admin credentials */ if (exec->mode == MODE_RESET) { flags->user_creds_only = true; } try { return execute(exec, flags); } catch (Exception &e) { fprintf(stderr, "%s\n", e.what()); exit(1); } error: fprintf(stderr, "\nFor help, try running %s --help\n\n", PACKAGE_NAME); return 1; } Globals* Globals::get() { if (instance==NULL) { instance = new Globals(); instance->_flags = new msktutil_flags; instance->_exec = new msktutil_exec; } return instance; } void Globals::set_supportedEncryptionTypes(char * value) { _flags->enctypes = VALUE_ON; _flags->supportedEncryptionTypes = strtol(value, NULL, 0); } msktutil_flags::msktutil_flags() : password(), password_from_cmdline(false), ldap(NULL), set_userPrincipalName(false), no_reverse_lookups(false), no_canonical_name(false), server_behind_nat(false), set_samba_secret(false), samba_cmd(DEFAULT_SAMBA_CMD), check_replication(false), dont_change_password(false), dont_expire_password(VALUE_IGNORE), dont_update_dnshostname(VALUE_OFF), disable_account(VALUE_IGNORE), no_pac(VALUE_IGNORE), delegate(VALUE_IGNORE), ad_userAccountControl(0), ad_enctypes(VALUE_IGNORE), ad_supportedEncryptionTypes(0), enctypes(VALUE_IGNORE), /* default values we *want* to support */ supportedEncryptionTypes(DEFAULT_MS_KERB_ENCTYPES), auth_type(0), user_creds_only(false), use_service_account(false), allow_weak_crypto(false), password_expired(false), auto_update_interval(30), sasl_mechanisms(DEFAULT_SASL_MECHANISMS), kvno(0), cleanup_days(-1), cleanup_enctype(VALUE_IGNORE) { /* Check for environment variables as well. These variables will * be overridden by command line arguments. */ if (getenv("MSKTUTIL_KEYTAB")) { keytab_file = getenv("MSKTUTIL_KEYTAB"); } if (getenv("MSKTUTIL_NO_PAC")) { no_pac = VALUE_ON; } if (getenv("MSKTUTIL_DELEGATION")) { delegate = VALUE_ON; } if (getenv("MSKTUTIL_LDAP_BASE")) { ldap_ou = getenv("MSKTUTIL_LDAP_BASE"); } if (getenv("MSKTUTIL_SERVER")) { server = getenv("MSKTUTIL_SERVER"); } if (getenv("MSKTUTIL_SAMBA_CMD")) { samba_cmd = getenv("MSKTUTIL_SAMBA_CMD"); } } msktutil_flags::~msktutil_flags() { ldap_cleanup(this); init_password(this); } msktutil_exec::msktutil_exec() : mode(MODE_NONE) { } msktutil_exec::~msktutil_exec() { VERBOSE("Destroying msktutil_exec"); remove_fake_krb5_conf(); remove_ccache(); } msktutil-1.2.2/msktutil.h000066400000000000000000000254551467024531100154330ustar00rootroot00000000000000/* *---------------------------------------------------------------------------- * * msktutil.h * * (C) 2004-2006 Dan Perry (dperry@pppl.gov) * (C) 2006 Brian Elliott Finley (finley@anl.gov) * (C) 2009-2010 Doug Engert (deengert@anl.gov) * (C) 2010 James Y Knight (foom@fuhm.net) * (C) 2010-2013 Ken Dreyer * (C) 2012-2017 Mark Proehl * (C) 2012-2017 Olaf Flebbe * (C) 2013-2017 Daniel Kobras * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *----------------------------------------------------------------------------- */ #ifndef __msktutil_h__ #define __msktutil_h__ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_COM_ERR_H # ifdef COM_ERR_NEEDS_EXTERN_C extern "C" { # endif #include # ifdef COM_ERR_NEEDS_EXTERN_C } # endif #endif #include #include #include #include #include #ifndef PACKAGE_NAME #define PACKAGE_NAME "msktutil" #endif #define PASSWORD_LEN 63 #define MAX_HOSTNAME_LEN 255 #define MAX_TRIES 10 #define MAX_SAM_ACCOUNT_LEN 20 #define MAX_DEF_MACH_PASS_LEN 14 #define MAX_DOMAIN_CONTROLLERS 20 #ifndef TMP_DIR #define TMP_DIR "/tmp" #endif /* In case it's not in krb5.h */ #ifndef MAX_KEYTAB_NAME_LEN #define MAX_KEYTAB_NAME_LEN 1100 #endif /* From SAM.H */ #define UF_WORKSTATION_TRUST_ACCOUNT 0x00001000 #define UF_ACCOUNT_DISABLE 0x00000002 #define UF_NORMAL_ACCOUNT 0x00000200 #define UF_DONT_EXPIRE_PASSWORD 0x00010000 #define UF_TRUSTED_FOR_DELEGATION 0x00080000 #define UF_USE_DES_KEY_ONLY 0x00200000 #define UF_NO_AUTH_DATA_REQUIRED 0x02000000 /* for msDs-supportedEncryptionTypes bit defines */ #define MS_KERB_ENCTYPE_DES_CBC_CRC 0x01 #define MS_KERB_ENCTYPE_DES_CBC_MD5 0x02 #define MS_KERB_ENCTYPE_RC4_HMAC_MD5 0x04 /* Define these if the system supports them, otherwise define to 0. */ #if HAVE_DECL_ENCTYPE_AES128_CTS_HMAC_SHA1_96 #define MS_KERB_ENCTYPE_AES128_CTC_HMAC_SHA1_96 0x08 #else #define MS_KERB_ENCTYPE_AES128_CTC_HMAC_SHA1_96 0 #endif #if HAVE_DECL_ENCTYPE_AES128_CTS_HMAC_SHA1_96 #define MS_KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 0x10 #else #define MS_KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 0 #endif #define MS_KERB_DES_ENCTYPES \ ( MS_KERB_ENCTYPE_DES_CBC_CRC | \ MS_KERB_ENCTYPE_DES_CBC_MD5 ) #define DEFAULT_MS_KERB_ENCTYPES \ ( MS_KERB_ENCTYPE_RC4_HMAC_MD5 | \ MS_KERB_ENCTYPE_AES128_CTC_HMAC_SHA1_96 | \ MS_KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 ) #define ALL_MS_KERB_ENCTYPES \ ( MS_KERB_DES_ENCTYPES | \ MS_KERB_ENCTYPE_RC4_HMAC_MD5 | \ MS_KERB_ENCTYPE_AES128_CTC_HMAC_SHA1_96 | \ MS_KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 ) /* Some KVNO Constansts */ #define KVNO_FAILURE -1 #define KVNO_WIN_2000 0 #define DEFAULT_SAMBA_CMD "net changesecretpw -f -i" /* Default candidate SASL mechanisms */ #define DEFAULT_SASL_MECHANISMS "GSS-SPNEGO GSSAPI" /* Ways we can authenticate */ enum auth_types { AUTH_NONE = 0, AUTH_FROM_SAM_KEYTAB, AUTH_FROM_SAM_UPPERCASE_KEYTAB, AUTH_FROM_HOSTNAME_KEYTAB, AUTH_FROM_PASSWORD, AUTH_FROM_USER_CREDS, AUTH_FROM_SUPPLIED_PASSWORD, AUTH_FROM_SUPPLIED_EXPIRED_PASSWORD, AUTH_FROM_EXPLICIT_KEYTAB, }; class LDAPConnection; enum msktutil_val { VALUE_OFF = 0, VALUE_ON = 1, VALUE_IGNORE = 2 }; extern int g_verbose; enum msktutil_mode { MODE_NONE = 0, MODE_CREATE, MODE_UPDATE, MODE_AUTO_UPDATE, MODE_FLUSH, MODE_CLEANUP, MODE_PRECREATE, MODE_DELETE, MODE_RESET }; class msktutil_flags { public: std::string keytab_file; std::string keytab_writename; std::string keytab_readname; std::string keytab_auth_princ; std::string ldap_ou; std::string hostname; std::string description; std::string server; std::string realm_name; std::string lower_realm_name; std::string base_dn; std::string sAMAccountName; std::string sAMAccountName_nodollar; std::string sAMAccountName_uppercase; std::string password; bool password_from_cmdline; std::string userPrincipalName; std::string old_account_password; std::string site; LDAPConnection* ldap; std::string ad_computerDn; std::string ad_dnsHostName; std::vector ad_principals; bool set_userPrincipalName; bool no_reverse_lookups; bool no_canonical_name; bool server_behind_nat; bool set_samba_secret; std::string samba_cmd; bool check_replication; bool dont_change_password; msktutil_val dont_expire_password; msktutil_val dont_update_dnshostname; msktutil_val disable_account; msktutil_val no_pac; msktutil_val delegate; unsigned int ad_userAccountControl; /* value AD has now */ int ad_enctypes; /* if msDs-supportedEncryptionTypes in AD */ unsigned int ad_supportedEncryptionTypes; /* value AD has now */ int enctypes; /* if --enctypes parameter was set */ unsigned int supportedEncryptionTypes; int auth_type; bool user_creds_only; bool use_service_account; bool allow_weak_crypto; bool password_expired; int auto_update_interval; std::string sasl_mechanisms; krb5_kvno kvno; int cleanup_days; int cleanup_enctype; msktutil_flags(); private: msktutil_flags operator=(const msktutil_flags& other); msktutil_flags(const msktutil_flags& other); ~msktutil_flags(); }; class msktutil_exec { public: msktutil_mode mode; std::vector add_principals; std::vector remove_principals; msktutil_exec(); ~msktutil_exec(); void set_mode(msktutil_mode mode); }; class Globals { msktutil_flags* _flags; msktutil_exec* _exec; static Globals *instance; public: static Globals* get(); static msktutil_flags *flags() { return Globals::get()->_flags; } static msktutil_exec *exec() { return Globals::get()->_exec; } void set_supportedEncryptionTypes(char * value); }; /* Prototypes */ extern std::string create_default_machine_password(const std::string &sAMAccountName); extern void ldap_cleanup(msktutil_flags *); extern void init_password(msktutil_flags *); extern std::string get_default_hostname(bool no_canonical_name = false); extern void get_default_keytab(msktutil_flags *); extern std::string get_salt(msktutil_flags *); extern void get_default_ou(msktutil_flags *); extern void ldap_get_base_dn(msktutil_flags *); extern std::string complete_hostname(const std::string &, bool no_canonical_name = false); extern std::string get_default_samaccountname(msktutil_flags *); extern std::string get_short_hostname(msktutil_flags *); extern int flush_keytab(msktutil_flags *); extern void cleanup_keytab(msktutil_flags *); extern void update_keytab(msktutil_flags *); extern void add_keytab_entries(msktutil_flags *); extern void remove_keytab_entries(msktutil_flags *,std::vector); extern void add_principal_keytab(const std::string &, msktutil_flags *); extern int ldap_flush_principals(msktutil_flags *); extern int set_password(msktutil_flags *); extern krb5_kvno ldap_get_kvno(msktutil_flags *); extern std::string ldap_get_pwdLastSet(msktutil_flags *); extern std::vector ldap_list_principals(msktutil_flags *); extern int ldap_add_principal(const std::string &, msktutil_flags *); int ldap_remove_principal(const std::string &principal, msktutil_flags *flags); extern std::string get_dc_host(const std::string &realm_name, const std::string &site_name, const bool); extern bool ldap_check_account(msktutil_flags *); extern void ldap_create_account(msktutil_flags *); extern int ldap_delete_account(msktutil_flags *); extern void create_fake_krb5_conf(msktutil_flags *); extern void remove_fake_krb5_conf(); extern void remove_ccache(); int find_working_creds(msktutil_flags *flags); bool get_creds(msktutil_flags *flags); int generate_new_password(msktutil_flags *flags); /* Verbose messages */ #define VERBOSE(text...) if (g_verbose) { fprintf(stdout, " -- %s: ", __FUNCTION__); fprintf(stdout, ## text); fprintf(stdout, "\n"); } /* Fatal error */ void error_exit(const char *text); /* To be used by higher level error messages, like krb5 */ void v_error_exit(const char* format, ...); /* printf into a C++ string. */ std::string sform(const char* format, ...); class Exception : public std::exception { protected: std::string m_message; /* Prohibit assignment */ Exception& operator=(const Exception&); public: /* Constructors */ /* Default construction with no message uses "Exception" */ Exception() : m_message("Exception") { } explicit Exception(char const * simple_string) : m_message(simple_string) {} explicit Exception(const std::string &str) : m_message(str) {} Exception(const Exception& src) : exception(), m_message(src.m_message) {} virtual ~Exception() throw() {}; char const * what() const throw() { return m_message.c_str(); } }; class KRB5Exception : public Exception { protected: krb5_error_code m_err; public: explicit KRB5Exception(const std::string &func, krb5_error_code err) : Exception(sform("Error: %s failed: %s", func.c_str(), error_message(err))) { m_err = err; } krb5_error_code err() const throw() { return m_err; } }; class LDAPException : public Exception { public: explicit LDAPException(const std::string &func, int err) : Exception(sform("Error: %s failed: %s", func.c_str(), ldap_err2string(err))) {} }; #ifdef __GNUC__ #define ATTRUNUSED __attribute__((unused)) #else #define ATTRUNUSED #endif #include "krb5wrap.h" #include "ldapconnection.h" #include "msktname.h" #endif msktutil-1.2.2/samba-password-command.sh000077500000000000000000000036331467024531100202760ustar00rootroot00000000000000#!/bin/bash # # Workaround for https://github.com/msktutil/msktutil/issues/124 # # for Samba 4.7 (RHEL/CentOS 7) # # 22.09.2018 jaroslaw.polok@gmail.com # # Usage: # # msktutil --set-samba-secret --use-samba-cmd /path/to/this/script # # MSKTUTIL_SAMBA_CMD=/pat/tp/this/script msktutil --set-samba-secret # # # Samba workgroup (UPPERCASE) # WORKGROUP="MYWORKGROUP" # WORKGROUP="" # # Active Directory Domain Controller to query for Domain SID # DCHOST="mydc.my.domain" # DCHOST="" # # Samba utilities used. # (RHEL/CentOS 7: tdb-tools and samba-common-tools rpms) # TDBTOOL="/usr/bin/tdbtool" NET="/usr/bin/net" TESTPARM="/usr/bin/testparm" err_exit() { echo "$0: Error in line $1: $ERR_MSG" >&2 } SAMBA_MSG="Invalid Samba configuration Adjust your SAMBA configuration first in /etc/samba/smb.conf [global] section must contain at least following directives: security = ADS workgroup = $WORKGROUP" trap 'err_exit $LINENO' EXIT set -e ERR_MSG="must be run as root." [ "$UID" -eq 0 ] ERR_MSG="WORKGROUP not set" [ "x$WORKGROUP" != "x" ] ERR_MSG="DCHOST not set" [ "x$DCHOST" != "x" ] ERR_MSG="tdbtool and net SAMBA utilities are not installed." [ -x "$TDBTOOL" ] && [ -x "$NET" ] ERR_MSG="$SAMBA_MSG" "$TESTPARM" -l -s --parameter-name workgroup 2>/dev/null | /usr/bin/grep -q -x "$WORKGROUP" "$TESTPARM" -l -s --parameter-name security 2>/dev/null | /usr/bin/grep -q -x "ADS" ERR_MSG="$NET getsid failed" "$NET" -k -P rpc getsid -S "$DCHOST" ERR_MSG="$TDBTOOL failed" "$TDBTOOL" /var/lib/samba/private/secrets.tdb store SECRETS/MACHINE_LAST_CHANGE_TIME/"$WORKGROUP" 0000 2>/dev/null 1>/dev/null "$TDBTOOL" /var/lib/samba/private/secrets.tdb store SECRETS/MACHINE_PASSWORD.PREV/"$WORKGROUP" none 2>/dev/null 1>/dev/null "$TDBTOOL" /var/lib/samba/private/secrets.tdb store SECRETS/MACHINE_PASSWORD/"$WORKGROUP" none 2>/dev/null 1>/dev/null ERR_MSG="$NET changesecretpw failed" exec "$NET" changesecretpw -f -i msktutil-1.2.2/strtoll.c000066400000000000000000000111701467024531100152420ustar00rootroot00000000000000/*- * Copyright (c) 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. [rescinded 22 July 1999] * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "config.h" #ifndef HAVE_STRTOLL #ifdef HAVE_LIMITS_H #include #endif #ifdef HAVE_SYS_PARAM_H #include #endif #include #ifdef NEED_DECLARATION_ERRNO extern int errno; #endif #include "ctype.h" #define ISUPPER isupper #define ISSPACE isspace #define ISDIGIT isdigit #define ISALPHA isalpha /* FIXME: It'd be nice to configure around these, but the include files are too painful. These macros should at least be more portable than hardwired hex constants. */ #ifndef ULONG_LONG_MAX #define ULONG_LONG_MAX ((unsigned long long)(~0LL)) #endif #ifndef LONG_LONG_MAX #define LONG_LONG_MAX ((long long)(ULONG_LONG_MAX >> 1)) #endif #ifndef LONG_LONG_MIN #define LONG_LONG_MIN ((long long)(~LONG_LONG_MAX)) #endif /* * Convert a string to a long long integer. * * Ignores `locale' stuff. Assumes that the upper and lower case * alphabets and digits are each contiguous. */ long long strtoll(const char *nptr, char **endptr, register int base) { register const char *s = nptr; register unsigned long long acc; register int c; register unsigned long long cutoff; register int neg = 0, any, cutlim; /* * Skip white space and pick up leading +/- sign if any. * If base is 0, allow 0x for hex and 0 for octal, else * assume decimal; if base is already 16, allow 0x. */ do { c = *s++; } while (ISSPACE(c)); if (c == '-') { neg = 1; c = *s++; } else if (c == '+') c = *s++; if ((base == 0 || base == 16) && c == '0' && (*s == 'x' || *s == 'X')) { c = s[1]; s += 2; base = 16; } if (base == 0) base = c == '0' ? 8 : 10; /* * Compute the cutoff value between legal numbers and illegal * numbers. That is the largest legal value, divided by the * base. An input number that is greater than this value, if * followed by a legal input character, is too big. One that * is equal to this value may be valid or not; the limit * between valid and invalid numbers is then based on the last * digit. For instance, if the range for long longs is * [-2147483648..2147483647] and the input base is 10, * cutoff will be set to 214748364 and cutlim to either * 7 (neg==0) or 8 (neg==1), meaning that if we have accumulated * a value > 214748364, or equal but the next digit is > 7 (or 8), * the number is too big, and we will return a range error. * * Set any if any `digits' consumed; make it negative to indicate * overflow. */ cutoff = neg ? -(unsigned long long)LONG_LONG_MIN : LONG_LONG_MAX; cutlim = cutoff % (unsigned long long)base; cutoff /= (unsigned long long)base; for (acc = 0, any = 0;; c = *s++) { if (ISDIGIT(c)) c -= '0'; else if (ISALPHA(c)) c -= ISUPPER(c) ? 'A' - 10 : 'a' - 10; else break; if (c >= base) break; if (any < 0 || acc > cutoff || acc == cutoff && c > cutlim) any = -1; else { any = 1; acc *= base; acc += c; } } if (any < 0) { acc = neg ? LONG_LONG_MIN : LONG_LONG_MAX; errno = ERANGE; } else if (neg) acc = -acc; if (endptr != 0) *endptr = (char *) (any ? s - 1 : nptr); return (acc); } #endif msktutil-1.2.2/strtoll.h000066400000000000000000000002121467024531100152420ustar00rootroot00000000000000#ifndef __strtoll_h__ #define __strtoll_h__ extern "C" { long long strtoll(const char *nptr, char **endptr, register int base); } #endif msktutil-1.2.2/windows-salt.txt000066400000000000000000000043551467024531100165760ustar00rootroot00000000000000Some Thoughts about Salting in Active Directory... =============================================================================== Windows uses realm_name+"host"+samAccountName_nodollar+"."+lower_realm_name for the salt of machine accounts. (Note: samaccountname_nodollar is lower case for machine accounts) (Note: only for DES/AES; arcfour-hmac-md5 doesn't use salts at all) Salt for service accounts is created in a different way: - if userPrincpalName is not set: realm_name+samAccountName (Note: samAccountName is case sensitive for service accounts) - if userPrincpalName is set: realm_name + first component from userPrincpalName (Windows 2000 may have used something different, but who cares...) FIXME: this is stupid, and not future proof. The salt is supposed to be an implementation detail that the server can set to whatever it feels like (so long as it doesn't change it except when the password changes). A future version of Windows may change the salting algorithm to something else, or may even start using random salts. In the normal authentication path, the client asks the KDC what salt to use when encrypting the password for the account, and then uses that. And for the creation of a keytab in MIT Kerberos (in the suual case), you use the kadmin protocol to download the already salted key block. But, here, we need to take a password and encrypt it the same way the server is going to, in order to store it in the keytab. All we need is to ask the server what salt it wants to use...But, as far as I can tell, there exists no API in libkrb5 that can retrieve the salt that should be used with a given principal, even though it's clearly available in the network protocol. Note: even if the salting string could be fetched from the network protocol, that would only be possible after the password has been set in AD. But the keytab entry should be created before that. What we're doing here is very much like MIT Kerberos' ktutil addent -password, which also assumes the server uses a particular salt. And that is also broken. Given this email thread: , I hope libkrb5 will provide the proper API before MS switches to start using randomized salts in some future AD release.