pax_global_header00006660000000000000000000000064151341076070014516gustar00rootroot0000000000000052 comment=39eee92e62096dc9d2121f1f2c7e7ac343bd0e3b account-utils-1.0.1/000077500000000000000000000000001513410760700143075ustar00rootroot00000000000000account-utils-1.0.1/.github/000077500000000000000000000000001513410760700156475ustar00rootroot00000000000000account-utils-1.0.1/.github/workflows/000077500000000000000000000000001513410760700177045ustar00rootroot00000000000000account-utils-1.0.1/.github/workflows/ci-opensuse.yml000066400000000000000000000041071513410760700226630ustar00rootroot00000000000000name: openSUSE build & test on: [push, pull_request] jobs: build-gcc: runs-on: ubuntu-latest container: registry.opensuse.org/opensuse/tumbleweed:latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install devel packages run: | zypper ref -f zypper --non-interactive in --no-recommends meson gcc libeconf-devel systemd-devel pam-devel libselinux-devel libcap-devel valgrind docbook5-xsl-stylesheets libxslt-tools - name: Setup meson run: meson setup build --auto-features=enabled - name: Compile code run: meson compile -v -C build - name: Run tests run: meson test -v -C build build-clang: runs-on: ubuntu-latest env: CC: clang container: registry.opensuse.org/opensuse/tumbleweed:latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install devel packages run: | zypper ref -f zypper --non-interactive in --no-recommends meson clang llvm-gold gcc libeconf-devel systemd-devel pam-devel libselinux-devel libcap-devel valgrind docbook5-xsl-stylesheets libxslt-tools - name: Setup meson run: meson setup build --auto-features=enabled - name: Compile code run: meson compile -v -C build - name: Run tests run: meson test -v -C build sanitizer: runs-on: ubuntu-latest container: registry.opensuse.org/opensuse/tumbleweed:latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install devel packages run: | zypper ref zypper --non-interactive in --no-recommends meson gcc libeconf-devel systemd-devel pam-devel libselinux-devel libcap-devel valgrind docbook5-xsl-stylesheets libxslt-tools - name: Setup meson run: meson setup build --auto-features=enabled -Db_sanitize=address,undefined - name: Compile code run: meson compile -v -C build - name: Run tests run: meson test -v -C build # meson test -v -C build --wrap='valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1' account-utils-1.0.1/.gitignore000066400000000000000000000001641513410760700163000ustar00rootroot00000000000000# Object files *.o *.ko *.obj *.elf # Libraries *.lib *.a *.la *.lo # Shared objects *.so *.so.* # Misc build *~ account-utils-1.0.1/INSTALL.md000066400000000000000000000005721513410760700157430ustar00rootroot00000000000000# Building and installing account-utils ## Building with Meson account-utils requires Meson 0.61.0 or newer. Building with Meson is quite simple: ```shell $ meson setup build $ meson compile -C build $ meson test -C build $ sudo meson install -C build ``` If you want to build with the address sanitizer enabled, add `-Db_sanitize=address` as an argument to `meson setup`. account-utils-1.0.1/LICENSE.BSD-2-Clause000066400000000000000000000023631513410760700173000ustar00rootroot00000000000000BSD 2-Clause License 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. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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. account-utils-1.0.1/LICENSE.GPL2000066400000000000000000000431031513410760700160200ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, 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 Lesser 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 Street, 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 Lesser General Public License instead of this License. account-utils-1.0.1/LICENSE.LGPL2.1000066400000000000000000000636421513410760700163050ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, 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. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, 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 and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, 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 library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete 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 distribute a copy of this License along with the Library. 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 Library or any portion of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, 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 Library, 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 Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you 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. If distribution of 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 satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be 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. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library 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. 9. 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 Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library 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 with this License. 11. 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 Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library 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 Library. 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. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library 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. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser 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 Library 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 Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, 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 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. 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 LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. 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 library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! account-utils-1.0.1/NEWS000066400000000000000000000007341513410760700150120ustar00rootroot00000000000000account-utils NEWS -- history of user-visible changes. Copyright (C) 2025, 2026 Thorsten Kukuk . Please enter bug reports at https://github.com/thkukuk/account-utils/issues Version 1.0.1 * drop_privs: don't run the check as root * Add pwaccessd and pwupdd manual pages Version 1.0.0 * Many bug fixes and other improvements Version 0.4.0 * Big update, first more or less future complete release Version 0.2.0 * Solve issues fround during security review account-utils-1.0.1/README.md000066400000000000000000000041061513410760700155670ustar00rootroot00000000000000# account-utils The account-utils package contains the utilities and services to do user management and authentication without the need for setuid/setgid bits. This allows the stack to work with `NoNewPrivs` enabled (means setuid/setgid binaries will no longer work). Communication happens via [varlink](https://varlink.org). There are two services: * `pwaccessd` is a systemd socket activated service which provides account information in `passwd` and `shadow` format, checks if the password or account is expired and verifies the password. Normal users have only access to their own passwd and shadow entry, root has access to all accounts. * `pwupdd` is a inetd style socket activated service, which means for every request a own instance is started. It provides methods to change the password, shell and the GECOS field. An user is allowed to modify it's own data after authentication via PAM, root can additional update all passwd and shadow entries via an own method. There are PAM modules: * `pam_unix_ng.so` is a UNIX style PAM module like `pam_unix.so`, except that it uses `pwaccessd` to get access to the account information and do the authentication. If `pwaccessd` is not running, it falls back to traditional, local authentication. For this it needs to run as root. Changing the password is always done local, but there is a `passwd` command which uses `pwupdd` with a PAM stack for this. * `pam_debuginfo.so` is a simple PAM module for debugging purpose, it prints all available relevant information like PAM flags, PAM data, euid, uid, no_new_privs state, etc. There are additional utilities, which don't use the standard glibc functions to modify passwd and shadow, but `pwaccessd` and `pwupdd`: * chage * chfn * chsh * expiry * passwd ## pam_unix_ng.so The `pam_unix_ng.so` PAM module uses `pwaccessd` as backend for authentication and to check if the account is expired. Changing the password is only possible if run as root, no varlink call for this. Use `passwd` from this package instead. If `pwaccessd` is not running, it tries authentication and account expiration itself as fallback. account-utils-1.0.1/TODO000066400000000000000000000002631513410760700150000ustar00rootroot00000000000000- pam_unix_ng: handle PAM_SILENT correct - pwupd/pwaccess varlink definition: add OUTPUT fields - passwd: --stdin option Bugs: - "passwd -e " reports "password changed" account-utils-1.0.1/example/000077500000000000000000000000001513410760700157425ustar00rootroot00000000000000account-utils-1.0.1/example/check_expired.c000066400000000000000000000015211513410760700207020ustar00rootroot00000000000000//SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include "pwaccess.h" #include "basics.h" int main(int argc, char **argv) { _cleanup_free_ char *error = NULL; bool pwchangeable = false; long daysleft = -1; int r; if (argc != 2) { fprintf(stderr, "Usage: check_expired \n"); return 1; } r = pwaccess_check_expired(argv[1], &daysleft, &pwchangeable, &error); if (r < 0) { if (error) fprintf(stderr, "%s\n", error); else fprintf(stderr, "check_expired failed: %s\n", strerror(-r)); return 1; } printf("Expired: %i\n", r); printf("Days left: %li\n", daysleft); if (pwchangeable) fprintf(stdout, "Password can be changed.\n"); else fprintf(stdout, "Password cannot be changed.\n"); return 0; } account-utils-1.0.1/example/get_account_name.c000066400000000000000000000012571513410760700214060ustar00rootroot00000000000000//SPDX-License-Identifier: GPL-2.0-or-later #include "pwaccess.h" #include "basics.h" int main(int argc, char **argv) { _cleanup_free_ char *error = NULL; _cleanup_free_ char *name = NULL; uid_t uid; int r; if (argc == 1) uid = getuid(); else if (argc == 2) uid = atol(argv[1]); else { fprintf(stderr, "Usage: get_account_name \n"); return 1; } r = pwaccess_get_account_name(uid, &name, &error); if (r < 0) { if (error) fprintf(stderr, "%s\n", error); else fprintf(stderr, "check_expired failed: %s\n", strerror(-r)); return 1; } printf("Your account name: %s\n", name); return 0; } account-utils-1.0.1/example/get_user_record.c000066400000000000000000000031101513410760700212540ustar00rootroot00000000000000//SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include "pwaccess.h" #include "basics.h" int main(int argc, char **argv) { _cleanup_free_ char *error = NULL; _cleanup_(struct_passwd_freep) struct passwd *pw = NULL; _cleanup_(struct_shadow_freep) struct spwd *sp = NULL; bool complete = false; int r; if (argc >= 2) r = pwaccess_get_user_record(-1, argv[1], &pw, &sp, &complete, &error); else r = pwaccess_get_user_record(getuid(), NULL, &pw, &sp, &complete, &error); if (r < 0) { if (error) fprintf (stderr, "%s\n", error); else fprintf (stderr, "get_user_record failed: %s\n", strerror(-r)); return 1; } if (pw == NULL) { fprintf(stderr, "ERROR: no password entry found!\n"); return 1; } printf("Name: %s\n", pw->pw_name); printf("Password: %s\n", strna(sp?sp->sp_pwdp:pw->pw_passwd)); printf("UID: %i\n", pw->pw_uid); printf("GID: %i\n", pw->pw_gid); printf("GECOS: %s\n", strna(pw->pw_gecos)); printf("Dir: %s\n", strna(pw->pw_dir)); printf("Shell: %s\n", strna(pw->pw_shell)); if (sp) { printf("LstChg: %li\n", sp->sp_lstchg); printf("Min: %li\n", sp->sp_min); printf("Max: %li\n", sp->sp_max); printf("Warn: %li\n", sp->sp_warn); printf("Inact: %li\n", sp->sp_inact); printf("Expire: %li\n", sp->sp_expire); printf("Flag: %li\n", sp->sp_flag); } if (!complete) printf("For permission reasons the result is incomplete.\n"); return 0; } account-utils-1.0.1/example/meson.build000066400000000000000000000012451513410760700201060ustar00rootroot00000000000000check_expired = executable ('check_expired', 'check_expired.c', include_directories : inc, link_with : libpwaccess) get_account_name = executable ('get_account_name', 'get_account_name.c', include_directories : inc, link_with : libpwaccess) get_user_record = executable ('get_user_record', 'get_user_record.c', include_directories : inc, link_with : libpwaccess) verify_password = executable ('verify_password', 'verify_password.c', include_directories : inc, link_with : libpwaccess) account-utils-1.0.1/example/verify_password.c000066400000000000000000000014641513410760700213410ustar00rootroot00000000000000//SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include "pwaccess.h" #include "basics.h" int main(int argc, char **argv) { _cleanup_free_ char *error = NULL; bool authenticated = false; bool nullok = true; int r; if (argc < 2) { fprintf(stderr, "Usage: verify_password [password]\n"); return 1; } r = pwaccess_verify_password(argv[1], argv[2], nullok, &authenticated, &error); if (r < 0) { if (error) fprintf(stderr, "%s\n", error); else fprintf(stderr, "verify_password failed: %s\n", strerror(-r)); return 1; } if (authenticated) fprintf(stdout, "Access granted.\n"); else { fprintf(stderr, "Access denied!\n"); return 1; } return 0; } account-utils-1.0.1/include/000077500000000000000000000000001513410760700157325ustar00rootroot00000000000000account-utils-1.0.1/include/basics.h000066400000000000000000000047651513410760700173630ustar00rootroot00000000000000//SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include #include #include #include #include #define _unused_ __attribute__((unused)) #define _pure_ __attribute__((__pure__)) #define _const_ __attribute__((__const__)) #define _sentinel_ __attribute__((__sentinel__)) /* Takes inspiration from Rust's Option::take() method: reads and returns a pointer, but at the same time * resets it to NULL. See: https://doc.rust-lang.org/std/option/enum.Option.html#method.take */ #define TAKE_GENERIC(var, type, nullvalue) \ ({ \ type *_pvar_ = &(var); \ type _var_ = *_pvar_; \ type _nullvalue_ = nullvalue; \ *_pvar_ = _nullvalue_; \ _var_; \ }) #define TAKE_PTR_TYPE(ptr, type) TAKE_GENERIC(ptr, type, NULL) #define TAKE_PTR(ptr) TAKE_PTR_TYPE(ptr, typeof(ptr)) #define TAKE_FD(fd) TAKE_GENERIC(fd, int, -EBADF) #define mfree(memory) \ ({ \ free(memory); \ (typeof(memory)) NULL; \ }) static inline void freep(void *p) { *(void**)p = mfree(*(void**) p); } static inline void closep(int *fd) { if (*fd) close(*fd); } static inline void fclosep(FILE **f) { if (*f) fclose(*f); } #define _cleanup_(x) __attribute__((__cleanup__(x))) #define _cleanup_close_ _cleanup_(closep) #define _cleanup_fclose_ _cleanup_(fclosep) #define _cleanup_free_ _cleanup_(freep) /* from string-util-fundamental.h */ #define WHITESPACE " \t\n\r" #define streq(a,b) (strcmp((a),(b)) == 0) #define strneq(a, b, n) (strncmp((a), (b), (n)) == 0) #define strcaseeq(a,b) (strcasecmp((a),(b)) == 0) #define strncaseeq(a, b, n) (strncasecmp((a), (b), (n)) == 0) static inline const char *strempty(const char *s) { return s ?:""; } static inline const char *strna(const char *s) { return s ?: "n/a"; } static inline const char *stroom(const char *s) { return s ?: "Out of memory"; } extern char *startswith(const char *s, const char *prefix) _pure_; extern char *endswith(const char *s, const char *suffix) _pure_; static inline bool isempty(const char *a) { return !a || a[0] == '\0'; } account-utils-1.0.1/include/meson.build000066400000000000000000000000701513410760700200710ustar00rootroot00000000000000configure_file(output: 'config.h', configuration: conf) account-utils-1.0.1/include/pwaccess.h000066400000000000000000000031121513410760700177100ustar00rootroot00000000000000//SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include #include #include #include #define PWACCESS_IS_NOT_RUNNING(r) (r == -ECONNREFUSED || r == -ENOENT || r == -ECONNRESET || r == -EACCES) typedef enum { PWA_EXPIRED_NO = 0, /* account is valid */ #define PWA_EXPIRED_NO PWA_EXPIRED_NO PWA_EXPIRED_ACCT = 1, /* account is expired */ #define PWA_EXPIRED_ACCT PWA_EXPIRED_ACCT PWA_EXPIRED_CHANGE_PW = 2, /* password is expired, change password */ #define PWA_EXPIRED_CHANGE_PW PWA_EXPIRED_CHANGE_PW PWA_EXPIRED_PW = 3, /* password is expired, password change not possible */ #define PWA_EXPIRED_PW PWA_EXPIRED_PW } pwa_expire_flag_t; extern struct passwd *struct_passwd_free(struct passwd *var); extern void struct_passwd_freep(struct passwd **var); extern struct spwd *struct_shadow_free(struct spwd *var); extern void struct_shadow_freep(struct spwd **var); /* All returning structs and strings, if not "const", need to be free'd after usage. This are especially name, pw, sp and error. */ extern int pwaccess_check_expired(const char *user, long *daysleft, bool *pwchangeable, char **error); extern int pwaccess_get_account_name(int64_t uid, char **name, char **error); extern int pwaccess_get_user_record(int64_t uid, const char *user, struct passwd **pw, struct spwd **sp, bool *complete, char **error); extern int pwaccess_verify_password(const char *user, const char *password, bool nullok, bool *ret_authenticated, char **error); account-utils-1.0.1/lib/000077500000000000000000000000001513410760700150555ustar00rootroot00000000000000account-utils-1.0.1/lib/libpwaccess.map000066400000000000000000000003641513410760700200560ustar00rootroot00000000000000LIBPWACCESS_0.4 { global: pwaccess_check_expired; pwaccess_get_account_name; pwaccess_get_user_record; pwaccess_verify_password; struct_passwd_freep; struct_passwd_free; struct_shadow_freep; struct_shadow_free; local: *; }; account-utils-1.0.1/lib/varlink.c000066400000000000000000000422321513410760700166720ustar00rootroot00000000000000//SPDX-License-Identifier: LGPL-2.1-or-later #include "config.h" #include #include #include #include "basics.h" #include "pwaccess.h" static int connect_to_pwaccessd(sd_varlink **ret, const char *socket, char **error) { _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; int r; r = sd_varlink_connect_address(&link, socket); if (r < 0) { if (error) if (asprintf (error, "Failed to connect to %s: %s", socket, strerror(-r)) < 0) { error = NULL; r = -ENOMEM; } return r; } /* Mark anything we get from the service as sensitive */ r = sd_varlink_set_input_sensitive(link); if (r < 0) { if (error) if (asprintf (error, "Failed to enable sensitive Varlink input: %s", strerror(-r)) < 0) { error = NULL; r = -ENOMEM; } return r; } *ret = TAKE_PTR(link); return 0; } struct user_record { bool success; char *error; bool complete; bool pwchangeable; int expired; long daysleft; char *account_name; sd_json_variant *content_passwd; sd_json_variant *content_shadow; }; struct passwd * struct_passwd_free(struct passwd *var) { var->pw_name = mfree(var->pw_name); if (var->pw_passwd) { explicit_bzero(var->pw_passwd, strlen(var->pw_passwd)); var->pw_passwd = mfree(var->pw_passwd); } var->pw_gecos = mfree(var->pw_gecos); var->pw_dir = mfree(var->pw_dir); var->pw_shell = mfree(var->pw_shell); return NULL; } void struct_passwd_freep(struct passwd **var) { if (!var || !*var) return; struct_passwd_free(*var); *var = mfree(*var); } struct spwd * struct_shadow_free(struct spwd *var) { var->sp_namp = mfree(var->sp_namp); if (var->sp_pwdp) { explicit_bzero(var->sp_pwdp, strlen(var->sp_pwdp)); var->sp_pwdp = mfree(var->sp_pwdp); } return NULL; } void struct_shadow_freep(struct spwd **var) { if (!var || !*var) return; struct_shadow_free(*var); *var = mfree(*var); } static void user_record_free(struct user_record *var) { var->error = mfree(var->error); var->account_name = mfree(var->account_name); var->content_passwd = sd_json_variant_unref(var->content_passwd); var->content_shadow = sd_json_variant_unref(var->content_shadow); } /* long is different on 32bit and 64bit architectures, but we don't have a sd_json_dispatch_long function */ struct spwd64 { char *sp_namp; char *sp_pwdp; int64_t sp_lstchg; int64_t sp_min; int64_t sp_max; int64_t sp_warn; int64_t sp_inact; int64_t sp_expire; uint64_t sp_flag; }; static inline int assign_check_range(long *dest, int64_t src) { if (src > LONG_MAX) return -EOVERFLOW; *dest = (long)src; return 0; } int pwaccess_get_user_record(int64_t uid, const char *user, struct passwd **ret_pw, struct spwd **ret_sp, bool *complete, char **error) { _cleanup_(user_record_free) struct user_record p = { .success = false, .error = NULL, .account_name = NULL, .complete = false, .content_passwd = NULL, .content_shadow = NULL, }; static const sd_json_dispatch_field dispatch_table[] = { { "Success", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct user_record, success), 0 }, { "ErrorMsg", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct user_record, error), 0 }, { "Complete", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct user_record, complete), 0 }, { "passwd", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant, offsetof(struct user_record, content_passwd), SD_JSON_NULLABLE }, { "shadow", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant, offsetof(struct user_record, content_shadow), SD_JSON_NULLABLE }, {} }; static const sd_json_dispatch_field dispatch_passwd_table[] = { { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct passwd, pw_name), SD_JSON_MANDATORY }, { "passwd", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct passwd, pw_passwd), SD_JSON_NULLABLE }, { "UID", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int, offsetof(struct passwd, pw_uid), SD_JSON_MANDATORY }, { "GID", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int, offsetof(struct passwd, pw_gid), SD_JSON_MANDATORY }, { "GECOS", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct passwd, pw_gecos), SD_JSON_NULLABLE }, { "dir", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct passwd, pw_dir), SD_JSON_NULLABLE }, { "shell", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct passwd, pw_shell), SD_JSON_NULLABLE }, {} }; _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; _cleanup_(struct_passwd_freep) struct passwd *pw = NULL; _cleanup_(struct_shadow_freep) struct spwd *sp = NULL; sd_json_variant *result = NULL; const char *error_id = NULL; int r; r = connect_to_pwaccessd(&link, _VARLINK_PWACCESS_SOCKET, error); if (r < 0) return r; if (uid < 0 && user == NULL) { fprintf(stderr, "Invalid combination of UID and user name provided (-1 and NULL)\n"); return -EINVAL; } if (uid >= 0) r = sd_json_variant_merge_objectbo(¶ms, SD_JSON_BUILD_PAIR_INTEGER("uid", uid)); if (r >= 0 && user) r = sd_json_variant_merge_objectbo(¶ms, SD_JSON_BUILD_PAIR_STRING("userName", user)); if (r < 0) { fprintf(stderr, "Failed to build param list: %s\n", strerror(-r)); return r; } r = sd_varlink_call(link, "org.openSUSE.pwaccess.GetUserRecord", params, &result, &error_id); if (r < 0) { fprintf(stderr, "Failed to call GetUserRecord method: %s\n", strerror(-r)); return r; } /* dispatch before checking error_id, we may need the result for the error message */ r = sd_json_dispatch(result, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); if (r < 0) { fprintf(stderr, "Failed to parse JSON answer: %s\n", strerror(-r)); return r; } if (error_id && strlen(error_id) > 0) { int retval = -EIO; if (error) { if (p.error) *error = TAKE_PTR(p.error); else { *error = strdup(error_id); if (*error == NULL) retval = -ENOMEM; } } /* Yes, we will overwrite a possible ENOMEM, but this shouldn't matter here */ if (streq(error_id, "org.openSUSE.pwaccess.NoEntryFound")) retval = -ENODATA; return retval; } if (!p.success) /* we should never have this case, but be safe */ { if (error) *error = TAKE_PTR(p.error); return -EIO; } if (sd_json_variant_is_null(p.content_passwd)) { printf("No entry found\n"); return 0; } pw = calloc(1, sizeof(struct passwd)); if (pw == NULL) return -ENOMEM; r = sd_json_dispatch(p.content_passwd, dispatch_passwd_table, SD_JSON_ALLOW_EXTENSIONS, pw); if (r < 0) { fprintf(stderr, "Failed to parse JSON passwd entry: %s\n", strerror(-r)); return r; } if (!sd_json_variant_is_null(p.content_shadow) && sd_json_variant_elements(p.content_shadow) > 0) { struct spwd64 sp64 = {NULL, NULL, 0, 0, 0, 0, 0, 0, 0}; static const sd_json_dispatch_field dispatch_shadow_table[] = { { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct spwd64, sp_namp), SD_JSON_MANDATORY }, { "passwd", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct spwd64, sp_pwdp), SD_JSON_NULLABLE }, { "lstchg", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct spwd64, sp_lstchg), 0 }, { "min", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct spwd64, sp_min), 0 }, { "max", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct spwd64, sp_max), 0 }, { "warn", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct spwd64, sp_warn), 0 }, { "inact", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct spwd64, sp_inact), 0 }, { "expire", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct spwd64, sp_expire), 0 }, { "flag", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct spwd64, sp_flag), 0 }, {} }; r = sd_json_dispatch(p.content_shadow, dispatch_shadow_table, SD_JSON_ALLOW_EXTENSIONS, &sp64); if (r < 0) { fprintf(stderr, "Failed to parse JSON shadow entry: %s\n", strerror(-r)); return r; } sp = calloc(1, sizeof(struct spwd)); if (sp == NULL) return -ENOMEM; sp->sp_namp = sp64.sp_namp; sp->sp_pwdp = sp64.sp_pwdp; r = assign_check_range(&sp->sp_lstchg, sp64.sp_lstchg); if (r < 0) return r; r = assign_check_range(&sp->sp_min, sp64.sp_min); if (r < 0) return r; r = assign_check_range(&sp->sp_max, sp64.sp_max); if (r < 0) return r; r = assign_check_range(&sp->sp_warn, sp64.sp_warn); if (r < 0) return r; r = assign_check_range(&sp->sp_inact, sp64.sp_inact); if (r < 0) return r; r = assign_check_range(&sp->sp_expire, sp64.sp_expire); if (r < 0) return r; /* sp_flag is ~0ul if unset, but sd-json/sd-varlink convert this always to -1 */ if (sp64.sp_flag == (uint64_t)-1) sp->sp_flag = ~0ul; else sp->sp_flag = sp64.sp_flag; } if (complete) *complete = p.complete; if (ret_pw) *ret_pw = TAKE_PTR(pw); if (ret_sp) *ret_sp = TAKE_PTR(sp); return 0; } int pwaccess_verify_password(const char *user, const char *password, bool nullok, bool *ret_authenticated, char **error) { _cleanup_(user_record_free) struct user_record p = { .success = false, .error = NULL, .account_name = NULL, .content_passwd = NULL, .content_shadow = NULL, }; static const sd_json_dispatch_field dispatch_table[] = { { "Success", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct user_record, success), 0 }, { "ErrorMsg", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct user_record, error), 0 }, {} }; _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; sd_json_variant *result = NULL; const char *error_id = NULL; int r; /* make sure caller does not ignore error code but uses this instead */ if (ret_authenticated) *ret_authenticated = false; if (!user || !ret_authenticated) return -EINVAL; r = connect_to_pwaccessd(&link, _VARLINK_PWACCESS_SOCKET, error); if (r < 0) return r; r = sd_json_buildo(¶ms, SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(user)), SD_JSON_BUILD_PAIR("password", SD_JSON_BUILD_STRING(strempty(password))), SD_JSON_BUILD_PAIR("nullOK", SD_JSON_BUILD_BOOLEAN(nullok))); if (r < 0) { fprintf(stderr, "Failed to build param list: %s\n", strerror(-r)); return r; } sd_json_variant_sensitive(params); /* password is sensitive */ r = sd_varlink_call(link, "org.openSUSE.pwaccess.VerifyPassword", params, &result, &error_id); if (r < 0) { fprintf(stderr, "Failed to call VerifyPassword method: %s\n", strerror(-r)); return r; } /* dispatch before checking error_id, we may need the result for the error message */ r = sd_json_dispatch(result, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); if (r < 0) { fprintf(stderr, "Failed to parse JSON answer: %s\n", strerror(-r)); return r; } if (error_id && strlen(error_id) > 0) { int retval = -EIO; if (error) { if (p.error) *error = TAKE_PTR(p.error); else { *error = strdup(error_id); if (*error == NULL) retval = -ENOMEM; } } /* Yes, we will overwrite a possible ENOMEM, but this shouldn't matter here */ if (streq(error_id, "org.openSUSE.pwaccess.NoEntryFound")) retval = -ENODATA; return retval; } if (!p.success) /* no success and no error means password does not match */ { if (error) *error = TAKE_PTR(p.error); return 0; } *ret_authenticated = true; return 0; } /* return values: < 0: error occured 0: all fine > 0: PWA_EXPIRED_* */ int pwaccess_check_expired(const char *user, long *daysleft, bool *pwchangeable, char **error) { _cleanup_(user_record_free) struct user_record p = { .success = false, .error = NULL, .account_name = NULL, .expired = 0, .daysleft = -1, .pwchangeable = true, /* if we don't get a no, no shadow information exist and thus it's changeable */ .content_passwd = NULL, .content_shadow = NULL, }; static const sd_json_dispatch_field dispatch_table[] = { { "Success", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct user_record, success), 0 }, { "ErrorMsg", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct user_record, error), 0 }, { "Expired", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int, offsetof(struct user_record, expired), 0 }, { "DaysLeft", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct user_record, daysleft), 0 }, { "PWChangeAble", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct user_record, pwchangeable), 0 }, {} }; _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; sd_json_variant *result = NULL; const char *error_id = NULL; int r; if (!user) return -EINVAL; r = connect_to_pwaccessd(&link, _VARLINK_PWACCESS_SOCKET, error); if (r < 0) return r; r = sd_json_buildo(¶ms, SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(user))); if (r < 0) { fprintf(stderr, "Failed to build param list: %s\n", strerror(-r)); return r; } r = sd_varlink_call(link, "org.openSUSE.pwaccess.ExpiredCheck", params, &result, &error_id); if (r < 0) { fprintf(stderr, "Failed to call ExpiredCheck method: %s\n", strerror(-r)); return r; } /* dispatch before checking error_id, we may need the result for the error message */ r = sd_json_dispatch(result, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); if (r < 0) { fprintf(stderr, "Failed to parse JSON answer: %s\n", strerror(-r)); return r; } if (error_id && strlen(error_id) > 0) { int retval = -EIO; if (error) { if (p.error) *error = TAKE_PTR(p.error); else { *error = strdup(error_id); if (*error == NULL) retval = -ENOMEM; } } /* Yes, we will overwrite a possible ENOMEM, but this shouldn't matter here */ if (streq(error_id, "org.openSUSE.pwaccess.NoEntryFound")) retval = -ENODATA; return retval; } if (!p.success) { if (error) *error = TAKE_PTR(p.error); return -EPROTO; } if (daysleft && p.daysleft >= 0) *daysleft = p.daysleft; if (pwchangeable) *pwchangeable = p.pwchangeable; return p.expired; } int pwaccess_get_account_name(int64_t uid, char **name, char **error) { _cleanup_(user_record_free) struct user_record p = { .success = false, .error = NULL, .account_name = NULL, .content_passwd = NULL, .content_shadow = NULL, }; static const sd_json_dispatch_field dispatch_table[] = { { "Success", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct user_record, success), 0 }, { "ErrorMsg", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct user_record, error), 0 }, { "userName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct user_record, account_name), 0 }, {} }; _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; sd_json_variant *result = NULL; const char *error_id = NULL; int r; if (name) *name = NULL; if (uid < 0) return -EINVAL; r = connect_to_pwaccessd(&link, _VARLINK_PWACCESS_SOCKET, error); if (r < 0) return r; r = sd_json_buildo(¶ms, SD_JSON_BUILD_PAIR("uid", SD_JSON_BUILD_INTEGER(uid))); if (r < 0) { fprintf(stderr, "Failed to build param list: %s\n", strerror(-r)); return r; } r = sd_varlink_call(link, "org.openSUSE.pwaccess.GetAccountName", params, &result, &error_id); if (r < 0) { fprintf(stderr, "Failed to call GetAccountName method: %s\n", strerror(-r)); return r; } /* dispatch before checking error_id, we may need the result for the error message */ r = sd_json_dispatch(result, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); if (r < 0) { fprintf(stderr, "Failed to parse JSON answer: %s\n", strerror(-r)); return r; } if (error_id && strlen(error_id) > 0) { int retval = -EIO; if (error) { if (p.error) *error = TAKE_PTR(p.error); else { *error = strdup(error_id); if (*error == NULL) retval = -ENOMEM; } } /* Yes, we will overwrite a possible ENOMEM, but this shouldn't matter here */ if (streq(error_id, "org.openSUSE.pwaccess.NoEntryFound")) retval = -ENODATA; return retval; } if (!p.success) { if (error) *error = TAKE_PTR(p.error); return 0; } if (name) *name = TAKE_PTR(p.account_name); return 0; } account-utils-1.0.1/libclient/000077500000000000000000000000001513410760700162545ustar00rootroot00000000000000account-utils-1.0.1/libclient/chauthtok.c000066400000000000000000000054001513410760700204110ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include "config.h" #include #include #include "chauthtok.h" #include "basics.h" #include "varlink-client-common.h" #define USEC_INFINITY ((uint64_t) UINT64_MAX) int chauthtok(const char *user, int pam_flags) { _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; _cleanup_free_ char *error = NULL; struct pam_response *resp = NULL; int r; r = connect_to_pwupdd(&link, _VARLINK_PWUPD_SOCKET, &error); if (r < 0) { if (error) fprintf(stderr, "%s\n", error); else fprintf(stderr, "Cannot connect to pwupd! (%s)\n", strerror(-r)); return -r; } sd_varlink_set_userdata(link, &resp); r = sd_json_variant_merge_objectbo(¶ms, SD_JSON_BUILD_PAIR_STRING("userName", user)); if (r < 0) { fprintf(stderr, "Failed to build param list: %s\n", strerror(-r)); return -r; } if (pam_flags) { r = sd_json_variant_merge_objectbo(¶ms, SD_JSON_BUILD_PAIR_INTEGER("flags", pam_flags)); if (r < 0) { fprintf(stderr, "Failed to build param list: %s\n", strerror(-r)); return -r; } } r = sd_varlink_bind_reply(link, reply_callback); if (r < 0) { fprintf(stderr, "Failed to bind reply callback: %s\n", strerror(-r)); return -r; } r = sd_varlink_observe(link, "org.openSUSE.pwupd.Chauthtok", params); if (r < 0) { fprintf(stderr, "Failed to call chauthtok method: %s\n", strerror(-r)); return -r; } loop: for (;;) { r = sd_varlink_is_idle(link); if (r < 0) { fprintf(stderr, "Failed to check if varlink connection is idle: %s\n", strerror(-r)); return -r; } if (r > 0) break; r = sd_varlink_process(link); if (r < 0) { fprintf(stderr, "Failed to process varlink connection: %s\n", strerror(-r)); return -r; } if (r != 0) continue; r = sd_varlink_wait(link, USEC_INFINITY); if (r < 0) { fprintf(stderr, "Failed to wait for varlink connection events: %s\n", strerror(-r)); return -r; } } if (resp) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *answer = NULL; r = sd_json_buildo(&answer, SD_JSON_BUILD_PAIR("response", SD_JSON_BUILD_STRING(resp->resp))); if (r < 0) { fprintf(stderr, "Failed to build response list: %s\n", strerror(-r)); return -r; } free(resp->resp); resp = mfree(resp); sd_json_variant_sensitive(answer); /* password is sensitive */ r = sd_varlink_observe(link, "org.openSUSE.pwupd.Conv", answer); if (r < 0) { fprintf(stderr, "Failed to call conv method: %s\n", strerror(-r)); return -r; } goto loop; } return 0; } account-utils-1.0.1/libclient/chauthtok.h000066400000000000000000000001641513410760700204200ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once extern int chauthtok(const char *user, int pam_flags); account-utils-1.0.1/libclient/drop_privs.c000066400000000000000000000015351513410760700206130ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include #include "drop_privs.h" int drop_privs(void) { /* drop gid */ if (setgid(getgid()) != 0) return -errno; /* drop uid */ if (setuid(getuid()) != 0) return -errno; /* Try to regain root. If this succeeds, we failed to drop privileges. Don't do this as root, else the check will fail. */ if (getuid() != 0 && setuid(0) != -1) return -EPERM; return 0; } int check_and_drop_privs(void) { int r; if (geteuid() == getuid() && getegid() == getgid()) return 0; fprintf(stderr, "Binary has the setuid or setgid bit set, please remove it.\n"); r = drop_privs(); if (r < 0) { fprintf(stderr, "Dropping privileges failed: %s\n", strerror(-r)); return r; } return 0; } account-utils-1.0.1/libclient/drop_privs.h000066400000000000000000000002011513410760700206050ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #pragma once extern int drop_privs(void); extern int check_and_drop_privs(void); account-utils-1.0.1/libclient/get_logindefs.c000066400000000000000000000033131513410760700212310ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later #include "config.h" #include #include "basics.h" #include "get_logindefs.h" static econf_err load_logindefs_config(econf_file **key_file) { return econf_readConfig(key_file, NULL /* project */, _PATH_VENDORDIR /* usr_conf_dir */, "login" /* config_name */, "defs" /* config_suffix */, "= \t" /* delim */, "#" /* comment */); } long get_logindefs_num(const char *key, long def) { _cleanup_(econf_freeFilep) econf_file *key_file = NULL; int32_t val; econf_err error; error = load_logindefs_config(&key_file); if (error != ECONF_SUCCESS) { fprintf(stderr, "Cannot parse login.defs: %s\n", econf_errString(error)); return def; } error = econf_getIntValueDef(key_file, NULL, key, &val, def); if (error != ECONF_SUCCESS) { if (error != ECONF_NOKEY) fprintf(stderr, "Error reading '%s': %s\n", key, econf_errString(error)); return def; } return val; } char * get_logindefs_string(const char *key, const char *def) { _cleanup_(econf_freeFilep) econf_file *key_file = NULL; char *val; econf_err error; error = load_logindefs_config(&key_file); if (error != ECONF_SUCCESS) { fprintf(stderr, "Cannot parse login.defs: %s\n", econf_errString(error)); return strdup(def); } error = econf_getStringValueDef(key_file, NULL, key, &val, def); if (error != ECONF_SUCCESS) { if (error != ECONF_NOKEY) fprintf(stderr, "Error reading '%s': %s\n", key, econf_errString(error)); return strdup(def); } return val; } account-utils-1.0.1/libclient/get_logindefs.h000066400000000000000000000002741513410760700212410ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once extern long get_logindefs_num(const char *key, long def); extern char *get_logindefs_string(const char *key, const char *def); account-utils-1.0.1/libclient/get_value.c000066400000000000000000000033411513410760700203740ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include "config.h" #include #include #include #include #include "basics.h" #include "get_value.h" /* prompt the user with the name of the field being changed and the current value. return value: 0 -> success < 0 -> error, -errno as code input argument: NULL -> Ctrl-D was pressed "" -> Field was cleard by user. User can enter a space or "none" to do this. def -> User entered only . input -> User entered something new . */ int get_value(const char *def, const char *prompt, char **input) { char buf[BUFSIZ]; char *cp; *input = NULL; printf("\t%s [%s]: ", prompt, strempty(def)); if (fgets(buf, sizeof(buf), stdin) != buf) { /* print newline to get defined output. */ printf("\n"); return 0; } if ((cp = strchr(buf, '\n')) != NULL) *cp = '\0'; if (buf[0]) /* something is entered */ { /* if none is entered, return an empty string. If somebody wishes to enter "none", he as to add a space. */ if(strcasecmp("none", buf) == 0) { *input = strdup(""); if (*input == NULL) return -ENOMEM; return 0; } /* Remove leading and trailing whitespace. This also makes it possible to change the field to empty or "none" by entering a space. */ /* cp should point to the trailing '\0'. */ cp = &buf[strlen(buf)]; while(--cp >= buf && isspace(*cp)) ; *++cp = '\0'; cp = buf; while (*cp && isspace(*cp)) cp++; *input = strdup(cp); if (*input == NULL) return -ENOMEM; return 0; } *input = strdup(strempty(def)); if (*input == NULL) return -ENOMEM; return 0; } account-utils-1.0.1/libclient/get_value.h000066400000000000000000000002021513410760700203720ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause #pragma once extern int get_value(const char *def, const char *prompt, char **input); account-utils-1.0.1/libclient/varlink-client-common.c000066400000000000000000000106351513410760700226350ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later #include "config.h" #include #include #include #include "basics.h" #include "varlink-client-common.h" struct pam_conv conv = { misc_conv, NULL }; struct result * struct_result_free(struct result *var) { var->error = mfree((char *)var->error); return NULL; } int connect_to_pwupdd(sd_varlink **ret, const char *socket, char **error) { _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; int r; r = sd_varlink_connect_address(&link, socket); if (r < 0) { if (error) if (asprintf (error, "Failed to connect to %s: %s", socket, strerror(-r)) < 0) { error = NULL; r = -ENOMEM; } return r; } /* Mark anything we get from the service as sensitive */ r = sd_varlink_set_input_sensitive(link); if (r < 0) { if (error) if (asprintf (error, "Failed to enable sensitive Varlink input: %s", strerror(-r)) < 0) { error = NULL; r = -ENOMEM; } return r; } *ret = TAKE_PTR(link); return 0; } static struct pam_message * pam_message_free(struct pam_message *var) { var->msg = mfree((char *)var->msg); return NULL; } int reply_callback(sd_varlink *link _unused_, sd_json_variant *parameters, const char *error, sd_varlink_reply_flags_t flags _unused_, void *userdata) { struct pam_response **resp = userdata; _cleanup_(pam_message_free) struct pam_message pmsg = { .msg_style = -1, .msg = NULL }; static const sd_json_dispatch_field dispatch_pmsg_table[] = { { "msg_style", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int, offsetof(struct pam_message, msg_style), SD_JSON_MANDATORY }, { "message", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct pam_message, msg), SD_JSON_NULLABLE }, {} }; _cleanup_(struct_result_free) struct result p = { .success = false, .error = NULL, }; static const sd_json_dispatch_field dispatch_result_table[] = { { "Success", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct result, success), SD_JSON_MANDATORY }, { "ErrorMsg", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct result, error), 0 }, {} }; int r; assert(*resp == NULL); if (error) { r = sd_json_dispatch(parameters, dispatch_result_table, SD_JSON_ALLOW_EXTENSIONS, &p); if (r < 0) { /* Mandatory field not found, so no pam_message but final end message */ fprintf(stderr, "Failed to parse JSON answer (result) for error '%s': %s\n", error, strerror(-r)); return r; } if (p.success || !p.error) /* Oops, something did go wrong */ fprintf(stderr, "Method call failed: %s\n", error); else fprintf(stderr, "%s\n", p.error); /* If we can translate this to an errno, let's print that as errno and return it, otherwise, return a generic error code. */ r = sd_varlink_error_to_errno(error, parameters); return r; } //sd_json_variant_dump(parameters, SD_JSON_FORMAT_NEWLINE, stdout, NULL); r = sd_json_dispatch(parameters, dispatch_pmsg_table, SD_JSON_ALLOW_EXTENSIONS, &pmsg); if (r < 0) { /* Mandatory field not found, so no pam_message but final end message */ if (r != -ENXIO) { fprintf(stderr, "Failed to parse JSON answer (pam_message): %s\n", strerror(-r)); return r; } r = sd_json_dispatch(parameters, dispatch_result_table, SD_JSON_ALLOW_EXTENSIONS, &p); if (r < 0) { /* Mandatory field not found, so no pam_message but final end message */ fprintf(stderr, "Failed to parse JSON answer (result): %s\n", strerror(-r)); return r; } /* This should never happen */ if (!p.success) { if (p.error) fprintf(stderr, "%s\n", p.error); else fprintf(stderr, "Error while changing account data.\n"); return -EBADMSG; } } else /* got pam_message */ { const struct pam_message *arg = &pmsg; r = conv.conv(1, &arg, resp, conv.appdata_ptr); if (r != PAM_SUCCESS) { fprintf(stderr, "misc_conv() failed: %s\n", pam_strerror(NULL, r)); return -EBADMSG; } if (*resp && pmsg.msg_style != PAM_PROMPT_ECHO_OFF && pmsg.msg_style != PAM_PROMPT_ECHO_ON) { if ((*resp)->resp) free((*resp)->resp); *resp = mfree(*resp); } } return 0; } account-utils-1.0.1/libclient/varlink-client-common.h000066400000000000000000000007221513410760700226360ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include extern struct pam_conv conv; struct result { bool success; char *error; }; extern struct result *struct_result_free(struct result *var); extern int connect_to_pwupdd(sd_varlink **ret, const char *socket, char **error); extern int reply_callback(sd_varlink *link, sd_json_variant *parameters, const char *error, sd_varlink_reply_flags_t flags, void *userdata); account-utils-1.0.1/libcommon/000077500000000000000000000000001513410760700162665ustar00rootroot00000000000000account-utils-1.0.1/libcommon/check_caller_perms.c000066400000000000000000000013701513410760700222400ustar00rootroot00000000000000//SPDX-License-Identifier: LGPL-2.1-or-later #include "config.h" #include #include "check_caller_perms.h" /* Do not allow access if the query does not originate from root or the entry does not belong to the calling user. Exception: if the peer uid is in the list of exceptions. "Lex mariadb": user mysql/mariadb needs to authenticate as database user so that the database user can get access to the database. */ bool check_caller_perms(uid_t peer_uid, uid_t target_uid, uid_t *allowed) { if (peer_uid == 0) return true; if (peer_uid == target_uid) return true; if (!allowed) return false; for (size_t i = 0; allowed[i] != 0; i++) if (peer_uid == allowed[i]) return true; return false; } account-utils-1.0.1/libcommon/check_caller_perms.h000066400000000000000000000002471513410760700222470ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once #include extern bool check_caller_perms(uid_t peer_uid, uid_t target_uid, uid_t *allowed); account-utils-1.0.1/libcommon/chfn_checks.c000066400000000000000000000065001513410760700206710ustar00rootroot00000000000000//SPDX-License-Identifier: LGPL-2.1-or-later #include "config.h" #include #include #include #include #include #include "basics.h" #include "chfn_checks.h" static const char * get_chfn_restrict(char **ret_error) { static const char *value = NULL; _cleanup_(econf_freeFilep) econf_file *key_file = NULL; econf_err error; char *val = NULL; if (value) return value; error = econf_readConfig(&key_file, NULL /* project */, _PATH_VENDORDIR /* usr_conf_dir */, "login" /* config_name */, "defs" /* config_suffix */, "= \t" /* delim */, "#" /* comment */); if (error != ECONF_SUCCESS) { if (ret_error) { _cleanup_free_ char *errstr = NULL; if (asprintf(&errstr, "Cannot parse login.defs: %s", econf_errString(error)) < 0) *ret_error = NULL; else *ret_error = TAKE_PTR(errstr); } return ""; /* be very restrictive, allow nothing */ } error = econf_getStringValue (key_file, NULL, "CHFN_RESTRICT", &val); if (error != ECONF_SUCCESS) { if (ret_error) { _cleanup_free_ char *errstr = NULL; if (asprintf(&errstr, "Error reading CHFN_RESTRICT: %s", econf_errString(error)) < 0) *ret_error = NULL; else *ret_error = TAKE_PTR(errstr); } return ""; } else value = val; return value; } bool may_change_field(uid_t uid, char field, char **error) { const char *cp; /* root is always allowed to change everything. */ if (uid == 0) return true; /* CHFN_RESTRICT specifies exactly which fields may be changed by regular users. */ cp = get_chfn_restrict(error); if (error && *error) return false; if (strchr(cp, field)) return true; return false; } /* convert a multibye string to a wide character string, so that we can use iswprint. */ static int mbstowcs_alloc (const char *string, wchar_t **ret) { size_t size; _cleanup_free_ wchar_t *buf = NULL; if (!string) return -EINVAL; size = mbstowcs(NULL, string, 0); if (size == (size_t) -1) return -EINVAL; buf = calloc(size + 1, sizeof(wchar_t)); if (buf == NULL) return -ENOMEM; size = mbstowcs(buf, string, size); if (size == (size_t) -1) return -EINVAL; *ret = TAKE_PTR(buf); return 0; } bool chfn_check_string(const char *string, const char *illegal, char **error) { _cleanup_free_ wchar_t *wstr = NULL; _cleanup_free_ wchar_t *willegal = NULL; int r; if (error) *error = NULL; r = mbstowcs_alloc(string, &wstr); if (r < 0) { if (error) *error = strdup(strerror(-r)); return false; } r = mbstowcs_alloc(illegal, &willegal); if (r < 0) { if (error) *error = strdup(strerror(-r)); return false; } for (size_t i = 0; i < wcslen(wstr); i++) { wchar_t c = wstr[i]; if (wcschr(willegal, c) != NULL || c == '\n') { if (error) if (asprintf(error, "The characters '%s\\n' are not allowed.", illegal) < 0) *error = NULL; return false; } if (iswcntrl (c)) { if (error) *error = strdup("Control characters are not allowed."); return false; } } return true; } account-utils-1.0.1/libcommon/chfn_checks.h000066400000000000000000000003461513410760700207000ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later #pragma once extern bool may_change_field(uid_t uid, char field, char **error); extern bool chfn_check_string(const char *string, const char *illegal, char **error); account-utils-1.0.1/libcommon/context.h000066400000000000000000000002631513410760700201240ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #include "read_config.h" struct context_t { struct config_t cfg; sd_event *loop; }; account-utils-1.0.1/libcommon/create_hash.c000066400000000000000000000024171513410760700207040ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause #include "config.h" #include #include #include #include #include "basics.h" #include "files.h" int create_hash(const char *password, const char *prefix, unsigned long count, char **hash, char **error) { /* Strings returned by crypt_gensalt_rn will be no longer than this. */ char salt[CRYPT_GENSALT_OUTPUT_SIZE]; _cleanup_free_ struct crypt_data *cdata = NULL; char *sp; assert(password); assert(hash); sp = crypt_gensalt_rn(prefix, count, NULL, 0, salt, sizeof(salt)); if (sp == NULL) return -errno; cdata = calloc(1, sizeof(*cdata)); if (cdata == NULL) return -ENOMEM; sp = crypt_r(password, salt, cdata); if (sp == NULL) return -errno; if (!strneq(sp, prefix, strlen(prefix))) { /* crypt doesn't know the algorithm, error out */ int r = -ENOSYS; if (error) { if (asprintf (error, "Algorithm with prefix '%s' is not supported by the crypto backend.", prefix) < 0) { *error = NULL; r = -ENOMEM; } } explicit_bzero(cdata, sizeof(struct crypt_data)); return r; } *hash = strdup(sp); explicit_bzero(cdata, sizeof(struct crypt_data)); if (*hash == NULL) return -ENOMEM; return 0; } account-utils-1.0.1/libcommon/files.c000066400000000000000000000162461513410760700175450ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause #include "config.h" #include #include #include #include #include #include #include #include #include #include "basics.h" #include "files.h" #ifdef WITH_SELINUX #include #define SELINUX_ENABLED (is_selinux_enabled()>0) #else #define SELINUX_ENABLED 0 #endif #define MAX_LOCK_RETRIES 300 /* How often should we try to lock password file */ typedef int (*update_account_file_cb)(FILE*, FILE*, void*); static int lock_db(void) { int retries = 0; int r; while((r = lckpwdf()) != 0 && retries < MAX_LOCK_RETRIES) { usleep(10000); /* 1/100 second */ ++retries; } if (r < 0) { if (retries == MAX_LOCK_RETRIES) return -ENOLCK; else return -errno; } return 0; } static void unlink_and_free_tempfilep(char **p) { if (p == NULL || *p == NULL) return; /* If the file is created with mkstemp(), it will (almost always) change the suffix. Treat this as a sign that the file was successfully created. We ignore both the rare case where the original suffix is used and unlink failures. */ if (!endswith(*p, ".XXXXXX")) (void) unlink(*p); *p = mfree(*p); } static inline void umaskp(mode_t *u) { umask(*u); } /* This is much like mkostemp() but is subject to umask(). */ static int mkostemp_safe(char *pattern) { _cleanup_(umaskp) mode_t _saved_umask_ = umask(0077); int r; r = mkostemp(pattern, O_CLOEXEC); if (r < 0) return -errno; else return r; } /* return value: < 0 : error 0 : found, nothing changed 1 : entry changed */ static int update_passwd_entry(FILE *oldf, FILE *newf, void *ctx) { struct passwd *pw; struct passwd *newpw = ctx; int gotit = 0; int r; /* Loop over all passwd entries */ while ((pw = fgetpwent(oldf)) != NULL) { if(!gotit && streq(newpw->pw_name, pw->pw_name)) { /* XXX we don't support changing uid/gid yet */ int changed = 0; if (newpw->pw_passwd != NULL && !streq(pw->pw_passwd, newpw->pw_passwd)) { pw->pw_passwd = newpw->pw_passwd; changed = 1; } if (newpw->pw_shell != NULL && !streq(pw->pw_shell, newpw->pw_shell)) { pw->pw_shell = newpw->pw_shell; changed = 1; } if (newpw->pw_gecos != NULL && !streq(pw->pw_gecos, newpw->pw_gecos)) { pw->pw_gecos = newpw->pw_gecos; changed = 1; } if (newpw->pw_dir != NULL && !streq(pw->pw_dir, newpw->pw_dir)) { pw->pw_dir = newpw->pw_dir; changed = 1; } if (!changed) /* nothing to change, change nothing */ return 0; gotit = 1; } /* write the passwd entry to tmp file */ r = putpwent(pw, newf); if (r < 0) return -errno; } if (gotit == 0) return -ENODATA; return gotit; } /* return value: < 0 : error 0 : found, nothing changed 1 : entry changed */ static int update_shadow_entry(FILE *oldf, FILE *newf, void *ctx) { struct spwd *sp; struct spwd *newsp = ctx; int gotit = 0; int r; /* Loop over all shadow entries */ while ((sp = fgetspent(oldf)) != NULL) { if(!gotit && streq(newsp->sp_namp, sp->sp_namp)) { /* write the new shadow entry to tmp file */ r = putspent(newsp, newf); if (r < 0) return -errno; gotit = 1; } else { /* write the shadow entry to tmp file */ r = putspent(sp, newf); if (r < 0) return -errno; } } if (gotit == 0) return -ENODATA; return gotit; } static int update_account_locked(const char *etcdir, const char *name, update_account_file_cb update_account_entry, void *ctx) { _cleanup_(unlink_and_free_tempfilep) char *tmpfn = NULL; _cleanup_free_ char *origfn = NULL; _cleanup_free_ char *oldfn = NULL; _cleanup_close_ int newfd = -EBADF; _cleanup_fclose_ FILE *oldf = NULL; _cleanup_fclose_ FILE *newf = NULL; struct stat st; int r; assert(etcdir); assert(name); assert(update_account_entry); assert(ctx); if (asprintf(&origfn, "%s/%s", etcdir, name) < 0) return -ENOMEM; if (asprintf(&oldfn, "%s/%s-", etcdir, name) < 0) return -ENOMEM; if (asprintf(&tmpfn, "%s/.%s.XXXXXX", etcdir, name) < 0) return -ENOMEM; if ((oldf = fopen(origfn, "r")) == NULL) return -errno; if (fstat(fileno(oldf), &st) < 0) return -errno; newfd = mkostemp_safe(tmpfn); if (newfd < 0) return newfd; /* newfd == -errno */ r = fchmod(newfd, st.st_mode); if (r < 0) return -errno; r = fchown(newfd, st.st_uid, st.st_gid); if (r < 0) return -errno; #if 0 /* XXX */ r = copy_xattr(passwd_orig, passwd_tmp); if (r > 0) return -r; #endif newf = fdopen(newfd, "w+"); if (newf == NULL) return -errno; /* make sure file descriptior does not get closed twice */ TAKE_FD(newfd); int gotit = update_account_entry(oldf, newf, ctx); if (gotit < 0) return gotit; r = fclose(oldf); oldf = NULL; if (r < 0) return -errno; r = fflush(newf); if (r < 0) return -errno; r = fsync(fileno(newf)); if (r < 0) return -errno; r = fclose(newf); newf = NULL; if (r < 0) return -errno; if (gotit == 0) { /* no changes */ unlink(tmpfn); return 0; } unlink(oldfn); r = link(origfn, oldfn); if (r < 0) return -errno; r = rename(tmpfn, origfn); if (r < 0) return -errno; return 0; } static int update_account(const char *etcdir, const char *name, update_account_file_cb update_account_entry, void *ctx) { _cleanup_free_ char *origfn = NULL; #ifdef WITH_SELINUX char *prev_context_raw = NULL; #endif int r; if (isempty(etcdir)) etcdir = "/etc"; /* XXX adjust lock if etcdir is not /etc */ if (streq(etcdir, "/etc")) { r = lock_db(); if (r < 0) return r; } /* XXX use old password to verify again, else some other process could * have already changed the password meanwhile */ if (asprintf(&origfn, "%s/%s", etcdir, name) < 0) return -ENOMEM; #ifdef WITH_SELINUX if (SELINUX_ENABLED) { char *context_raw = NULL; if (getfilecon_raw(origfn, &context_raw) < 0) return -errno; if (getfscreatecon_raw(&prev_context_raw) < 0) { int saved_errno = errno; freecon(context_raw); return -saved_errno; } if (setfscreatecon_raw(context_raw) < 0) { int saved_errno = errno; freecon(context_raw); freecon(prev_context_raw); return -saved_errno; } freecon(context_raw); } #endif r = update_account_locked(etcdir, name, update_account_entry, ctx); #ifdef WITH_SELINUX if (SELINUX_ENABLED) { if (setfscreatecon_raw(prev_context_raw) < 0) r = -errno; freecon(prev_context_raw); } #endif /* XXX adjust lock if etcdir is not /etc */ if (streq(etcdir, "/etc")) { if (ulckpwdf() != 0) return -errno; } return r; } int update_passwd(struct passwd *newpw, const char *etcdir) { if (!newpw) return -EINVAL; return update_account(etcdir, "passwd", update_passwd_entry, newpw); } int update_shadow(struct spwd *newsp, const char *etcdir) { if (!newsp) return -EINVAL; return update_account(etcdir, "shadow", update_shadow_entry, newsp); } account-utils-1.0.1/libcommon/files.h000066400000000000000000000005731513410760700175460ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause #pragma once #include #include #include extern int update_passwd(struct passwd *newpw, const char *etcdir); extern int update_shadow(struct spwd *newsp, const char *etcdir); extern int create_hash(const char *password, const char *prefix, unsigned long count, char **hash, char **error); account-utils-1.0.1/libcommon/mkdir_p.c000066400000000000000000000016571513410760700200700ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include #include "mkdir_p.h" int mkdir_p(const char *path, mode_t mode) { if (path == NULL) return -EINVAL; if (mkdir(path, mode) == 0) return 0; if (errno == EEXIST) { struct stat st; /* Check if the existing path is a directory */ if (stat(path, &st) != 0) return -errno; /* If not, fail with ENOTDIR */ if (!S_ISDIR(st.st_mode)) return -ENOTDIR; /* if it is a directory, return */ return 0; } /* If it fails for any reason but ENOENT, fail */ if (errno != ENOENT) return -errno; char *buf = strdup(path); if (buf == NULL) return -ENOMEM; int r = mkdir_p(dirname(buf), mode); free(buf); /* if we couldn't create the parent, fail, too */ if (r < 0) return r; return mkdir(path, mode); } account-utils-1.0.1/libcommon/mkdir_p.h000066400000000000000000000026771513410760700201000ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause Copyright (c) 2024, Thorsten Kukuk 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. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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. */ #pragma once #include extern int mkdir_p(const char *path, mode_t mode); account-utils-1.0.1/libcommon/no_new_privs.c000066400000000000000000000003721513410760700211440ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause #include #include "no_new_privs.h" bool no_new_privs_enabled(void) { /* The no_new_privs flag disables setuid at execve(2) time. */ return (prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0) == 1); } account-utils-1.0.1/libcommon/no_new_privs.h000066400000000000000000000001501513410760700211430ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause #include extern bool no_new_privs_enabled(void); account-utils-1.0.1/libcommon/read_config.c000066400000000000000000000105651513410760700207010ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include #include "basics.h" #include "read_config.h" struct config_t * struct_config_free(struct config_t *cfg) { cfg->allow_get_user_record = mfree(cfg->allow_get_user_record); cfg->allow_verify_password = mfree(cfg->allow_verify_password); cfg->allow_expired_check = mfree(cfg->allow_expired_check); return NULL; } /* trim leading and trailing whitespaces */ static char * trim_whitespace(char *str) { char *end; while(isspace((unsigned char)*str)) str++; if(*str == '\0') return str; end = str + strlen(str) - 1; while(end > str && isspace((unsigned char)*end)) end--; *(end+1) = 0; return str; } /* function to resolve a single user string (name or UID) to a uid_t Returns 0 on success, -errno on failure */ static int parse_token(const char *token, uid_t *result_uid) { if (isempty(token)) return -EINVAL; /* if the first character is a number, it must be a UID */ if (isdigit(token[0])) { char *ep; long long ll; errno = 0; ll = strtol(token, &ep, 10); if (errno == ERANGE || ll < 0 || ll > UINT32_MAX || token == ep || *ep != '\0') { if (errno == 0) return -EINVAL; else return -errno; } *result_uid = (uid_t)ll; return 0; } else /* Try as username */ { struct passwd *pwd; errno = 0; pwd = getpwnam(token); if (pwd == NULL) { if (errno == 0) return -ENODATA; else return -errno; } *result_uid = pwd->pw_uid; } return 0; } static econf_err lookup_group(econf_file *key_file, const char *group, uid_t **list) { _cleanup_free_ char *value = NULL; _cleanup_free_ uid_t *uids = NULL; econf_err error; *list = NULL; /* look at first in method specific group */ error = econf_getStringValue(key_file, group, "allow", &value); if (error == ECONF_NOKEY || error == ECONF_NOGROUP) /* Fallback if key not found */ error = econf_getStringValue(key_file, "global", "allow", &value); if (error == ECONF_NOKEY || error == ECONF_NOGROUP) /* no data, done */ return ECONF_SUCCESS; /* error out in other cases */ if (error != ECONF_SUCCESS) return error; if (isempty(value)) return ECONF_SUCCESS; /* split value into tokens and parse them */ /* count number of tokens */ size_t count = 0; for (size_t i = 0; value[i] != '\0'; i++) if (value[i] == ',') count++; count++; /* allocate one more slot for the final "NULL" */ uids = calloc(count + 1, sizeof (uid_t)); if (uids == NULL) return ECONF_NOMEM; /* Split and store */ count = 0; char *token = strtok(value, ","); while (token != NULL) { uid_t uid = 0; int r; token = trim_whitespace(token); r = parse_token(token, &uid); token = strtok(NULL, ","); if (r < 0) { /* XXX we need a good warning */ continue; /* continue with the other user */ } if (uid == 0) /* root is allways allowed, ignore */ continue; uids[count++] = uid; } uids[count] = 0; *list = TAKE_PTR(uids); return ECONF_SUCCESS; } /* we will read everything and only report the firstx error */ econf_err read_config(struct config_t *cfg) { _cleanup_(econf_freeFilep) econf_file *key_file = NULL; econf_err error; econf_err retval = ECONF_SUCCESS; #ifdef TESTSDIR error = econf_newKeyFile_with_options(&key_file, "ROOT_PREFIX="TESTSDIR); if (error) return error; #endif /* This looks for pwaccessd.conf{.d} in /usr/share/account-utils/ or /etc/account-utils/ */ error = econf_readConfig(&key_file, "account-utils", /* project name */ "/usr/share", /* directory below /usr */ "pwaccessd", /* file name without extension */ "conf", /* suffix */ "=", /* delimiter */ "#"); /* comment */ if (error != ECONF_SUCCESS) return error; /* XXX read debug_level from [global] and set max_log_level */ error = lookup_group(key_file, "GetUserRecord", &(cfg->allow_get_user_record)); if (error && !retval) retval = error; error = lookup_group(key_file, "VerifyPassword", &(cfg->allow_verify_password)); if (error && !retval) retval = error; error = lookup_group(key_file, "ExpiredCheck", &(cfg->allow_expired_check)); if (error && !retval) return error; return retval; } account-utils-1.0.1/libcommon/read_config.h000066400000000000000000000004751513410760700207050ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include struct config_t { uid_t *allow_get_user_record; uid_t *allow_verify_password; uid_t *allow_expired_check; }; extern struct config_t *struct_config_free(struct config_t *cfg); extern econf_err read_config(struct config_t *cfg); account-utils-1.0.1/libcommon/string-util-fundamental.c000066400000000000000000000013641513410760700232130ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1-or-later */ // based on systemd v258 #include #include "basics.h" char *startswith(const char *s, const char *prefix) { size_t l; assert(s); assert(prefix); l = strlen(prefix); if (!strneq(s, prefix, l)) return NULL; return (char*) s + l; } char* endswith(const char *s, const char *suffix) { size_t sl, pl; assert(s); assert(suffix); sl = strlen(s); pl = strlen(suffix); if (pl == 0) return (char*) s + sl; if (sl < pl) return NULL; if (!streq(s + sl - pl, suffix)) return NULL; return (char*) s + sl - pl; } account-utils-1.0.1/libcommon/verify.c000066400000000000000000000106151513410760700177410ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause #include "config.h" #include #include #include #include #include "pwaccess.h" #include "basics.h" #include "verify.h" bool valid_name(const char *name) { /* This function tests if the name has invalid characters, not if the name is really valid. User/group names must match BRE regex: [a-zA-Z0-9_.][a-zA-Z0-9_.-]*$\? Reject every name containing additional characters. */ if (isempty(name)) return false; while (*name != '\0') { if (!((*name >= 'a' && *name <= 'z') || (*name >= 'A' && *name <= 'Z') || (*name >= '0' && *name <= '9') || *name == '_' || *name == '.' || *name == '-' || *name == '$') ) return false; ++name; } return true; } bool is_shadow(const struct passwd *pw) { assert(pw); if (isempty(pw->pw_passwd)) return false; if (streq(pw->pw_passwd, "x") || (pw->pw_passwd && strlen(pw->pw_passwd) > 2 && (pw->pw_passwd[0] == '#') && (pw->pw_passwd[1] == '#') && streq(pw->pw_name, pw->pw_passwd + 2))) return true; return false; } int expired_check(const struct spwd *sp, long *daysleft, bool *pwchangeable) { long int now, passed; assert(sp); if (daysleft) *daysleft = -1; if (pwchangeable) *pwchangeable = true; now = time(NULL) / (60 * 60 * 24); /* account expired */ if (sp->sp_expire > 0 && now >= sp->sp_expire) return PWA_EXPIRED_ACCT; /* new password required */ if (sp->sp_lstchg == 0) { if (daysleft) *daysleft = 0; return PWA_EXPIRED_CHANGE_PW; } /* password aging disabled */ /* The last and max fields must be present for an account to have an expired password. A maximum of >10000 days is considered to be infinite. */ if (sp->sp_lstchg == -1 || sp->sp_max == -1 || sp->sp_max >= 10000) return PWA_EXPIRED_NO; passed = now - sp->sp_lstchg; if (sp->sp_max >= 0) { if (sp->sp_inact >= 0) { long inact = sp->sp_max < LONG_MAX - sp->sp_inact ? sp->sp_max + sp->sp_inact : LONG_MAX; if (passed >= inact) { /* authtok expired */ if (daysleft) *daysleft = inact - passed; return PWA_EXPIRED_PW; } } /* needs a new password */ if (passed >= sp->sp_max) return PWA_EXPIRED_CHANGE_PW; if (sp->sp_warn > 0) { long warn = sp->sp_warn > sp->sp_max ? -1 : sp->sp_max - sp->sp_warn; if (passed >= warn && daysleft) /* warn before expire */ *daysleft = sp->sp_max - passed; } } if (sp->sp_min > 0 && passed < sp->sp_min && pwchangeable) /* The last password change was too recent. */ *pwchangeable = false; return PWA_EXPIRED_NO; } static inline int consttime_streq(const char *userinput, const char *secret) { volatile const char *u = userinput, *s = secret; volatile int ret = 0; do { ret |= *u ^ *s; s += !!*s; } while (*u++ != '\0'); return ret == 0; } int verify_password(const char *hash, const char *p, bool nullok) { _cleanup_free_ char *pp = NULL; if (isempty(p) && !nullok) return VERIFY_FAILED; else if (isempty(hash)) { if (isempty(p) && nullok) return VERIFY_OK; else return VERIFY_FAILED; } else if (!p || *hash == '*' || *hash == '!') return VERIFY_FAILED; else { /* Get the status of the hash from checksalt */ int retval_checksalt = crypt_checksalt(hash); /* * Check for hashing methods that are disabled by * libcrypt configuration and/or system preset. */ if (retval_checksalt == CRYPT_SALT_METHOD_DISABLED) return VERIFY_CRYPT_DISABLED; if (retval_checksalt == CRYPT_SALT_INVALID) return VERIFY_CRYPT_INVALID; struct crypt_data *cdata; cdata = calloc(1, sizeof(*cdata)); if (cdata != NULL) { pp = strdup(crypt_r(p, hash, cdata)); explicit_bzero(cdata, sizeof(struct crypt_data)); free(cdata); } } if (pp && consttime_streq(pp, hash)) return VERIFY_OK; return VERIFY_FAILED; } bool is_blank_password(const struct passwd *pw, const struct spwd *sp) { bool is_blank = false; assert(pw); if (is_shadow(pw)) { if (!sp) is_blank = false; else is_blank = (strlen(strempty(sp->sp_pwdp)) == 0); } else is_blank = (strlen(strempty(pw->pw_passwd)) == 0); return is_blank; } account-utils-1.0.1/libcommon/verify.h000066400000000000000000000012631513410760700177450ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause #pragma once #define VERIFY_OK 0 /* password matches */ #define VERIFY_FAILED 1 /* password does not match */ #define VERIFY_CRYPT_DISABLED 2 /* salt got disabled in libcrypt */ #define VERIFY_CRYPT_INVALID 3 /* salt is not supported by libcrypt */ #include #include extern bool valid_name(const char *name); extern bool is_shadow(const struct passwd *pw); extern bool is_blank_password(const struct passwd *pw, const struct spwd *sp); extern int expired_check(const struct spwd *sp, long *daysleft, bool *pwchangeable); extern int verify_password(const char *hash, const char *password, bool nullok); account-utils-1.0.1/man/000077500000000000000000000000001513410760700150625ustar00rootroot00000000000000account-utils-1.0.1/man/chage.1.xml000066400000000000000000000160511513410760700170150ustar00rootroot00000000000000 chage 1 account-utils %version% chage chage change user password expiry information chage option user DESCRIPTION chage is used to list and change the password expiry information of a user. It allows the system administrator to change the number of days between allowed and required password changes and the date of the last password change. It allows also to define when an account will expire. The chage command is restricted to the system administrator, except for the option, which may be used by an user to determine when his password or account is due to expire. If no option is given, chage operates in an interactive mode, prompting the user with the current values for all of the fields. Enter the new value to change the field, or leave the line blank to use the current value. If the users exists in the local passwd5 file, but not in the local shadow5 file, chage will create a new entry in the shadow file. This implementation does not require the setuid bit set, instead it will communicate via the varlink protocol with pwaccessd8 and pwupd8 to read and modify the account data. OPTIONS This option will list the password expiry information in a human readable format. The user will see the date when he changed the password the last time, when the password will be expire, when the password will be locked and when the account will expire. #days With this option the minimum number of days between password changes is changed. A value of zero for this field indicates that the user may change her password at any time. Else the user will not be permitted to change the password until minimum number of days have elapsed. #days With this option the maximum number of days during which a password is valid is changed. When maxdays plus lastday is less than the current day, the user will be required to change his password before being able to use the account. date With this option the date when the password was last changed can be set to another value. lastday has to be specified as number of days since January 1st, 1970. The date may also be expressed in the format YYYY-MM-DD. If supported by the system, a value of zero forces the user to change the password at next login. expiredate With this option the date when the account will be expired can be changed. The expire date has to be specified as number of days since January 1st, 1970. The date may also be expressed in the format YYYY-MM-DD. #days This option is used to set the number of days of inactivity after a password has expired before the account is locked. A user whose account is locked must contact the system administrator before being able to use the account again. A value of -1 disables this feature. #days With this option the number of days of warning before a password change is required can be changed. This option is the number of days prior to the password expiring that a user will be warned the password is about to expire. FILES /etc/passwd user account information /etc/shadow shadow user account information SEE ALSO pwaccessd8 , pwupd8 , passwd1 , passwd5 , shadow5 account-utils-1.0.1/man/chfn.1.xml000066400000000000000000000143561513410760700166720ustar00rootroot00000000000000 chfn 1 account-utils %version% chfn chfn change finger information chfn option user DESCRIPTION chfn is used to change the user finger information. This are the users fullname, office room number, office phone number and home phone number. This information is stored in the /etc/passwd file and typically printed by tools like finger1 or pinky1 and similiar programs. A normal user may only change the fields for their own account, the super user may change the fields for any account. Also, only the super user may use the option to change the other portions of the GECOS field. If no information is given on the command line, chfn operates in an interactive fashion, prompting the user for each field. Enter the new value to change the field, or leave the line blank to use the current value. Enter none or a blank only to remove the old value. The current value is displayed between a pair of [...] marks. The only restrictions placed on the contents of the fields is that no control characters may be present, nor any of comma, colon, or equal sign. The other field does not have this restriction. This implementation does not require the setuid bit set, instead it will communicate via varlink protocol with pwaccessd8 and pwupd8 to read and modify the account data. OPTIONS Specify the user's real name. Specify the user's office room number. Specify the user's office phone number. Specify the user's home phone number. Specify the other portion of the GECOS field. Print a more verbose help text and exit. Print version information and exit. CONFIGURATION The following configuration variables provided by login.defs5 define the behavior of the chfn command: This parameter specifies which values in the GECOS field may be changed by regular users using the chfn program. It can be any combination of letters , , , and for Full name, Room number, Work phone, Home phone and Other. Only the superuser can make changes if not specified. FILES /etc/passwd user account information SEE ALSO pwaccessd8 , pwupd8 , finger1 , pinky1 , passwd5 , login.defs5 account-utils-1.0.1/man/chsh.1.xml000066400000000000000000000077711513410760700167040ustar00rootroot00000000000000 chsh 1 account-utils %version% chsh chsh change login shell chsh option user DESCRIPTION chsh is used to change the user login shell. A normal user may only change the login shell for their own account, the super user may change the login shell for any account. If a shell is not given on the command line, chsh operates in an interactive fashion, prompting the user with the current login shell. Enter the new value to change the field, or leave the line blank to use the current value. Enter a space or none to remove the old content. The current value is displayed between a pair of [...] marks. The only restrictions placed on the login shell is that the command name must be listed in /etc/shells, unless the invoker is the super-user, and then any value may be added. An account with a restricted login shell may not change their login shell. This implementation does not require the setuid bit set, instead it will communicate via varlink protocol with pwaccessd8 and pwupd8 to read and modify the account data. OPTIONS Specify the login shell. Print the list of allowed shells5 and exit. Print a more verbose help text and exit. Print version information and exit. FILES /etc/passwd user account information SEE ALSO pwaccessd8 , pwupd8 , login1 , passwd5 , shells5 account-utils-1.0.1/man/custom-man.xsl000066400000000000000000000007001513410760700176720ustar00rootroot00000000000000 account-utils-1.0.1/man/expiry.1.xml000066400000000000000000000060741513410760700172720ustar00rootroot00000000000000 expiry 1 account-utils %version% expiry expiry check password expiration and enforce password change expiry -c|-f user DESCRIPTION expiry checks the current password expiration and prints a warning if the password is expiring or enforces a change when required. This implementation does not require the setuid bit set, instead it will communicate via varlink protocol with pwaccessd8 and pwupd8 to read and modify the account data. OPTIONS Print number of days when password expires for the caller or the specified . The caller or is forced to change the password if it is expired but not inactive. FILES /etc/passwd user account information /etc/shadow shadow user account information SEE ALSO pwaccessd8 , pwupd8 , passwd1 , passwd5 , shadow5 account-utils-1.0.1/man/meson.build000066400000000000000000000052771513410760700172370ustar00rootroot00000000000000xsltproc_exe = find_program('xsltproc', required : get_option('man')) want_man = (get_option('man').enabled() or get_option('man').auto()) and xsltproc_exe.found() xsltproc_flags = [ '--nonet', '--xinclude', '--stringparam', 'version', '@0@'.format(meson.project_version()), '--path', '@0@:@1@'.format(meson.current_build_dir(), meson.current_source_dir())] custom_man_xsl = files('custom-man.xsl') xslt_cmd = [xsltproc_exe, '-o', '@OUTPUT0@'] + xsltproc_flags mandir1 = get_option('mandir') / 'man1' mandir8 = get_option('mandir') / 'man8' if xsltproc_exe.found() custom_target('chage.1', input : 'chage.1.xml', output : 'chage.1', command : xslt_cmd + [custom_man_xsl, '@INPUT@'], install : want_man, install_dir : mandir1) custom_target('chfn.1', input : 'chfn.1.xml', output : 'chfn.1', command : xslt_cmd + [custom_man_xsl, '@INPUT@'], install : want_man, install_dir : mandir1) custom_target('chsh.1', input : 'chsh.1.xml', output : 'chsh.1', command : xslt_cmd + [custom_man_xsl, '@INPUT@'], install : want_man, install_dir : mandir1) custom_target('expiry.1', input : 'expiry.1.xml', output : 'expiry.1', command : xslt_cmd + [custom_man_xsl, '@INPUT@'], install : want_man, install_dir : mandir1) custom_target('passwd.1', input : 'passwd.1.xml', output : 'passwd.1', command : xslt_cmd + [custom_man_xsl, '@INPUT@'], install : want_man, install_dir : mandir1) custom_target('pam_unix_ng.8', input : 'pam_unix_ng.8.xml', output : 'pam_unix_ng.8', command : xslt_cmd + [custom_man_xsl, '@INPUT@'], install : want_man, install_dir : mandir8) custom_target('pam_debuginfo.8', input : 'pam_debuginfo.8.xml', output : 'pam_debuginfo.8', command : xslt_cmd + [custom_man_xsl, '@INPUT@'], install : want_man, install_dir : mandir8) custom_target('pwaccessd.8', input : 'pwaccessd.8.xml', output : 'pwaccessd.8', command : xslt_cmd + [custom_man_xsl, '@INPUT@'], install : want_man, install_dir : mandir8) custom_target('pwupdd.8', input : 'pwupdd.8.xml', output : 'pwupdd.8', command : xslt_cmd + [custom_man_xsl, '@INPUT@'], install : want_man, install_dir : mandir8) endif account-utils-1.0.1/man/pam_debuginfo.8.xml000066400000000000000000000067511513410760700205620ustar00rootroot00000000000000 pam_debuginfo 8 account-utils %version% pam_debuginfo pam_debuginfo PAM module logging information for debugging pam_debuginfo.so ... DESCRIPTION This is a PAM module which logs a lot of useful information for debugging. It logs the following information: Service name PAM type PAM flags UID EUID PAM items SELinux status NoNewPrivs status OPTIONS level Define with which syslog3 level the information should be printed. Valid values are debug, info, notice, warning, error, critical, alert and emerg. MODULE TYPES PROVIDED All module types (, , , ) are provided. RETURN VALUES PAM_IGNORE Returned by all service types. EXAMPLES Add the following line to e.g. /etc/pam.d/login to log all information via syslog8 : auth optional pam_debuginfo.so account optional pam_debuginfo.so password optional pam_debuginfo.so session optional pam_debuginfo.so SEE ALSO pam.conf5 , pam.d5 , pam8 AUTHOR pam_debuginfo was written by Thorsten Kukuk <kukuk@suse.com>. account-utils-1.0.1/man/pam_unix_ng.8.xml000066400000000000000000000166151513410760700202670ustar00rootroot00000000000000 pam_unix_ng 8 account-utils %version% pam_unix_ng pam_unix_ng PAM module for traditional password authentication pam_unix_ng.so ... DESCRIPTION This is a standard UNIX authentication PAM module which delegates tasks requiring access to /etc/shadow to pwaccessd8, which allows to use this module in environments without setuid binaries. If pwaccessd is not running, it tries to read the local files as fallback itself. OPTIONS debug Print debug information via syslog3 . quiet Avoid all messages except errors. nullok The default action of this module is to not permit the user access to a service if their official password is blank. The argument overrides this default. If the application sets the PAM_DISALLOW_NULL_AUTHTOK flag, is ignored in the auth module type. try_first_pass The module first attempts to use the password from the previously stacked modules to see if it is also suitable for this module before prompting the user to enter their password again. use_first_pass The module only attempts to use the password from the previously stacked modules and never prompts the user for input. If no password is available or the password does not match, the user is denied access. use_authtok When a password is changed, the module will set the new password to the one provided by a previously stacked module. authtok_type=type The default action is for the module to use the following prompts when requesting passwords: "New UNIX password: " and "Retype UNIX password: ". The example word UNIX can be replaced with this option, by default it is empty. minlen=<number> Minimal length of new password. The default is 8 characters. crypt_prefix=<prefix> Prefix of the hash algorithm to use. See crypt5 for valid values. crypt_count=<number> This option controls the processing cost of the hash. See crypt5 for valid values. fail_delay=<milliseconds> The module requests by default a delay of 2000 milliseconds should the authentication as a whole fail. This argument can be used to adjust the delay or disable it (fail_delay=0). MODULE TYPES PROVIDED All module types (, , , ) are provided. RETURN VALUES PAM_SUCCESS Everything was successful. PAM_SERVICE_ERR Internal service module error. PAM_USER_UNKNOWN User not known. PAM_IGNORE Returned by service types which do nothing. EXAMPLES Add the following line to e.g. /etc/pam.d/login to log when a user logs in and out to syslog8 : session required pam_unix_ng.so SEE ALSO pwaccessd8 , pam.conf5 , pam.d5 , pam8 AUTHOR pam_unix_ng was written by Thorsten Kukuk <kukuk@suse.com>. account-utils-1.0.1/man/passwd.1.xml000066400000000000000000000201011513410760700172360ustar00rootroot00000000000000 passwd 1 account-utils %version% passwd passwd change user password passwd option user DESCRIPTION The command changes passwords for user accounts. While an administrator may change the password for any account, a normal user is only allowed to change the password for their own account. can also change account information, such as the full name of the user, their login shell and password expiry dates or disable an account. This implementation does not require the setuid bit set, instead it will communicate via the varlink protocol with pwaccessd8 and pwupd8 to read and modify the account data. OPTIONS The password of the given account can be deleted by the system administrator. If the PAM stack is configured accordingly, the user can log in without entering a password. Immediately expire the password. The user will be forced to change the password at next login. -h, --help Print a verbose help text and exit. days This option is used to set the number of days of inactivity after a password has expired before the account is locked. A user whose account is locked must contact the system administrator before being able to use the account again. A value of -1 disables this feature. Keep non-expired authentication tokens. The password will only be changed if it is expired. This functionality depends on the used PAM modules to change the password. A system administrator can lock the account of the specified user by adding a ! in front of the password, so that it cannot match anything. #days With this option the minimum number of days between password changes is changed. A value of zero for this field indicates that the user may change her password at any time. Else the user will not be permitted to change the password until minimum number of days have elapsed. #days With this option the maximum number of days during which a password is valid is changed. When maxdays plus lastday is less than the current day, the user will be required to change his password before being able to use the account. Suppress informal messages. This mainly depends on the used PAM modules. Read the password from stdin, which could also be a pipe. Other input requested from a PAM module will lead to an error. Report password status on the named account. The first part indicates if the user account is locked (LK), has no password (NP), or has an existing or locked password (PS). The second part gives the date of the last password change. The next parts are the minimum age, maximum age, warning period, and inactivity period for the password. A system administrator can unlock the specified account by removing the ! in front of the password again. This can lead to a password less account, if it was password less before, too. Print version information and exit. #days With this option the number of days of warning before a password change is required can be changed. This option is the number of days prior to the password expiring that a user will be warned the password is about to expire. FILES /etc/passwd user account information /etc/shadow shadow user account information SEE ALSO pwaccessd8 , pwupd8 , passwd5 , shadow5 account-utils-1.0.1/man/pwaccessd.8.xml000066400000000000000000000156141513410760700177350ustar00rootroot00000000000000 pwaccessd 8 account-utils %version% pwaccessd pwaccessd pwaccessd.service pwaccessd.socket manage passwd and shadow information pwaccessd.service pwaccessd.socket /usr/libexec/pwaccessd OPTIONS Description pwaccessd is a systemd1 socket-activated service which provides account information in struct passwd and struct shadow format. It is capable of checking if a password or account has expired and verifies passwords. By default, normal users only have access to their own passwd and shadow entries. The root user has access to all accounts. Specific users can be granted extended access via configuration. Options Activation through socket. This is the standard mode when running under systemd. Enable debug mode. Enable verbose logging. Give the help list. Print program version. Varlink Interfaces The pwaccessd daemon exposes the following functionality via Varlink interfaces: GetAccountName Provides the user name corresponding to a given UID. GetUserRecord Provides the passwd and shadow entry for a given UID or account name. GetGroupRecord Provides the group entry for a given GID or group name. VerifyPassword Validates a password for a specific user. ExpiredCheck Checks if a user account or password is expired. Configuration pwaccessd reads its configuration from pwaccessd.conf. It follows the UAPI Configuration Files Specification, meaning it searches for configuration files in directories such as /usr/share/account-utils/, /run/account-utils/, and /etc/account-utils/. Files in /etc/account-utils/ take precedence. The configuration format is INI-style. The primary configuration key is allow. This key accepts a list of user accounts that are allowed to read all passwd and shadow entries, in addition to root. The allow key can be defined within specific sections (groups) corresponding to the Varlink interface methods: [GetUserRecord] [VerifyPassword] [ExpiredCheck] If the key is not found in the specific section, pwaccessd will fall back to looking in the [global] section. Example pwaccessd.conf [global] # Allow user 'admin' to perform all actions allow = admin [VerifyPassword] # Allow 'auth-service' to verify passwords, overriding global allow = auth-service Files /usr/libexec/pwaccessd The daemon binary. /etc/account-utils/pwaccessd.conf The main configuration file. See Also systemd1, expiry1, passwd1, passwd5, shadow5, pam_unix_ng8 account-utils-1.0.1/man/pwupdd.8.xml000066400000000000000000000112741513410760700172620ustar00rootroot00000000000000 pwupdd 8 account-utils %version% pwupdd pwupdd pwupdd.service pwupdd.socket update passwd and shadow entries pwupdd.service pwupdd.socket /usr/libexec/pwupdd OPTIONS Description pwupdd is an inetd8 style socket-activated service. A new instance of the daemon is started for every incoming request. It exposes a Varlink interface to allow authorized users to modify their own account data, including passwords, login shells, and GECOS field information. Authentication is handled via PAM8. Additionally, the root user can utilize specific methods to update any /etc/passwd or /etc/shadow entry. Options , Enable debug mode. , Enable verbose logging. , Give the help list. Print program version. Varlink Interfaces The service exposes the following methods via Varlink: Chauthtok Changes the password for a provided user. Authentication is performed via PAM using the configuration pwupd-passwd. This method may be called by the root user or the user owning the record. Chfn Changes the finger (GECOS) information of a user. Authentication is performed via PAM using the configuration pwupd-chfn. This method may be called by the root user or the user owning the record. Chsh Changes the login shell of an account. Authentication is performed via PAM using the configuration pwupd-chsh. This method may be called by the root user or the user owning the record. UpdatePasswdShadow Updates the passwd and shadow entry of a specified user. Only root is allowed to call this method. See Also passwd1, chfn1, chsh1, pam8 account-utils-1.0.1/meson.build000066400000000000000000000216421513410760700164560ustar00rootroot00000000000000project( 'account-utils', 'c', meson_version : '>= 0.61.0', default_options : [ 'prefix=/usr', 'sysconfdir=/etc', 'localstatedir=/var', 'buildtype=debugoptimized', 'default_library=shared', 'b_pie=true', 'b_lto=true', 'warning_level=2'], license : ['GPL-2.0-or-later', 'LGPL-2.1-or-later'], version : '1.0.1', ) distribution = get_option('distribution') if distribution == '' distribution = 'example' endif conf = configuration_data() conf.set_quoted('VERSION', meson.project_version()) conf.set_quoted('PACKAGE', meson.project_name()) conf.set_quoted('_VARLINK_PWACCESS_SOCKET_DIR', '/run/account') conf.set_quoted('_VARLINK_PWACCESS_SOCKET', '/run/account/pwaccess-socket') conf.set_quoted('_VARLINK_PWUPD_SOCKET_DIR', '/run/account') conf.set_quoted('_VARLINK_PWUPD_SOCKET', '/run/account/pwupd-socket') conf.set_quoted('_VARLINK_NEWIDMAPD_SOCKET_DIR', '/run/account') conf.set_quoted('_VARLINK_NEWIDMAPD_SOCKET', '/run/account/newidmapd-socket') conf.set_quoted('_PATH_VENDORDIR', get_option('vendordir')) cc = meson.get_compiler('c') pkg = import('pkgconfig') inc = include_directories(['include','libcommon','libclient']) add_project_arguments(['-D_GNU_SOURCE=1', '-DXTSTRINGDEFINES', '-D_FORTIFY_SOURCE=2', '-D_FILE_OFFSET_BITS=64', '-D_TIME_BITS=64'], language : 'c') possible_cc_flags = [ '-fstack-protector-strong', '-funwind-tables', '-fasynchronous-unwind-tables', '-fstack-clash-protection', '-Werror=return-type', '-Wbad-function-cast', '-Wcast-align', '-Wformat-security', '-Winline', '-Wmissing-declarations', '-Wmissing-prototypes', '-Wnested-externs', '-Wshadow', '-Wstrict-prototypes', '-Wundef', ] add_project_arguments(cc.get_supported_arguments(possible_cc_flags), language : 'c') prefixdir = get_option('prefix') if not prefixdir.startswith('/') error('Prefix is not absolute: "@0@"'.format(prefixdir)) endif libexecdir = join_paths(prefixdir, get_option('libexecdir')) systemunitdir = prefixdir / 'lib/systemd/system' tmpfilesdir = prefixdir / 'lib/tmpfiles.d' pamlibdir = get_option('pamlibdir') if pamlibdir == '' pamlibdir = get_option('libdir') / 'security' endif pamconfdir = get_option('pamconfdir') if pamconfdir == '' pamconfdir = prefixdir / 'lib/pam.d' endif libpam = dependency('pam', required: true) libpam_misc = dependency('pam_misc', required: true) libsystemd = dependency('libsystemd', version: '>= 257', required: true) libcrypt = dependency('libxcrypt', version: '>= 4.4.27', required: true) libeconf = dependency('libeconf', version : '>=0.7.5', required : true) libcap = dependency('libcap', required: get_option('tools')) libselinux = dependency('libselinux', required: get_option('selinux')) if libselinux.found() conf.set('WITH_SELINUX', 1) endif libpwaccess_c = files('lib/varlink.c') libpwaccess_map = 'lib/libpwaccess.map' libpwaccess_map_version = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), libpwaccess_map) libpwaccess = shared_library( 'pwaccess', libpwaccess_c, include_directories : inc, link_args : ['-shared', libpwaccess_map_version], link_depends : libpwaccess_map, dependencies : [libsystemd], install : true, version : meson.project_version(), soversion : '0' ) install_headers('include/pwaccess.h') pkg.generate( libpwaccess, name : 'libpwaccess', description : 'library to read passwd and shadow entries via varlink daemon', version : meson.project_version(), ) libcommon_c = files('libcommon/check_caller_perms.c', 'libcommon/chfn_checks.c', 'libcommon/create_hash.c', 'libcommon/files.c', 'libcommon/mkdir_p.c', 'libcommon/no_new_privs.c', 'libcommon/read_config.c', 'libcommon/string-util-fundamental.c', 'libcommon/verify.c') libcommon = static_library( 'common', libcommon_c, include_directories : inc, install : false ) libclient_c = files('libclient/chauthtok.c', 'libclient/drop_privs.c', 'libclient/get_logindefs.c', 'libclient/get_value.c', 'libclient/varlink-client-common.c') libclient = static_library( 'client', libclient_c, include_directories : inc, install : false ) pwaccessd_c = ['src/pwaccessd.c', 'src/varlink-org.openSUSE.pwaccess.c', 'src/varlink-service-common.c'] pwupdd_c = ['src/pwupdd.c', 'src/varlink-org.openSUSE.pwupd.c', 'src/varlink-service-common.c'] newidmapd_c = ['src/newidmapd.c', 'src/varlink-org.openSUSE.newidmapd.c', 'libcommon/mkdir_p.c', 'src/map_range.c', 'src/varlink-service-common.c'] executable('pwaccessd', pwaccessd_c, include_directories : inc, link_with : [libcommon], dependencies : [libsystemd, libcrypt, libeconf], install_dir : libexecdir, install : true) executable('pwupdd', pwupdd_c, include_directories : inc, link_with : [libcommon, libpwaccess], dependencies : [libpam, libsystemd, libeconf, libselinux], install_dir : libexecdir, install : true) executable('newidmapd', newidmapd_c, include_directories : inc, link_with : [libpwaccess], dependencies : [libsystemd, libeconf], install_dir : libexecdir, install : true) executable('newuidmap', 'src/newxidmap.c', 'src/map_range.c', c_args : '-DXID="uid"', include_directories : inc, link_with : [libclient, libcommon], dependencies : [libsystemd], install : true) executable('newgidmap', 'src/newxidmap.c', 'src/map_range.c', c_args : '-DXID="gid"', include_directories : inc, link_with : [libclient, libcommon], dependencies : [libsystemd], install : true) executable('chage', 'src/chage.c', include_directories : inc, link_with : [libclient, libpwaccess], dependencies : [libsystemd, libpam, libpam_misc, libeconf], install : true) executable('chfn', 'src/chfn.c', include_directories : inc, link_with : [libclient, libcommon, libpwaccess], dependencies : [libsystemd, libpam, libpam_misc, libeconf], install : true) executable('chsh', 'src/chsh.c', include_directories : inc, link_with : [libclient, libpwaccess], dependencies : [libsystemd, libpam, libpam_misc, libeconf], install : true) executable('passwd', 'src/passwd.c', include_directories : inc, link_with : [libclient, libcommon, libpwaccess], dependencies : [libsystemd, libpam, libpam_misc, libeconf], install : true) executable('expiry', 'src/expiry.c', include_directories : inc, link_with : [libclient, libpwaccess], dependencies : [libsystemd, libpam, libpam_misc], install : true) pam_unix_ng_c = files('src/pam_unix_ng-common.c', 'src/pam_unix_ng-session.c', 'src/pam_unix_ng-acct.c', 'src/pam_unix_ng-auth.c', 'src/pam_unix_ng-passwd.c') pam_unix_ng_map = 'src/pam_unix_ng.map' pam_unix_ng_map_version = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), pam_unix_ng_map) pam_unix_ng = shared_library( 'pam_unix_ng', pam_unix_ng_c, name_prefix : '', include_directories : inc, link_args : ['-shared', pam_unix_ng_map_version], link_depends : pam_unix_ng_map, link_with : [libclient, libcommon, libpwaccess], dependencies : [libcrypt, libeconf, libpam, libselinux], install : true, install_dir : pamlibdir ) pam_debuginfo_c = ['src/pam_debuginfo.c', 'libcommon/no_new_privs.c', 'libcommon/string-util-fundamental.c'] pam_debuginfo = shared_library( 'pam_debuginfo', pam_debuginfo_c, name_prefix : '', include_directories : inc, link_args : ['-shared', pam_unix_ng_map_version], link_depends : pam_unix_ng_map, dependencies : [libpam, libselinux], install : true, install_dir : pamlibdir ) # create config.h subdir('include') # systemd units subdir('units') if get_option('tools') subdir('tools') endif # example pam configuration files pamdpwupdchfnsrc = 'pam.d' / distribution / 'pwupd-chfn' pamdpwupdchshsrc = 'pam.d' / distribution / 'pwupd-chsh' pamdpwupdpasswdsrc = 'pam.d' / distribution / 'pwupd-passwd' pamdpasswdsrc = 'pam.d' / distribution / 'passwd' install_data(pamdpwupdchfnsrc, install_dir : pamconfdir) install_data(pamdpwupdchshsrc, install_dir : pamconfdir) install_data(pamdpwupdpasswdsrc, install_dir : pamconfdir) install_data(pamdpasswdsrc, install_dir : pamconfdir) # Unit tests subdir('tests') # Manual pages subdir('man') subdir('example') account-utils-1.0.1/meson_options.txt000066400000000000000000000013211513410760700177410ustar00rootroot00000000000000option('man', type : 'feature', value : 'auto', description : 'build and install man pages') option('pamlibdir', type : 'string', description : 'directory for PAM modules') option('pamconfdir', type : 'string', description : 'directory for PAM configuration') option('selinux', type: 'feature', value: 'auto', description: 'SELinux support') option('vendordir', type: 'string', value: '/usr/etc', description : 'directory for distribution provided config files (e.g. login.defs)') option('distribution', type : 'string', description : 'Name of used distribution: example, suse') option('tools', type: 'boolean', value: true, description: 'Build additional utilities') account-utils-1.0.1/pam.d/000077500000000000000000000000001513410760700153065ustar00rootroot00000000000000account-utils-1.0.1/pam.d/example/000077500000000000000000000000001513410760700167415ustar00rootroot00000000000000account-utils-1.0.1/pam.d/example/passwd000066400000000000000000000003041513410760700201620ustar00rootroot00000000000000#%PAM-1.0 auth required pam_unix_ng.so account required pam_unix_ng.so password requisite pam_pwquality.so password required pam_unix_ng.so use_authtok session required pam_unix_ng.so account-utils-1.0.1/pam.d/example/pwupd-chfn000066400000000000000000000003431513410760700207370ustar00rootroot00000000000000#%PAM-1.0 auth sufficient pam_rootok.so auth required pam_unix_ng.so account required pam_unix_ng.so password requisite pam_pwquality.so password required pam_unix_ng.so use_authtok session required pam_unix_ng.so account-utils-1.0.1/pam.d/example/pwupd-chsh000066400000000000000000000003431513410760700207460ustar00rootroot00000000000000#%PAM-1.0 auth sufficient pam_rootok.so auth required pam_unix_ng.so account required pam_unix_ng.so password requisite pam_pwquality.so password required pam_unix_ng.so use_authtok session required pam_unix_ng.so account-utils-1.0.1/pam.d/example/pwupd-passwd000066400000000000000000000003041513410760700213170ustar00rootroot00000000000000#%PAM-1.0 auth required pam_unix_ng.so account required pam_unix_ng.so password requisite pam_pwquality.so password required pam_unix_ng.so use_authtok session required pam_unix_ng.so account-utils-1.0.1/pam.d/suse/000077500000000000000000000000001513410760700162655ustar00rootroot00000000000000account-utils-1.0.1/pam.d/suse/passwd000066400000000000000000000002111513410760700175030ustar00rootroot00000000000000#%PAM-1.0 auth include common-auth account include common-account session include common-session password include common-password account-utils-1.0.1/pam.d/suse/pwupd-chfn000066400000000000000000000002501513410760700202600ustar00rootroot00000000000000#%PAM-1.0 auth sufficient pam_rootok.so auth include common-auth account include common-account session include common-session password include common-password account-utils-1.0.1/pam.d/suse/pwupd-chsh000066400000000000000000000002501513410760700202670ustar00rootroot00000000000000#%PAM-1.0 auth sufficient pam_rootok.so auth include common-auth account include common-account session include common-session password include common-password account-utils-1.0.1/pam.d/suse/pwupd-passwd000066400000000000000000000002111513410760700206400ustar00rootroot00000000000000#%PAM-1.0 auth include common-auth account include common-account session include common-session password include common-password account-utils-1.0.1/src/000077500000000000000000000000001513410760700150765ustar00rootroot00000000000000account-utils-1.0.1/src/chage.c000066400000000000000000000364451513410760700163250ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include "config.h" #include #include #include #include #include #include #include #include #include #include "basics.h" #include "pwaccess.h" #include "varlink-client-common.h" #include "get_value.h" #include "get_logindefs.h" #include "drop_privs.h" #define DAY (24L*3600L) #define SCALE DAY static int oom(void) { fprintf(stderr, "Out of memory!\n"); return ENOMEM; } /* convert a string to a time_t value and return it as number of days since 1.1.1970. */ static long int str2date(const char *str) { struct tm tp; char *cp; time_t result; if (streq(str, "1969-12-31")) return -1; memset(&tp, 0, sizeof tp); cp = strptime(str, "%Y-%m-%d", &tp); if (!cp || *cp != '\0') return -1; result = mktime(&tp); if (result == (time_t) -1) return -1; return (result + (DAY/2)) / DAY; } /* convert time_t into a readable date string. */ static char * date2str(time_t date) { struct tm *tp; char buf[20]; tp = gmtime(&date); if (strftime(buf, sizeof(buf), "%Y-%m-%d", tp) == 0) { fprintf(stderr, "strftime failed!\n"); return NULL; } return strdup(buf); } static void format_date_buf(char *buf, size_t buf_size, long date_val) { if (date_val == -1) strcpy(buf, "-1"); else { _cleanup_free_ char *p = date2str(date_val * SCALE); if (p != NULL) strlcpy(buf, p, buf_size); else strlcpy(buf, "-1", buf_size); } } static int prompt_and_check(char *buf, const char *prompt, char **result_ptr) { int r = get_value(buf, prompt, result_ptr); if (r < 0) return -r; if (*result_ptr == NULL) { fprintf(stderr, "chage aborted.\n"); return ENODATA; } return 0; } /* Print the time in a human readable format. */ static void print_date(time_t date, bool iso8601) { struct tm *tp; char buf[40]; tp = gmtime(&date); if (strftime(buf, sizeof buf, iso8601?"%F":"%b %d, %Y", tp) == 0) { fprintf(stderr, "strftime failed!\n"); return; } puts(buf); } /* Print the current values of the expiration fields. */ static int print_shadow_info (const char *user, struct spwd *sp, bool iso8601) { if (sp == NULL) { fprintf(stderr, "ERROR: No shadow entry for user '%s' found.\n", user); return ENODATA; } printf ("Last password change:\t\t"); if (sp->sp_lstchg == 0) printf("password change enforced\n"); else if (sp->sp_lstchg < 0) printf("never\n"); else print_date(sp->sp_lstchg * SCALE, iso8601); printf("Password expires:\t\t"); if (sp->sp_lstchg < 0 || sp->sp_max >= 10000 * (DAY / SCALE) || sp->sp_max < 0) printf("never\n"); else print_date(sp->sp_lstchg * SCALE + sp->sp_max * SCALE, iso8601); printf("Password inactive:\t\t"); if (sp->sp_lstchg < 0 || sp->sp_inact < 0 || sp->sp_max >= 10000 * (DAY / SCALE) || sp->sp_max < 0) printf("never\n"); else print_date(sp->sp_lstchg * SCALE + (sp->sp_max + sp->sp_inact) * SCALE, iso8601); printf("Account expires:\t\t"); if (sp->sp_expire < 0) printf("never\n"); else print_date(sp->sp_expire * SCALE, iso8601); printf("Minimum password age:\t\t"); if (sp->sp_min <= 0) printf("disabled\n"); else printf("%ld days\n", sp->sp_min); printf("Maximum password age:\t\t"); if (sp->sp_max <= 0) printf("disabled\n"); else printf("%ld days\n", sp->sp_max); printf("Password warning period:\t"); if (sp->sp_warn <= 0) printf("disabled\n"); else printf("%ld days\n", sp->sp_warn); printf("Password inactivity period:\t"); if (sp->sp_inact < 0) printf("disabled\n"); else printf("%ld days\n", sp->sp_inact); return 0; } static int update_account(const struct passwd *pw, const struct spwd *sp) { _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *passwd = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *shadow = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; _cleanup_free_ char *error = NULL; _cleanup_(struct_result_free) struct result p = { .success = false, .error = NULL, }; static const sd_json_dispatch_field dispatch_table[] = { { "Success", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct result, success), 0 }, { "ErrorMsg", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct result, error), 0 }, {} }; const char *error_id = NULL; int r; r = connect_to_pwupdd(&link, _VARLINK_PWUPD_SOCKET, &error); if (r < 0) { if (error) fprintf(stderr, "%s\n", error); else fprintf(stderr, "Cannot connect to pwupd! (%s)\n", strerror(-r)); return -r; } if (pw) { r = sd_json_variant_merge_objectbo(&passwd, SD_JSON_BUILD_PAIR_STRING("name", pw->pw_name), SD_JSON_BUILD_PAIR_STRING("passwd", pw->pw_passwd), SD_JSON_BUILD_PAIR_INTEGER("UID", pw->pw_uid), SD_JSON_BUILD_PAIR_INTEGER("GID", pw->pw_gid), SD_JSON_BUILD_PAIR_STRING("GECOS", pw->pw_gecos), SD_JSON_BUILD_PAIR_STRING("dir", pw->pw_dir), SD_JSON_BUILD_PAIR_STRING("shell", pw->pw_shell)); if (r < 0) { fprintf(stderr, "Error building passwd data: %s\n", strerror(-r)); return -r; } } r = sd_json_variant_merge_objectbo(&shadow, SD_JSON_BUILD_PAIR_STRING("name", sp->sp_namp), SD_JSON_BUILD_PAIR_STRING("passwd", sp->sp_pwdp), SD_JSON_BUILD_PAIR_INTEGER("lstchg", sp->sp_lstchg), SD_JSON_BUILD_PAIR_INTEGER("min", sp->sp_min), SD_JSON_BUILD_PAIR_INTEGER("max", sp->sp_max), SD_JSON_BUILD_PAIR_INTEGER("warn", sp->sp_warn), SD_JSON_BUILD_PAIR_INTEGER("inact", sp->sp_inact), SD_JSON_BUILD_PAIR_INTEGER("expire", sp->sp_expire), SD_JSON_BUILD_PAIR_INTEGER("flag", sp->sp_flag)); if (r < 0) { fprintf(stderr, "Error building shadow data: %s\n", strerror(-r)); return -r; } r = sd_json_variant_merge_objectbo(¶ms, SD_JSON_BUILD_PAIR_VARIANT("shadow", shadow)); if (r >= 0 && passwd) r = sd_json_variant_merge_objectbo(¶ms, SD_JSON_BUILD_PAIR_VARIANT("passwd", passwd)); if (r < 0) { fprintf(stderr, "JSON merge result object failed: %s", strerror(-r)); return -r; } r = sd_varlink_call(link, "org.openSUSE.pwupd.UpdatePasswdShadow", params, &result, &error_id); if (r < 0) { fprintf(stderr, "Failed to call UpdatePasswdShadow method: %s\n", strerror(-r)); return r; } /* dispatch before checking error_id, we may need the result for the error message */ r = sd_json_dispatch(result, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); if (r < 0) { fprintf(stderr, "Failed to parse JSON answer: %s\n", strerror(-r)); return r; } if (error_id && strlen(error_id) > 0) { if (p.error) fprintf(stderr, "Error updating account information:\n%s\n", p.error); else fprintf(stderr, "Error updating account information:\n%s\n", error_id); return -EIO; } printf("Account information changed.\n"); return 0; } static void print_usage(FILE *stream) { fprintf(stream, "Usage: chage [options] [--help] [--version] [user]\n"); } static void print_help(void) { fprintf(stdout, "chage - change and list user expiry data\n\n"); print_usage(stdout); fputs(" -d, --lastday Set date of last password change\n", stdout); fputs(" -E, --expiredate Date on which user's password expires\n", stdout); fputs(" -i, --iso8601 Print dates as YYYY-MM-DD\n", stdout); fputs(" -I, --inactive Lock expired account after inactive days\n", stdout); fputs(" -l, --list List account aging information\n", stdout); fputs(" -m, --mindays Minimum # of days before password can be changed\n", stdout); fputs(" -M, --maxdays Maximum # of days before password can be canged\n", stdout); fputs(" -h, --help Give this help list\n", stdout); fputs(" -v, --version Print program version\n", stdout); fputs(" -W, --warndays # days of warning before password expires\n", stdout); fputs(" must be in the form of \"YYYY-MM-DD\"\n", stdout); } static void print_error(void) { fprintf(stderr, "Try `chage --help' for more information.\n"); } int main(int argc, char **argv) { _cleanup_(struct_passwd_freep) struct passwd *pw = NULL; _cleanup_(struct_shadow_freep) struct spwd *sp = NULL; _cleanup_free_ char *error = NULL; bool complete = false; char *user = NULL; char *expiredate = NULL; char *inactive = NULL; char *lastday = NULL; char *maxdays = NULL; char *mindays = NULL; char *warndays = NULL; int i_flag = 0; int l_flag = 0; int r; setlocale(LC_ALL, ""); while (1) { int c; int option_index = 0; static struct option long_options[] = { {"expiredate", required_argument, NULL, 'E' }, {"help", no_argument, NULL, 'h' }, {"inactive", required_argument, NULL, 'I' }, {"iso8601", no_argument, NULL, 'i' }, {"lastday", required_argument, NULL, 'd' }, {"list", no_argument, NULL, 'l' }, {"maxdays", required_argument, NULL, 'M' }, {"mindays", required_argument, NULL, 'm' }, {"version", no_argument, NULL, 'v' }, {"warndays", required_argument, NULL, 'W' }, {NULL, 0, NULL, '\0'} }; c = getopt_long(argc, argv, "E:hI:id:lM:m:vW:", long_options, &option_index); if (c == (-1)) break; switch (c) { case 'E': expiredate = optarg; break; case 'I': inactive = optarg; break; case 'i': i_flag = 1; break; case 'd': lastday = optarg; break; case 'M': maxdays = optarg; break; case 'm': mindays = optarg; break; case 'W': warndays = optarg; break; case 'l': l_flag = 1; break; case 'h': print_help(); return 0; case 'v': printf("chage (%s) %s\n", PACKAGE, VERSION); return 0; default: print_error(); return EINVAL; } } argc -= optind; argv += optind; if (argc == 1) user = argv[0]; if (argc > 1) { fprintf(stderr, "chage: Too many arguments.\n"); print_error(); return EINVAL; } if (l_flag && (expiredate || inactive || lastday || maxdays || mindays || warndays)) { fprintf(stderr, "The --list option cannot be combined with other options.\n"); print_error(); return EINVAL; } r = check_and_drop_privs(); if (r < 0) return -r; /* get user account data */ r = pwaccess_get_user_record(user?-1:(int64_t)getuid(), user?user:NULL, &pw, &sp, &complete, &error); if (r < 0) { fprintf(stderr, "get_user_record failed: %s\n", error?error:strerror(-r)); return -r; } if (pw == NULL) { fprintf(stderr, "ERROR: Unknown user '%s'.\n", user); return ENODATA; } if (!complete) { fprintf(stderr, "Permission denied.\n"); return EPERM; } if (!user) user = pw->pw_name; /* execute options */ if (l_flag) return print_shadow_info(user, sp, i_flag); if (getuid() != 0) { fprintf(stderr, "Permission denied.\n"); return EPERM; } /* create default shadow entry if there is none */ bool pw_changed = false; char *ep; if (!sp) { sp = calloc(1, sizeof(struct spwd)); if (!sp) return oom(); sp->sp_namp = strdup(pw->pw_name); if (!sp->sp_namp) return oom(); sp->sp_pwdp = pw->pw_passwd; pw->pw_passwd = strdup("x"); if (!pw->pw_passwd) return oom(); pw_changed = true; sp->sp_lstchg = time(NULL) / DAY; /* disable instead of requesting password change */ if (!sp->sp_lstchg) sp->sp_lstchg = -1; sp->sp_min = get_logindefs_num("PASS_MIN_DAYS", -1); sp->sp_max = get_logindefs_num("PASS_MAX_DAYS", -1); sp->sp_warn = get_logindefs_num("PASS_WARN_AGE", -1); sp->sp_inact = -1; sp->sp_expire = -1; } /* Use user provided values */ if (!(expiredate || inactive || lastday || maxdays || mindays || warndays)) { char buf[80]; snprintf(buf, sizeof(buf), "%ld", sp->sp_min); r = prompt_and_check(buf, "Minimum password age", &mindays); if (r != 0) return r; snprintf(buf, sizeof(buf), "%ld", sp->sp_max); r = prompt_and_check(buf, "Maximum password age", &maxdays); if (r != 0) return r; format_date_buf(buf, sizeof(buf), sp->sp_lstchg); r = prompt_and_check(buf, "Last password change (YYYY-MM-DD)", &lastday); if (r != 0) return r; snprintf(buf, sizeof(buf), "%ld", sp->sp_warn); r = prompt_and_check(buf, "Password warning period", &warndays); if (r != 0) return r; snprintf(buf, sizeof(buf), "%ld", sp->sp_inact); r = prompt_and_check(buf, "Password inactivity period", &inactive); if (r != 0) return r; format_date_buf(buf, sizeof(buf), sp->sp_expire); r = prompt_and_check(buf, "Account expires (YYYY-MM-DD)", &expiredate); if (r != 0) return r; } /* values are provided as option or asked for */ if (mindays) { long l; errno = 0; l = strtol(mindays, &ep, 10); if (errno == ERANGE || l < -1 || mindays == ep || *ep != '\0') { fprintf(stderr, "Cannot parse 'mindays=%s'\n", mindays); return EINVAL; } sp->sp_min = l; } if (maxdays) { long l; errno = 0; l = strtol(maxdays, &ep, 10); if (errno == ERANGE || l < -1 || maxdays == ep || *ep != '\0') { fprintf(stderr, "Cannot parse 'maxdays=%s'\n", maxdays); return EINVAL; } sp->sp_max = l; } if (warndays) { long l; errno = 0; l = strtol(warndays, &ep, 10); if (errno == ERANGE || l < -1 || warndays == ep || *ep != '\0') { fprintf(stderr, "Cannot parse 'warndays=%s'\n", warndays); return EINVAL; } sp->sp_warn = l; } if (inactive) { long l; errno = 0; l = strtol(inactive, &ep, 10); if (errno == ERANGE || l < -1 || inactive == ep || *ep != '\0') { fprintf(stderr, "Cannot parse 'inactive=%s'\n", inactive); return EINVAL; } sp->sp_inact = l; } if (lastday) { if (streq(lastday, "1969-12-31")) sp->sp_lstchg = -1; else { sp->sp_lstchg = str2date(lastday); if (sp->sp_lstchg == -1) { long l; errno = 0; l = strtol(lastday, &ep, 10); if (errno == ERANGE || l < -1 || lastday == ep || *ep != '\0') { fprintf(stderr, "Cannot parse 'lastday=%s'\n", lastday); return EINVAL; } sp->sp_lstchg = l; } } } if (expiredate) { if (streq(expiredate, "1969-12-31")) sp->sp_expire = -1; else { sp->sp_expire = str2date(expiredate); if (sp->sp_expire == -1) { long l; errno = 0; l = strtol(expiredate, &ep, 10); if (errno == ERANGE || l < -1 || expiredate == ep || *ep != '\0') { fprintf(stderr, "Cannot parse 'expiredate=%s'\n", expiredate); return EINVAL; } sp->sp_expire = l; } } } return update_account(pw_changed?pw:NULL, sp); } account-utils-1.0.1/src/chfn.c000066400000000000000000000220601513410760700161600ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include "config.h" #include #include #include #include #include #include #include #include #include #include "basics.h" #include "pwaccess.h" #include "varlink-client-common.h" #include "get_value.h" #include "chfn_checks.h" #include "drop_privs.h" #define USEC_INFINITY ((uint64_t) UINT64_MAX) static int ask_or_print(const char *old, const char *prompt, char **input, char field) { _cleanup_free_ char *error = NULL; bool allowed = true; int r; allowed = may_change_field(getuid(), field, &error); if (error) { fprintf(stderr, "%s\n", error); return -EPERM; } if (allowed) { r = get_value(old, prompt, input); if (r < 0) return r; if (*input == NULL) { fprintf(stderr, "chfn aborted.\n"); return -ENODATA; } /* don't change string if equal */ if (streq(strempty(old), *input)) *input = mfree(*input); else { /* field "other" allows ',' and '=' */ if (!chfn_check_string(*input, field=='o'?":":":,=", &error)) { *input = mfree(*input); if (error) fprintf(stderr, "%s: %s\n", prompt, error); return -EINVAL; } } } else printf("%s: '%s'\n", prompt, strempty(old)); return 0; } static void print_usage(FILE *stream) { fprintf(stream, "Usage: chfn [options] [user]\n"); } static void print_help(void) { fprintf(stdout, "chfn - change user information\n\n"); print_usage(stdout); fputs(" -f, --full-name Change full name\n", stdout); fputs(" -h, --home-phone Change home phone number\n", stdout); fputs(" -o, --other Change other GECOS information\n", stdout); fputs(" -r, --room Change room number\n", stdout); fputs(" -w, --work-phone Change work phone number\n", stdout); fputs(" -u, --help Give this help list\n", stdout); fputs(" -v, --version Print program version\n", stdout); } static void print_error(void) { fprintf (stderr, "Try `chfn --help' for more information.\n"); } int main(int argc, char **argv) { _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; _cleanup_free_ char *new_full_name = NULL; _cleanup_free_ char *new_home_phone = NULL; _cleanup_free_ char *new_other = NULL; _cleanup_free_ char *new_room = NULL; _cleanup_free_ char *new_work_phone = NULL; const char *old_full_name = NULL; const char *old_home_phone = NULL; const char *old_other = NULL; const char *old_room = NULL; const char *old_work_phone = NULL; const char *user = NULL; _cleanup_free_ char *error = NULL; struct pam_response *resp = NULL; int r; setlocale(LC_ALL, ""); while (1) { int c; int option_index = 0; static struct option long_options[] = { {"full-name", required_argument, NULL, 'f' }, {"home-phone", required_argument, NULL, 'h' }, {"other", required_argument, NULL, 'o' }, {"room", required_argument, NULL, 'r' }, {"work-phone", required_argument, NULL, 'w' }, {"version", no_argument, NULL, 'v' }, {"help", no_argument, NULL, 'u' }, {NULL, 0, NULL, '\0'} }; c = getopt_long (argc, argv, "f:h:o:r:uvw:", long_options, &option_index); if (c == (-1)) break; switch (c) { case 'f': new_full_name = strdup(optarg); if (new_full_name == NULL) return ENOMEM; break; case 'h': new_home_phone = strdup(optarg); if (new_home_phone == NULL) return ENOMEM; break; case 'o': new_other = strdup(optarg); if (new_other == NULL) return ENOMEM; break; case 'r': new_room = strdup(optarg); if (new_room == NULL) return ENOMEM; break; case 'w': new_work_phone = strdup(optarg); if (new_work_phone == NULL) return ENOMEM; break; case 'u': print_help(); return 0; case 'v': printf("chfn (%s) %s\n", PACKAGE, VERSION); return 0; default: print_error(); return 1; } } argc -= optind; argv += optind; if (argc > 1) { fprintf(stderr, "chfn: Too many arguments.\n"); print_error(); return 1; } r = check_and_drop_privs(); if (r < 0) return -r; if (argc == 1) user = argv[0]; else { _cleanup_free_ char *name = NULL; r = pwaccess_get_account_name(getuid(), &name, &error); if (r < 0) { fprintf(stderr, "Get account name failed: %s\n", error?error:strerror(-r)); return -r; } user = strdupa(name); if (user == NULL) { fprintf(stderr, "Out of memory!\n"); return ENOMEM; } } /* no new values as argument provided, ask for them */ if (!new_full_name && !new_home_phone && !new_other && !new_room && !new_work_phone) { char *p; const char *f; struct passwd *pw = getpwnam(user); if (pw == NULL) { fprintf(stderr, "User (%s) not found!\n", user); return ENODATA; } /* set old values */ p = pw->pw_gecos; f = strsep(&p, ","); old_full_name = f; f = strsep(&p, ","); old_room = f; f = strsep(&p, ","); old_work_phone = f; f = strsep(&p, ","); old_home_phone = f; /* Anything left over is "other". */ old_other = p; printf("Enter the new value, or press return for the default.\n"); r = ask_or_print(old_full_name, "Full Name", &new_full_name, 'f'); if (r < 0) return -r; r = ask_or_print(old_room, "Room Number", &new_room, 'r'); if (r < 0) return -r; r = ask_or_print(old_work_phone, "Work Phone", &new_work_phone, 'w'); if (r < 0) return -r; r = ask_or_print(old_home_phone, "Home Phone", &new_home_phone, 'h'); if (r < 0) return -r; r = ask_or_print(old_other, "Other", &new_other, 'o'); if (r < 0) return -r; } /* abort if there is nothing to change */ if (!new_full_name && !new_home_phone && !new_other && !new_room && !new_work_phone) { printf("Nothing to change.\n"); return 0; } r = connect_to_pwupdd(&link, _VARLINK_PWUPD_SOCKET, &error); if (r < 0) { if (error) fprintf(stderr, "%s\n", error); else fprintf(stderr, "Cannot connect to pwupd! (%s)\n", strerror(-r)); return -r; } sd_varlink_set_userdata(link, &resp); r = sd_json_variant_merge_objectbo(¶ms, SD_JSON_BUILD_PAIR_STRING("userName", user)); if (r >= 0 && new_full_name) r = sd_json_variant_merge_objectbo(¶ms, SD_JSON_BUILD_PAIR_STRING("fullName", new_full_name)); if (r >= 0 && new_room) r = sd_json_variant_merge_objectbo(¶ms, SD_JSON_BUILD_PAIR_STRING("room", new_room)); if (r >= 0 && new_work_phone) r = sd_json_variant_merge_objectbo(¶ms, SD_JSON_BUILD_PAIR_STRING("workPhone", new_work_phone)); if (r >= 0 && new_home_phone) r = sd_json_variant_merge_objectbo(¶ms, SD_JSON_BUILD_PAIR_STRING("homePhone", new_home_phone)); if (r >= 0 && new_other) r = sd_json_variant_merge_objectbo(¶ms, SD_JSON_BUILD_PAIR_STRING("other", new_other)); if (r < 0) { fprintf(stderr, "Failed to build param list: %s\n", strerror(-r)); return -r; } r = sd_varlink_bind_reply(link, reply_callback); if (r < 0) { fprintf(stderr, "Failed to bind reply callback: %s\n", strerror(-r)); return -r; } r = sd_varlink_observe(link, "org.openSUSE.pwupd.Chfn", params); if (r < 0) { fprintf(stderr, "Failed to call chfn method: %s\n", strerror(-r)); return -r; } loop: for (;;) { r = sd_varlink_is_idle(link); if (r < 0) { fprintf(stderr, "Failed to check if varlink connection is idle: %s\n", strerror(-r)); return -r; } if (r > 0) break; r = sd_varlink_process(link); if (r < 0) { fprintf(stderr, "Failed to process varlink connection: %s\n", strerror(-r)); return -r; } if (r != 0) continue; r = sd_varlink_wait(link, USEC_INFINITY); if (r < 0) { fprintf(stderr, "Failed to wait for varlink connection events: %s\n", strerror(-r)); return -r; } } if (resp) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *answer = NULL; r = sd_json_buildo(&answer, SD_JSON_BUILD_PAIR("response", SD_JSON_BUILD_STRING(strempty(resp->resp)))); if (r < 0) { fprintf(stderr, "Failed to build response list: %s\n", strerror(-r)); return -r; } free(resp->resp); resp = mfree(resp); sd_json_variant_sensitive(answer); /* password is sensitive */ r = sd_varlink_observe(link, "org.openSUSE.pwupd.Conv", answer); if (r < 0) { fprintf(stderr, "Failed to call conv method: %s\n", strerror(-r)); return -r; } goto loop; } return 0; } account-utils-1.0.1/src/chsh.c000066400000000000000000000151051513410760700161710ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include "config.h" #include #include #include #include #include #include #include #include #include #include "basics.h" #include "pwaccess.h" #include "varlink-client-common.h" #include "get_value.h" #include "drop_privs.h" #define USEC_INFINITY ((uint64_t) UINT64_MAX) static int get_shell_list(void) { _cleanup_(econf_freeFilep) econf_file *key_file = NULL; _cleanup_(econf_freeArrayp) char **keys = NULL; size_t size = 0; econf_err error; error = econf_readConfig(&key_file, NULL /* project */, _PATH_VENDORDIR /* usr_conf_dir */, "shells" /* config_name */, NULL /* config_suffix */, "" /* delim, key only */, "#" /* comment */); if (error != ECONF_SUCCESS) { fprintf(stderr, "Cannot parse shell files: %s", econf_errString(error)); return 1; } error = econf_getKeys(key_file, NULL, &size, &keys); if (error) { fprintf(stderr, "Cannot evaluate entries in shell files: %s", econf_errString(error)); return 1; } for (size_t i = 0; i < size; i++) printf("%s\n", keys[i]); return 0; } static void print_usage(FILE *stream) { fprintf(stream, "Usage: chsh [-s shell] [-l] [--help] [--version] [user]\n"); } static void print_help(void) { fprintf(stdout, "chsh - change login shell\n\n"); print_usage(stdout); fputs(" -l, --list-shells List allowed shells from /etc/shells\n", stdout); fputs(" -s, --shell Use 'shell' as new login shell\n", stdout); fputs(" -h, --help Give this help list\n", stdout); fputs(" -v, --version Print program version\n", stdout); } static void print_error(void) { fprintf (stderr, "Try `chsh --help' for more information.\n"); } int main(int argc, char **argv) { struct pam_response *resp = NULL; char *new_shell = NULL; int l_flag = 0; int r; setlocale(LC_ALL, ""); while (1) { int c; int option_index = 0; static struct option long_options[] = { {"shell", required_argument, NULL, 's' }, {"list-shells", no_argument, NULL, 'l' }, {"version", no_argument, NULL, 'v' }, {"help", no_argument, NULL, 'h' }, {NULL, 0, NULL, '\0'} }; c = getopt_long (argc, argv, "s:lvh", long_options, &option_index); if (c == (-1)) break; switch (c) { case 'l': l_flag = 1; break; case 's': if (!optarg) { print_usage(stderr); return 1; } new_shell = optarg; break; case 'h': print_help(); return 0; case 'v': printf("chsh (%s) %s\n", PACKAGE, VERSION); return 0; default: print_error(); return 1; } } argc -= optind; argv += optind; if (argc > 1 || (l_flag && argc > 0)) { fprintf(stderr, "chsh: Too many arguments.\n"); print_error(); return 1; } if (l_flag && new_shell) { fprintf(stderr, "chsh: Too many arguments.\n"); print_error(); return 1; } r = check_and_drop_privs(); if (r < 0) return -r; if (l_flag) return get_shell_list(); else { _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; _cleanup_(struct_passwd_freep) struct passwd *pw = NULL; _cleanup_free_ char *error = NULL; const char *user = NULL; const char *old_shell = NULL; if (argc == 1) r = pwaccess_get_user_record(-1, argv[0], &pw, NULL, NULL, &error); else r = pwaccess_get_user_record(getuid(), NULL, &pw, NULL, NULL, &error); if (r < 0) { fprintf (stderr, "get_user_record failed: %s\n", error?error:strerror(-r)); return 1; } user = pw->pw_name; old_shell = pw->pw_shell; if (new_shell == NULL) { printf("Enter the new value, or press return for the default.\n"); r = get_value(old_shell, "Login Shell", &new_shell); if (r < 0) return -r; } /* we don't need to change the shell if here is no change */ if (new_shell == NULL || streq(old_shell, new_shell)) { printf("Shell not changed.\n"); return 0; } r = connect_to_pwupdd(&link, _VARLINK_PWUPD_SOCKET, &error); if (r < 0) { if (error) fprintf(stderr, "%s\n", error); else fprintf(stderr, "Cannot connect to pwupd! (%s)\n", strerror(-r)); return -r; } sd_varlink_set_userdata(link, &resp); r = sd_json_buildo(¶ms, SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(user)), SD_JSON_BUILD_PAIR("shell", SD_JSON_BUILD_STRING(strempty(new_shell)))); if (r < 0) { fprintf(stderr, "Failed to build param list: %s\n", strerror(-r)); return -r; } r = sd_varlink_bind_reply(link, reply_callback); if (r < 0) { fprintf(stderr, "Failed to bind reply callback: %s\n", strerror(-r)); return -r; } r = sd_varlink_observe(link, "org.openSUSE.pwupd.Chsh", params); if (r < 0) { fprintf(stderr, "Failed to call chsh method: %s\n", strerror(-r)); return -r; } loop: for (;;) { r = sd_varlink_is_idle(link); if (r < 0) { fprintf(stderr, "Failed to check if varlink connection is idle: %s\n", strerror(-r)); return -r; } if (r > 0) break; r = sd_varlink_process(link); if (r < 0) { fprintf(stderr, "Failed to process varlink connection: %s\n", strerror(-r)); return -r; } if (r != 0) continue; r = sd_varlink_wait(link, USEC_INFINITY); if (r < 0) { fprintf(stderr, "Failed to wait for varlink connection events: %s\n", strerror(-r)); return -r; } } if (resp) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *answer = NULL; r = sd_json_buildo(&answer, SD_JSON_BUILD_PAIR("response", SD_JSON_BUILD_STRING(strempty(resp->resp)))); if (r < 0) { fprintf(stderr, "Failed to build response list: %s\n", strerror(-r)); return -r; } free(resp->resp); resp = mfree(resp); sd_json_variant_sensitive(answer); /* password is sensitive */ r = sd_varlink_observe(link, "org.openSUSE.pwupd.Conv", answer); if (r < 0) { fprintf(stderr, "Failed to call conv method: %s\n", strerror(-r)); return -r; } goto loop; } } return 0; } account-utils-1.0.1/src/expiry.c000066400000000000000000000075021513410760700165660ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include "config.h" #include #include #include #include #include #include "basics.h" #include "pwaccess.h" #include "chauthtok.h" #include "drop_privs.h" static void print_usage(FILE *stream) { fprintf(stream, "Usage: expiry [-c|-f] [user] [--help] [--version]\n"); } static void print_help(void) { fprintf(stdout, "expiry - check password expiration and force password change\n\n"); print_usage(stdout); fputs(" -c, --check Print number of days when password expires\n", stdout); fputs(" -f, --force Force password change if password is expired\n", stdout); fputs(" -h, --help Give this help list\n", stdout); fputs(" -v, --version Print program version\n", stdout); } static void print_error(void) { fprintf (stderr, "Try `expiry --help' for more information.\n"); } int main(int argc, char **argv) { _cleanup_free_ char *error = NULL; _cleanup_free_ char *user = NULL; long daysleft = -1; int cflg = 0; int fflg = 0; int r; setlocale(LC_ALL, ""); while (1) { int c; int option_index = 0; static struct option long_options[] = { {"check", no_argument, NULL, 'c' }, {"force", no_argument, NULL, 'f' }, {"help", no_argument, NULL, 'h' }, {"version", no_argument, NULL, 'v' }, {NULL, 0, NULL, '\0'} }; c = getopt_long (argc, argv, "cfhv", long_options, &option_index); if (c == (-1)) break; switch (c) { case 'c': cflg = 1; break; case 'f': fflg = 1; break; case 'h': print_help(); return 0; case 'v': printf("expiry (%s) %s\n", PACKAGE, VERSION); return 0; default: print_error(); return 1; } } argc -= optind; argv += optind; if (argc > 1) { fprintf(stderr, "expiry: too many arguments.\n"); print_error(); return EINVAL; } if (cflg+fflg > 1) { fprintf(stderr, "expiry: options -c and -f conflict.\n"); print_error(); return EINVAL; } r = check_and_drop_privs(); if (r < 0) return -r; /* common for -c and -f */ if (argc == 1) { user = strdup(argv[0]); if (!user) { fprintf(stderr, "Out of memory!\n"); return ENOMEM; } } else { r = pwaccess_get_account_name(getuid(), &user, &error); if (r < 0) { fprintf(stderr, "Get account name failed: %s\n", error?error:strerror(-r)); return -r; } } r = pwaccess_check_expired(user, &daysleft, NULL /* pwchangeable */, &error); if (r < 0) { fprintf(stderr, "Calling pwaccess check expired failed: %s\n", error?error:strerror(-r)); return -r; } if (cflg) { if (daysleft >= 0) printf("Your password will expire in %ld %s.\n", daysleft, (daysleft == 1)?"day":"days"); /* return expire status as return value */ return r; } else if (fflg) { switch (r) { case PWA_EXPIRED_NO: return 0; break; case PWA_EXPIRED_ACCT: printf("Your account has expired; please contact your system administrator.\n"); return EPERM; break; case PWA_EXPIRED_CHANGE_PW: printf("Your password has expired.\n"); break; case PWA_EXPIRED_PW: printf("Your password is inactive; please contact your system administrator.\n"); return EPERM; break; default: fprintf(stderr, "Unexpected expire value: %i\n", r); return EINVAL; break; } return chauthtok(user, PAM_CHANGE_EXPIRED_AUTHTOK); } else { fprintf(stderr, "expiry: no arguments provided.\n"); print_error(); return 1; } return 0; } account-utils-1.0.1/src/map_range.c000066400000000000000000000003041513410760700171700ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include "basics.h" #include "map_range.h" void map_range_freep(struct map_range **var) { if (!var || !*var) return; *var = mfree(*var); } account-utils-1.0.1/src/map_range.h000066400000000000000000000005071513410760700172020ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include struct map_range { int64_t upper; /* first ID inside the namespace */ int64_t lower; /* first ID outside the namespace */ int64_t count; /* Length of the inside and outside ranges */ }; extern void map_range_freep(struct map_range **var); account-utils-1.0.1/src/newidmapd.c000066400000000000000000000437321513410760700172230ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include "config.h" #include #include #include #include #include #include #include "basics.h" #include "mkdir_p.h" #include "varlink-service-common.h" #include "pwaccess.h" #include "map_range.h" #include "varlink-org.openSUSE.newidmapd.h" #define USEC_PER_SEC ((uint64_t) 1000000ULL) /* event loop which quits after 30 seconds idle time */ #define DEFAULT_EXIT_USEC (30*USEC_PER_SEC) #define UID_MAX ((uid_t)-1) struct parameters { pid_t pid; char *map; /* see varlink interface definition for valid names */ int nranges; struct map_range *mappings; sd_json_variant *content_map_ranges; }; static void parameters_free(struct parameters *var) { var->map = mfree(var->map); var->nranges = 0; map_range_freep(&(var->mappings)); var->content_map_ranges = sd_json_variant_unref(var->content_map_ranges); } static int open_pidfd(pid_t pid) { _cleanup_free_ char *proc_dir = NULL; int proc_dir_fd; if (asprintf(&proc_dir, "/proc/%u/", pid) == -1) { log_msg(LOG_ERR, "Out of memory!"); return -ENOMEM; } proc_dir_fd = open(proc_dir, O_DIRECTORY); if (proc_dir_fd < 0) { log_msg(LOG_ERR, "Open proc directory (%s) failed: %m\n", proc_dir); return -errno; } return proc_dir_fd; } static bool verify_range(uid_t uid, int64_t start, int64_t count, const struct map_range mapping) { if (mapping.count == 0) return false; /* Allow a process to map its own uid */ if ((mapping.count == 1) && (uid == mapping.lower)) return true; /* first ID outside namespace must be between start and start+count */ if (mapping.lower < start || mapping.lower >= start+count) return false; /* last ID outside must be smaller than start+count. -1 because lower is already the first ID. */ if ((mapping.lower+mapping.count-1) < (start+count)) return true; return false; } /* result < 0: error (-errno) 0 : range is valid 1 : range is invalid */ static int verify_ranges(uid_t uid, int nranges, const struct map_range *mappings, const char *map) { const char *subid_file = NULL; _cleanup_(econf_freeFilep) econf_file *econf = NULL; econf_err error; char *user; _cleanup_free_ char *pwerror = NULL; _cleanup_free_ char *val = NULL; long start, count; int r; r = pwaccess_get_account_name(uid, &user, &pwerror); if (r < 0) { log_msg(LOG_ERR, "Cannot get account data for uid '%u': %s", uid, pwerror?pwerror:strerror(-r)); return r; } if (streq(map, "uid_map")) subid_file = "/etc/subuid"; else if (streq(map, "gid_map")) subid_file = "/etc/subgid"; else { log_msg(LOG_ERR, "Unknown map name: '%s'", map); return -EINVAL; } error = econf_readFile(&econf, subid_file, ":", "#"); if (error != ECONF_SUCCESS) { log_msg(LOG_ERR, "Cannot open %s: %s", subid_file, econf_errString(error)); if (error == ECONF_NOFILE) return -ENOENT; else return -EIO; } error = econf_getStringValue(econf, NULL, user, &val); if (error != ECONF_SUCCESS) { if (error == ECONF_NOKEY) log_msg(LOG_ERR, "Mapping range for user '%s' not found in %s", user, subid_file); else log_msg(LOG_ERR, "Error retrieving key '%s': %s", user, econf_errString(error)); return -ENODATA; } char *cp = strchr(val, ':'); if (cp == NULL) { log_msg(LOG_ERR, "Invalid format for user %s in %s: %s", user, subid_file, val); return -EINVAL; } *cp++='\0'; char *ep = NULL; errno = 0; start = strtol(val, &ep, 10); if (errno == ERANGE || start < -1 || start > UID_MAX || val == ep || *ep != '\0') { log_msg(LOG_ERR, "Cannot parse 'start' value (%s,%s,%s)", subid_file, user, val); return -EINVAL; } errno = 0; count = strtol(cp, &ep, 10); if (errno == ERANGE || count < -1 || count >= (UID_MAX - start) || cp == ep || *ep != '\0') { log_msg(LOG_ERR, "Cannot parse 'count' value (%s,%s,%s)", subid_file, user, cp); return -EINVAL; } log_msg(LOG_DEBUG, "%s: user=%s, start=%li, count=%li", subid_file, user, start, count); for (int i = 0; i < nranges; i++) { if (!verify_range(uid, start, count, mappings[i])) return 1; } return 0; } static int write_mapping(int proc_dir_fd, int nranges, const struct map_range *mappings, const char *map) { _cleanup_free_ char *res = NULL; _cleanup_close_ int fd = -EBADF; int r; res = strdup(""); if (res == NULL) { log_msg(LOG_ERR, "Out of memory!"); return -ENOMEM; } for (int i = 0; i < nranges; i++) { _cleanup_free_ char *old_res = res; if (asprintf(&res, "%s%lu %lu %lu\n", old_res, mappings[i].upper, mappings[i].lower, mappings[i].count) == -1) { log_msg(LOG_ERR, "Out of memory!"); return -ENOMEM; } } log_msg(LOG_DEBUG, "mapping string: '%s'", res); /* Write the mapping to the mapping file */ fd = openat(proc_dir_fd, map, O_WRONLY|O_NOFOLLOW|O_CLOEXEC); if (fd < 0) { r = -errno; log_msg(LOG_ERR, "Failed to open '%s': %s", map, strerror(-r)); return r; } if (write(fd, res, strlen(res)) == -1) { r = -errno; log_msg(LOG_ERR, "Failed to write to '%s': %s", map, strerror(-r)); return r; } if (close(fd) != 0 && errno != EINTR) { r = -errno; log_msg(LOG_ERR, "Failed to close '%s': %s", map, strerror(-r)); return r; } return 0; } static int vl_method_write_mappings(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t _unused_(flags), void _unused_(*userdata)) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; _cleanup_(parameters_free) struct parameters p = { .pid = 0, .map = NULL, .nranges = 0, .mappings = NULL, .content_map_ranges = NULL, }; static const sd_json_dispatch_field dispatch_table[] = { { "PID", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int, offsetof(struct parameters, pid), SD_JSON_MANDATORY}, { "Map", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct parameters, map), SD_JSON_NULLABLE}, { "MapRanges", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(struct parameters, content_map_ranges), SD_JSON_MANDATORY}, {} }; _cleanup_close_ int proc_dir_fd = -EBADF; uid_t peer_uid; gid_t peer_gid; int r; log_msg(LOG_INFO, "Varlink method \"WriteMappings\" called..."); r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); if (r < 0) { log_msg(LOG_ERR, "WriteMappings request: varlink dispatch failed: %s", strerror(-r)); return r; } if (isempty(p.map)) { log_msg(LOG_ERR, "No map name provided."); return sd_varlink_errorbo(link, "org.openSUSE.newidmapd.InvalidParameter", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "No 'Map' entry provided.")); } if (!streq(p.map, "uid_map") && !streq(p.map, "gid_map")) { log_msg(LOG_ERR, "Map name is neither 'uid_map' nor 'gid_map'."); return sd_varlink_errorbo(link, "org.openSUSE.newidmapd.InvalidParameter", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "Unknown map name provided.")); } if (!sd_json_variant_is_array(p.content_map_ranges)) { fprintf(stderr, "JSON 'MapRanges' is no array!\n"); return -EINVAL; } size_t nranges = sd_json_variant_elements(p.content_map_ranges); /* 340 entries is the kernel limit since 4.16 */ if (nranges > 340) { log_msg(LOG_ERR, "Too many MapRanges entries: %i, limit is 340", p.nranges); return sd_varlink_errorbo(link, "org.openSUSE.newidmapd.InvalidParameter", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "Entry 'MapRanges' has too many entries (>340)")); } p.nranges = nranges; p.mappings = calloc(p.nranges, sizeof(struct map_range)); if (p.mappings == NULL) { log_msg(LOG_ERR, "Out of memory!"); return -ENOMEM; } for (int i = 0; i < p.nranges; i++) { struct map_range e = { .upper = -1, .lower = -1, .count = -1, }; static const sd_json_dispatch_field dispatch_entry_table[] = { { "upper", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_int64, offsetof(struct map_range, upper), SD_JSON_MANDATORY }, { "lower", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_int64, offsetof(struct map_range, lower), SD_JSON_MANDATORY }, { "count", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_int64, offsetof(struct map_range, count), SD_JSON_MANDATORY }, {} }; sd_json_variant *entry = sd_json_variant_by_index(p.content_map_ranges, i); if (!sd_json_variant_is_object(entry)) { log_msg(LOG_ERR, "entry is no object!"); return sd_varlink_errorbo(link, "org.openSUSE.newidmapd.InvalidParameter", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "Entry 'MapRanges' is no object")); } r = sd_json_dispatch(entry, dispatch_entry_table, SD_JSON_ALLOW_EXTENSIONS, &e); if (r < 0) { log_msg(LOG_ERR, "Failed to parse JSON map_ranges entry: %s", strerror(-r)); return sd_varlink_errorbo(link, "org.openSUSE.newidmapd.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "Failed to parse MapRanges object")); } if (e.upper < 0 || e.upper > UID_MAX || e.lower < 0 || e.lower > UID_MAX || e.count < 1 || e.count >= (UID_MAX - e.upper)) { log_msg(LOG_ERR, "Invalid map_ranges upper=%" PRIi64 ", lower=%" PRIi64 ", count=%" PRIi64, e.upper, e.lower, e.count); return sd_varlink_errorbo(link, "org.openSUSE.newidmapd.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "Failed to parse MapRanges object")); } log_msg(LOG_DEBUG, "map_ranges[%i] (%s): upper=%" PRIi64 ", lower=%" PRIi64 ", count=%" PRIi64, i, p.map, e.upper, e.lower, e.count); p.mappings[i] = e; } r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) { log_msg(LOG_ERR, "Failed to get peer UID: %s", strerror(-r)); return r; } r = sd_varlink_get_peer_gid(link, &peer_gid); if (r < 0) { log_msg(LOG_ERR, "Failed to get peer GID: %s", strerror(-r)); return r; } proc_dir_fd = open_pidfd(p.pid); if (proc_dir_fd < 0) return sd_varlink_errorbo(link, "org.openSUSE.newidmapd.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "Cannot open '/proc/'")); /* Get the effective uid and effective gid of the target process */ struct stat st; r = fstat(proc_dir_fd, &st); if (r < 0) { log_msg(LOG_ERR, "Could not stat proc directory: %s", strerror(-r)); return sd_varlink_errorbo(link, "org.openSUSE.newidmapd.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "Cannot access '/proc/'")); } if (st.st_uid != peer_uid || st.st_gid != peer_gid) { log_msg(LOG_ERR, "PID %i is owned by a different user: peer_uid=%u st_uid=%u peer_gid=%u st_gid=%u", p.pid, peer_uid, st.st_uid, peer_gid, st.st_gid); return sd_varlink_errorbo(link, "org.openSUSE.newidmapd.PermissionDenied", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "PID is owned by a different user")); } r = verify_ranges(peer_uid, p.nranges, p.mappings, p.map); if (r < 0) { return sd_varlink_errorbo(link, "org.openSUSE.newidmapd.InvalidParameter", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "Mapping ranges are not correct")); } if (r > 0) { return sd_varlink_errorbo(link, "org.openSUSE.newidmapd.PermissionDenied", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "Mapping ranges are not correct")); } r = write_mapping(proc_dir_fd, p.nranges, p.mappings, p.map); if (r < 0) { return sd_varlink_errorbo(link, "org.openSUSE.newidmapd.PermissionDenied", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "Cannot write to '/proc//'")); } return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Success", true)); } /* Send a messages to systemd daemon, that inicialization of daemon is finished and daemon is ready to accept connections. */ static void announce_ready(void) { int r = sd_notify(0, "READY=1\n" "STATUS=Processing requests..."); if (r < 0) log_msg(LOG_ERR, "sd_notify(READY) failed: %s", strerror(-r)); } static void announce_stopping(void) { int r = sd_notify(0, "STOPPING=1\n" "STATUS=Shutting down..."); if (r < 0) log_msg(LOG_ERR, "sd_notify(STOPPING) failed: %s", strerror(-r)); } static int varlink_event_loop_with_idle(sd_event *e, sd_varlink_server *s) { int r, code; for (;;) { r = sd_event_get_state(e); if (r < 0) return r; if (r == SD_EVENT_FINISHED) break; r = sd_event_run(e, DEFAULT_EXIT_USEC); if (r < 0) return r; if (r == 0 && (sd_varlink_server_current_connections(s) == 0)) sd_event_exit(e, 0); } r = sd_event_get_exit_code(e, &code); if (r < 0) return r; return code; } static int run_varlink(bool socket_activation) { int r; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; r = mkdir_p(_VARLINK_NEWIDMAPD_SOCKET_DIR, 0755); if (r < 0) { log_msg(LOG_ERR, "Failed to create directory '"_VARLINK_NEWIDMAPD_SOCKET_DIR"' for Varlink socket: %s", strerror(-r)); return r; } r = sd_event_new(&event); if (r < 0) { log_msg(LOG_ERR, "Failed to create new event: %s", strerror(-r)); return r; } r = sd_varlink_server_new (&varlink_server, SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA|SD_VARLINK_SERVER_INPUT_SENSITIVE); if (r < 0) { log_msg(LOG_ERR, "Failed to allocate varlink server: %s", strerror(-r)); return r; } r = sd_varlink_server_set_description (varlink_server, "newidmapd"); if (r < 0) { log_msg(LOG_ERR, "Failed to set varlink server description: %s", strerror(-r)); return r; } r = sd_varlink_server_set_info(varlink_server, NULL, PACKAGE" (newidmapd)", VERSION, "https://github.com/thkukuk/newidmapd"); if (r < 0) return r; r = sd_varlink_server_add_interface(varlink_server, &vl_interface_org_openSUSE_newidmapd); if (r < 0) { log_msg(LOG_ERR, "Failed to add interface: %s", strerror(-r)); return r; } r = sd_varlink_server_bind_method_many(varlink_server, "org.openSUSE.newidmapd.WriteMappings", vl_method_write_mappings, "org.openSUSE.newidmapd.GetEnvironment", vl_method_get_environment, "org.openSUSE.newidmapd.Ping", vl_method_ping, "org.openSUSE.newidmapd.Quit", vl_method_quit, "org.openSUSE.newidmapd.SetLogLevel", vl_method_set_log_level); if (r < 0) { log_msg(LOG_ERR, "Failed to bind Varlink methods: %s", strerror(-r)); return r; } sd_varlink_server_set_userdata(varlink_server, event); r = sd_varlink_server_attach_event(varlink_server, event, SD_EVENT_PRIORITY_NORMAL); if (r < 0) { log_msg(LOG_ERR, "Failed to attach to event: %s", strerror(-r)); return r; } r = sd_varlink_server_listen_auto(varlink_server); if (r < 0) { log_msg(LOG_ERR, "Failed to listen: %s", strerror(-r)); return r; } if (!socket_activation) { r = sd_varlink_server_listen_address(varlink_server, _VARLINK_NEWIDMAPD_SOCKET, 0666); if (r < 0) { log_msg(LOG_ERR, "Failed to bind to Varlink socket: %s", strerror(-r)); return r; } } announce_ready(); if (socket_activation) r = varlink_event_loop_with_idle(event, varlink_server); else r = sd_event_loop(event); announce_stopping(); return r; } static void print_help(void) { printf("newidmapd - manage passwd and shadow\n"); printf(" -s, --socket Activation through socket\n"); printf(" -d, --debug Debug mode\n"); printf(" -v, --verbose Verbose logging\n"); printf(" -?, --help Give this help list\n"); printf(" --version Print program version\n"); } int main(int argc, char **argv) { bool socket_activation = false; while (1) { int c; int option_index = 0; static struct option long_options[] = { {"socket", no_argument, NULL, 's'}, {"debug", no_argument, NULL, 'd'}, {"verbose", no_argument, NULL, 'v'}, {"version", no_argument, NULL, '\255'}, {"usage", no_argument, NULL, '?'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, '\0'} }; c = getopt_long(argc, argv, "sdvh?", long_options, &option_index); if (c == (-1)) break; switch (c) { case 's': socket_activation = true; break; case 'd': set_max_log_level(LOG_DEBUG); break; case '?': case 'h': print_help(); return 0; case 'v': set_max_log_level(LOG_INFO); break; case '\255': fprintf(stdout, "newidmapd (%s) %s\n", PACKAGE, VERSION); return 0; default: print_help(); return 1; } } argc -= optind; argv += optind; if (argc > 1) { fprintf(stderr, "Try `newidmapd --help' for more information.\n"); return 1; } log_msg(LOG_INFO, "Starting newidmapd (%s) %s...", PACKAGE, VERSION); int r = run_varlink(socket_activation); if (r < 0) { log_msg(LOG_ERR, "ERROR: varlink loop failed: %s", strerror(-r)); return -r; } log_msg(LOG_INFO, "newidmapd stopped."); return 0; } account-utils-1.0.1/src/newxidmap.c000066400000000000000000000147021513410760700172420ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include "config.h" #include #include #include #include #include "basics.h" #include "map_range.h" struct status { bool success; char *error; }; static void status_free (struct status *var) { var->error = mfree(var->error); } static int get_map_ranges(int ranges, char **argv, struct map_range **res) { _cleanup_(map_range_freep) struct map_range *mappings = NULL; char *ep; assert(res); *res = NULL; mappings = calloc(ranges, sizeof(struct map_range)); if (!mappings) { fprintf(stderr, "Out of memory!\n"); return -ENOMEM; } /* Gather up the ranges from the command line */ for (int i = 0; i < ranges; i++) { int j = i*3; errno = 0; mappings[i].upper = strtoul(argv[j], &ep, 10); if (errno == ERANGE || argv[j] == ep || *ep != '\0') { fprintf(stderr, "Cannot parse upper argument ('%s')\n", argv[j]); return EINVAL; } errno = 0; mappings[i].lower = strtoul(argv[j+1], &ep, 10); if (errno == ERANGE || argv[j+1] == ep || *ep != '\0') { fprintf(stderr, "Cannot parse lower argument ('%s')\n", argv[j+1]); return EINVAL; } errno = 0; mappings[i].count = strtoul(argv[j+2], &ep, 10); if (errno == ERANGE || argv[j] == ep || *ep != '\0') { fprintf(stderr, "Cannot parse count argument ('%s')\n", argv[j]); return EINVAL; } } *res = TAKE_PTR(mappings); return 0; } static int connect_to_newidmapd(sd_varlink **ret, const char *socket, char **error) { _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; int r; r = sd_varlink_connect_address(&link, socket); if (r < 0) { if (error) if (asprintf (error, "Failed to connect to %s: %s", socket, strerror(-r)) < 0) { error = NULL; r = -ENOMEM; } return r; } *ret = TAKE_PTR(link); return 0; } static void print_usage(FILE *stream) { fprintf(stream, "Usage: new"XID"map [|fd:] <"XID"> [ <"XID"> ] ... [--help] [--version]\n"); } static void print_help(void) { fprintf(stdout, "new"XID"map - set "XID" mapping of a user namespace\n\n"); print_usage(stdout); fputs(" -h, --help Give this help list\n", stdout); fputs(" -v, --version Print program version\n", stdout); } static void print_error(void) { fprintf (stderr, "Try `new"XID"map --help' for more information.\n"); } int main(int argc, char **argv) { _cleanup_(status_free) struct status p = { .success = false, .error = NULL, }; static const sd_json_dispatch_field dispatch_table[] = { { "Success", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct status, success), 0 }, { "ErrorMsg", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct status, error), 0 }, {} }; _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL; _cleanup_free_ char *error = NULL; sd_json_variant *result = NULL; const char *error_id = NULL; int ranges; _cleanup_(map_range_freep) struct map_range *mappings = NULL; pid_t arg_pid; char *ep; int r; while (1) { int c; int option_index = 0; static struct option long_options[] = { {"version", no_argument, NULL, 'v' }, {"help", no_argument, NULL, 'h' }, {NULL, 0, NULL, '\0'} }; c = getopt_long (argc, argv, "vh", long_options, &option_index); if (c == (-1)) break; switch (c) { case 'h': print_help(); return 0; case 'v': printf("new"XID"map (%s) %s\n", PACKAGE, VERSION); return 0; default: print_error(); return 1; } } argc -= optind; argv += optind; if (argc < 4) { fprintf(stderr, "new"XID"map: Not enough arguments.\n"); print_error(); return 1; } const char *pid_str = argv[0]; if (strlen(pid_str) > 3 && startswith(pid_str, "fd:")) { fprintf(stderr, "'fd:' as argument is currently not supported\n"); return EINVAL; } errno = 0; arg_pid = strtol(pid_str, &ep, 10); if (errno == ERANGE || arg_pid < -1 || pid_str == ep || *ep != '\0') { fprintf(stderr, "Cannot parse PID argument ('%s')\n", pid_str); return EINVAL; } ranges = (argc - 1) / 3; if ((ranges * 3) != (argc -1)) { fprintf(stderr, "Number of arguments is wrong (not a multiple of 3 + 1)!\n"); return EINVAL; } r = get_map_ranges(ranges, argv + 1, &mappings); if (r < 0) return -r; r = connect_to_newidmapd(&link, _VARLINK_NEWIDMAPD_SOCKET, &error); if (r < 0) { if (error) fprintf(stderr, "%s\n", error); else fprintf(stderr, "Cannot connect to newidmapd! (%s)\n", strerror(-r)); return -r; } for (int i = 0; i < ranges; i++) { r = sd_json_variant_append_arraybo(&array, SD_JSON_BUILD_PAIR_UNSIGNED("upper", mappings[i].upper), SD_JSON_BUILD_PAIR_UNSIGNED("lower", mappings[i].lower), SD_JSON_BUILD_PAIR_UNSIGNED("count", mappings[i].count)); if (r < 0) { fprintf(stderr, "Appending array failed: %s\n", strerror(-r)); return -r; } } r = sd_json_buildo(¶ms, SD_JSON_BUILD_PAIR_INTEGER("PID", arg_pid), SD_JSON_BUILD_PAIR_STRING("Map", XID"_map"), SD_JSON_BUILD_PAIR_VARIANT("MapRanges", array)); if (r < 0) { fprintf(stderr, "Failed to build param list: %s\n", strerror(-r)); return -r; } //sd_json_variant_dump(params, SD_JSON_FORMAT_NEWLINE, stdout, NULL); r = sd_varlink_call(link, "org.openSUSE.newidmapd.WriteMappings", params, &result, &error_id); if (r < 0) { fprintf(stderr, "Failed to call WriteMappings method: %s\n", strerror(-r)); return -r; } /* dispatch before checking error_id, we may need the result for the error message */ r = sd_json_dispatch(result, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); if (r < 0) { fprintf(stderr, "Failed to parse JSON answer: %s", strerror(-r)); return -r; } if (!isempty(error) || !isempty(error_id)) { fprintf(stderr, "%s\n", p.error?p.error:error_id); return EIO; } return 0; } account-utils-1.0.1/src/pam_debuginfo.c000066400000000000000000000105751513410760700200510ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause #include "config.h" #include #include #include #include #include #ifdef WITH_SELINUX #include #endif #include "basics.h" #include "no_new_privs.h" static void freeconp(char **p) { #ifdef WITH_SELINUX if (!p || !*p) return; freecon(*p); *p = NULL; #else (void)p; #endif } static const char * selinux_status(pam_handle_t *pamh) { #ifdef WITH_SELINUX if (is_selinux_enabled() > 0) { int r = security_getenforce(); switch (r) { case 1: return ", selinux=enforcing"; break; case 0: return ", selinux=permissive"; break; default: pam_syslog(pamh, LOG_ERR, "selinux error: %s", strerror(errno)); return ", selinux=error"; break; } } else return ", selinux=off"; #else (void)pamh; return ""; #endif } /* XXX add flags */ static void log_info(pam_handle_t *pamh, const char *type, int flags, int loglevel) { _cleanup_(freeconp) char *secon = NULL; const void *service = NULL; const void *user = NULL; const void *ruser = NULL; const void *rhost = NULL; const void *tty = NULL; const char *login_name; if (getcon(&secon) < 0) pam_syslog(pamh, LOG_ERR, "getcon() failed: %s", strerror(errno)); pam_get_item(pamh, PAM_SERVICE, &service); pam_get_item(pamh, PAM_USER, &user); pam_get_item(pamh, PAM_RUSER, &ruser); pam_get_item(pamh, PAM_RHOST, &rhost); pam_get_item(pamh, PAM_TTY, &tty); login_name = pam_modutil_getlogin(pamh); /* XXX split flags in single bits with defines */ pam_syslog(pamh, loglevel, "service=%s type=%s flags=%d " "logname=%s uid=%u euid=%u " "tty=%s ruser=%s rhost=%s " "user=%s%s%s%s%s", strna(service), type, flags, strna(login_name), getuid(), geteuid(), strna(tty), strna(ruser), strna(rhost), strna(user), no_new_privs_enabled()?", no_new_privs=1":"", selinux_status(pamh), secon?", context=":"", secon?secon:""); } static int parse_args(pam_handle_t *pamh, int flags _unused_, int argc, const char **argv, int *loglevel) { *loglevel = LOG_DEBUG; /* step through arguments */ for (; argc-- > 0; ++argv) { const char *cp; if ((cp = startswith(*argv, "loglevel=")) != NULL) { if (streq(cp, "debug")) *loglevel = LOG_DEBUG; else if (streq(cp, "info")) *loglevel = LOG_INFO; else if (streq(cp, "notice")) *loglevel = LOG_NOTICE; else if (streq(cp, "warning")) *loglevel = LOG_WARNING; else if (streq(cp, "error")) *loglevel = LOG_ERR; else if (streq(cp, "critical")) *loglevel = LOG_CRIT; else if (streq(cp, "alert")) *loglevel = LOG_ALERT; else if (streq(cp, "emerg")) *loglevel = LOG_EMERG; else pam_syslog(pamh, LOG_ERR, "Unknown loglevel value: %s", cp); } else pam_syslog(pamh, LOG_ERR, "Unknown option: %s", *argv); } return 0; } int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) { int loglevel; parse_args(pamh, flags, argc, argv, &loglevel); log_info(pamh, "account", flags, loglevel); return PAM_IGNORE; } int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { int loglevel; parse_args(pamh, flags, argc, argv, &loglevel); log_info(pamh, "auth", flags, loglevel); return PAM_IGNORE; } int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { int loglevel; parse_args(pamh, flags, argc, argv, &loglevel); log_info(pamh, "setcred", flags, loglevel); return PAM_IGNORE; } int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) { int loglevel; parse_args(pamh, flags, argc, argv, &loglevel); log_info(pamh, "password", flags, loglevel); return PAM_IGNORE; } int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { int loglevel; parse_args(pamh, flags, argc, argv, &loglevel); log_info(pamh, "session(open)", flags, loglevel); return PAM_IGNORE; } int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { int loglevel; parse_args(pamh, flags, argc, argv, &loglevel); log_info(pamh, "session(close)", flags, loglevel); return PAM_IGNORE; } account-utils-1.0.1/src/pam_unix_ng-acct.c000066400000000000000000000072531513410760700204650ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include "basics.h" #include "pam_unix_ng.h" #include "pwaccess.h" #include "verify.h" static int acct_mgmt(pam_handle_t *pamh, struct config_t *cfg) { const void *void_str; const char *user; _cleanup_free_ char *error = NULL; long daysleft = -1; int r; r = pam_get_item(pamh, PAM_USER, &void_str); if (r != PAM_SUCCESS || isempty(void_str)) { pam_syslog(pamh, LOG_ERR, "Unknown user"); return PAM_USER_UNKNOWN; } user = void_str; r = pwaccess_check_expired(user, &daysleft, NULL /* pwchangeable */, &error); if (r < 0) { if (r == -ENODATA) return PAM_USER_UNKNOWN; pam_syslog(pamh, LOG_ERR, "pwaccess expired failed: %s", error ? error : strerror(-r)); if (PWACCESS_IS_NOT_RUNNING(r)) { struct spwd spbuf; struct spwd *sp = NULL; _cleanup_free_ char *buf = NULL; long bufsize = 0; if (!(cfg->ctrl & ARG_QUIET)) pam_syslog(pamh, LOG_NOTICE, "pwaccessd not running, using internal fallback code"); r = alloc_getxxnam_buffer(pamh, &buf, &bufsize); if (r != PAM_SUCCESS) return r; r = getspnam_r(user, &spbuf, buf, bufsize, &sp); if (sp == NULL) { if (r != 0) { pam_syslog(pamh, LOG_WARNING, "getspnam_r(): %s", strerror(r)); pam_error(pamh, "getspnam_r(): %s", strerror(r)); return PAM_SYSTEM_ERR; } else r = PWA_EXPIRED_NO; } else r = expired_check(sp, &daysleft, NULL /* pwchangeable */); } else return PAM_SYSTEM_ERR; } int retval = PAM_SUCCESS; switch ((pwa_expire_flag_t)r) { case PWA_EXPIRED_NO: break; case PWA_EXPIRED_ACCT: pam_syslog(pamh, LOG_NOTICE, "account %s has expired (account expired)", user); pam_error(pamh, "Your account has expired; please contact your system administrator."); retval = PAM_ACCT_EXPIRED; break; case PWA_EXPIRED_CHANGE_PW: if (daysleft == 0) { pam_syslog(pamh, LOG_NOTICE, "expired password for user %s (admin enforced)", user); pam_error(pamh, "You are required to change your password immediately (administrator enforced)."); } else { pam_syslog(pamh, LOG_NOTICE, "expired password for user %s (password aged)", user); pam_error(pamh, "You are required to change your password immediately (password expired)."); } retval = PAM_NEW_AUTHTOK_REQD; break; case PWA_EXPIRED_PW: pam_syslog(pamh, LOG_NOTICE, "password for user %s is inactive", user); pam_error(pamh, "Your password is inactive; please contact your system administrator."); retval = PAM_AUTHTOK_EXPIRED; break; default: pam_syslog(pamh, LOG_ERR, "Unexpected expire value: %i", r); retval = PAM_SYSTEM_ERR; break; } if (daysleft >= 0) { pam_syslog(pamh, LOG_INFO, "password for user %s will expire in %ld days", user, daysleft); if (!(cfg->ctrl & ARG_QUIET)) pam_info(pamh, "Warning: your password will expire in %ld %s.", daysleft, (daysleft == 1)?"day":"days"); } return retval; } int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) { struct timespec start, stop; struct config_t cfg; int r; r = parse_args(pamh, flags, argc, argv, &cfg, false); if (r < 0) return errno_to_pam(r); if (cfg.ctrl & ARG_DEBUG) { clock_gettime(CLOCK_MONOTONIC, &start); pam_syslog(pamh, LOG_DEBUG, "acct_mgmt called"); } r = acct_mgmt(pamh, &cfg); if (cfg.ctrl & ARG_DEBUG) { clock_gettime(CLOCK_MONOTONIC, &stop); log_runtime_ms(pamh, "acct_mgmt", r, start, stop); } return r; } account-utils-1.0.1/src/pam_unix_ng-auth.c000066400000000000000000000077311513410760700205150ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include "basics.h" #include "pam_unix_ng.h" #include "pwaccess.h" #include "verify.h" static bool password_is_blank(const char *user, struct config_t *cfg) { bool nullok = (cfg->ctrl & ARG_NULLOK) && !(cfg->ctrl & ARG_NONULL); bool authenticated = false; int r; /* Never allow empty password if PAM_DISALLOW_NULL_AUTHTOK is set */ if (cfg->ctrl & ARG_NONULL) return false; /* Ask always for a password if empty passwords are forbidden */ if (!nullok) return false; /* if something fails, return false and user has to enter an empty password as worst. */ #if 0 /* XXX this needs a new option like the "nullresetok" from pam_unix.so. */ r = pwaccess_check_expired(user, NULL, NULL, NULL); if (r < 0) return false; else if (r > 0 && r != PWA_EXPIRED_CHANGE_PW) return false; else if (r == PWA_EXPIRED_CHANGE_PW) nullok = true; #endif r = pwaccess_verify_password(user, "", nullok, &authenticated, NULL); if (r != PAM_SUCCESS) return false; if (!authenticated) return false; return true; } static int authenticate(pam_handle_t *pamh, struct config_t *cfg) { bool authenticated = false; _cleanup_free_ char *error = NULL; const char *user = NULL; const char *password = NULL; int r; /* Get login name */ r = pam_get_user(pamh, &user, NULL /* prompt=xxx */); if (r != PAM_SUCCESS) { if (cfg->ctrl & ARG_DEBUG) pam_syslog(pamh, LOG_DEBUG, "pam_get_user failed: return %d", r); return (r == PAM_CONV_AGAIN ? PAM_INCOMPLETE:r); } /* can this happen? */ if (isempty(user)) return PAM_USER_UNKNOWN; else if (!valid_name(user)) { pam_syslog(pamh, LOG_ERR, "username contains invalid characters"); return PAM_USER_UNKNOWN; } else if (cfg->ctrl & ARG_DEBUG) pam_syslog(pamh, LOG_DEBUG, "username [%s]", user); /* Don't prompt for a password if it is empty */ if (!password_is_blank(user, cfg)) { /* get the users password */ r = pam_get_authtok(pamh, PAM_AUTHTOK, &password, NULL /* prompt */); if (r != PAM_SUCCESS) { if (cfg->ctrl & ARG_DEBUG) pam_syslog(pamh, LOG_DEBUG, "pam_get_authtok failed: return %d", r); if (r != PAM_CONV_AGAIN) pam_syslog(pamh, LOG_CRIT, "Could not get password for [%s]", user); return (r == PAM_CONV_AGAIN ? PAM_INCOMPLETE:r); } } if (cfg->fail_delay != 0) { /* convert milliseconds to microseconds */ r = pam_fail_delay(pamh, cfg->fail_delay*1000); if (r != PAM_SUCCESS) { if (cfg->ctrl & ARG_DEBUG) pam_syslog(pamh, LOG_DEBUG, "pam_fail_delay failed: return %d", r); pam_syslog(pamh, LOG_CRIT, "Could not set fail delay"); return r; } } r = authenticate_user(pamh, cfg->ctrl, user, strempty(password), &authenticated, &error); if (error) pam_error(pamh, "%s", error); if (r != PAM_SUCCESS) return r; if (authenticated) return PAM_SUCCESS; else log_authentication_failure(pamh, user); return PAM_AUTH_ERR; } int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { struct timespec start, stop; struct config_t cfg; int r; r = parse_args(pamh, flags, argc, argv, &cfg, false); if (r < 0) return errno_to_pam(r); if (cfg.ctrl & ARG_DEBUG) { clock_gettime(CLOCK_MONOTONIC, &start); pam_syslog(pamh, LOG_DEBUG, "authenticate called"); } r = authenticate(pamh, &cfg); if (cfg.ctrl & ARG_DEBUG) { clock_gettime(CLOCK_MONOTONIC, &stop); log_runtime_ms(pamh, "authenticate", r, start, stop); } return r; } int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { struct config_t cfg; int r; r = parse_args(pamh, flags, argc, argv, &cfg, false); if (r < 0) return errno_to_pam(r); if (cfg.ctrl & ARG_DEBUG) pam_syslog(pamh, LOG_DEBUG, "setcred called"); return PAM_SUCCESS; } account-utils-1.0.1/src/pam_unix_ng-common.c000066400000000000000000000214101513410760700210320ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause #include #include #include #include "basics.h" #include "pwaccess.h" #include "pam_unix_ng.h" #include "verify.h" #include "no_new_privs.h" #include "get_logindefs.h" int parse_args(pam_handle_t *pamh, int flags, int argc, const char **argv, struct config_t *cfg, bool init_crypt) { const char *cp; /* clear all variables */ memset(cfg, 0, sizeof(struct config_t)); /* defaults */ cfg->fail_delay = 2000; cfg->minlen = 8; /* only read login.defs if we change the password */ if (init_crypt) { _cleanup_free_ char *val; cfg->crypt_count = 0; val = get_logindefs_string("ENCRYPT_METHOD", NULL); if (isempty(val) || streq(val, "YESCRYPT")) cfg->crypt_prefix = "$y$"; else if (streq(val, "GHOST_YESCRYPT")) cfg->crypt_prefix = "$gy$"; else if (streq(val, "SHA512")) { cfg->crypt_count = get_logindefs_num("SHA_CRYPT_MAX_ROUNDS", 5000); cfg->crypt_prefix = "$6$"; } else if (streq(val, "SHA256")) { cfg->crypt_count = get_logindefs_num("SHA_CRYPT_MAX_ROUNDS", 5000); cfg->crypt_prefix = "$5$"; } else if (streq(val, "MD5")) { // cfg->crypt_prefix = "$1$"; pam_info(pamh, "MD5-based algorithms for password encryption are no longer supported!"); pam_syslog(pamh, LOG_NOTICE, "ENCRYPT_METHOD from login.defs has no longer supported MD5 value"); } else if (streq(val, "BLOWFISH") || streq(val, "BCRYPT")) cfg->crypt_prefix = "$2b$"; else { pam_syslog(pamh, LOG_NOTICE, "ENCRYPT_METHOD from login.defs has unknown value: '%s'", val); cfg->crypt_prefix = "$y$"; /* the default if no option is set */ } } /* does the application require quiet? */ if (flags & PAM_SILENT) cfg->ctrl |= ARG_QUIET; if (flags & PAM_DISALLOW_NULL_AUTHTOK) cfg->ctrl |= ARG_NONULL; /* step through arguments */ for (; argc-- > 0; ++argv) { if (streq(*argv, "debug")) cfg->ctrl |= ARG_DEBUG; else if (streq(*argv, "quiet")) cfg->ctrl |= ARG_QUIET; else if (streq(*argv, "nullok")) cfg->ctrl |= ARG_NULLOK; else if ((cp = startswith(*argv, "minlen=")) != NULL) { char *ep; long l; errno = 0; l = strtol(cp, &ep, 10); if (errno == ERANGE || l < 0 || l > INT32_MAX || cp == ep || *ep != '\0') pam_syslog(pamh, LOG_ERR, "Cannot parse 'minlen=%s'", cp); else cfg->minlen = l; } else if ((cp = startswith(*argv, "crypt_prefix=")) != NULL) cfg->crypt_prefix = cp; else if ((cp = startswith(*argv, "crypt_count=")) != NULL) { char *ep; long long ll; errno = 0; ll = strtoll(cp, &ep, 10); if (errno == ERANGE || ll < 0 || ll > UINT32_MAX || cp == ep || *ep != '\0') pam_syslog(pamh, LOG_ERR, "Cannot parse 'crypt_count=%s'", cp); else cfg->crypt_count = ll; } else if ((cp = startswith(*argv, "fail_delay=")) != NULL) { char *ep; long l; errno = 0; l = strtol(cp, &ep, 10); if (errno == ERANGE || l < 0 || l > UINT32_MAX || cp == ep || *ep != '\0') pam_syslog(pamh, LOG_ERR, "Cannot parse 'fail_delay=%s'", cp); else cfg->fail_delay = l; } /* this options are handled by pam_get_authtok() */ else if (!streq(*argv, "try_first_pass") && !streq(*argv, "use_first_pass") && !streq(*argv, "use_authtok") && startswith(*argv, "authtok_type=") == NULL) pam_syslog(pamh, LOG_ERR, "Unknown option: %s", *argv); } if (cfg->ctrl & ARG_DEBUG) pam_syslog(pamh, LOG_DEBUG, "Flags set by application:%s%s", flags & PAM_SILENT?" PAM_SILENT":"", flags & PAM_DISALLOW_NULL_AUTHTOK?" PAM_DISALLOW_NULL_AUTHTOK":""); return 0; } int alloc_getxxnam_buffer(pam_handle_t *pamh, char **buf, long *size) { long bufsize; bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); if (bufsize == -1) /* Value was indeterminate */ bufsize = 1024; /* sysconf() returns 1024 */ *buf = malloc(bufsize); if (*buf == NULL) { pam_syslog(pamh, LOG_CRIT, "Out of memory!"); return PAM_BUF_ERR; } *size = bufsize; return PAM_SUCCESS; } int authenticate_user(pam_handle_t *pamh, uint32_t ctrl, const char *user, const char *password, bool *ret_authenticated, char **error) { /* NONULL has preference over NULLOK */ bool nullok = (ctrl & ARG_NULLOK) && !(ctrl & ARG_NONULL); int r; r = pwaccess_verify_password(user, password, nullok, ret_authenticated, error); if (r < 0) { if (r == -ENODATA) return PAM_USER_UNKNOWN; pam_syslog(pamh, LOG_ERR, "pwaccess verify failed: %s", *error ? *error : strerror(-r)); if (PWACCESS_IS_NOT_RUNNING(r)) { struct passwd pwdbuf; struct passwd *pw = NULL; struct spwd spbuf; struct spwd *sp = NULL; _cleanup_free_ char *buf = NULL; _cleanup_free_ char *hash = NULL; long bufsize; if (!(ctrl & ARG_QUIET)) pam_syslog(pamh, LOG_NOTICE, "pwaccessd not running, using internal fallback code"); r = alloc_getxxnam_buffer(pamh, &buf, &bufsize); if (r != PAM_SUCCESS) return r; r = getpwnam_r(user, &pwdbuf, buf, bufsize, &pw); if (pw == NULL) { if (r == 0) { if (valid_name(user)) pam_error(pamh, "User '%s' not found", user); else pam_error(pamh, "User not found (contains invalid characters)"); return PAM_USER_UNKNOWN; } pam_syslog(pamh, LOG_WARNING, "getpwnam_r(): %s", strerror(r)); pam_error(pamh, "getpwnam_r(): %s", strerror(r)); return PAM_SYSTEM_ERR; } hash = strdup(strempty(pw->pw_passwd)); if (hash == NULL) { pam_syslog(pamh, LOG_CRIT, "Out of memory!"); pam_error(pamh, "Out of memory!"); return PAM_BUF_ERR; } if (is_shadow(pw)) /* Get shadow entry */ { /* reuse buffer, !!! pw is no longer valid !!! */ pw = NULL; r = getspnam_r(user, &spbuf, buf, bufsize, &sp); if (sp == NULL) { if (r != 0) /* r == 0 means there is no shadow entry for this account, so pw->pw_passwd is incorrectly set. Ignore, crypt() will fail. */ { pam_syslog(pamh, LOG_WARNING, "getspnam_r(): %s", strerror(r)); pam_error(pamh, "getspnam_r(): %s", strerror(r)); return PAM_SYSTEM_ERR; } } else { hash = mfree(hash); hash = strdup(strempty(sp->sp_pwdp)); if (hash == NULL) { pam_syslog(pamh, LOG_CRIT, "Out of memory!"); pam_error(pamh, "Out of memory!"); return PAM_BUF_ERR; } } } r = verify_password(hash, password, nullok); if (r == VERIFY_OK) *ret_authenticated = true; else if (r != VERIFY_FAILED) { switch(r) { case VERIFY_CRYPT_DISABLED: pam_syslog(pamh, LOG_ERR, "crypt algo of hash is disabled"); pam_error(pamh, "Crypt alogrithm of password hash is disabled"); break; case VERIFY_CRYPT_INVALID: pam_syslog(pamh, LOG_ERR, "crypt algo of hash is not supported"); pam_error(pamh, "Crypt alogrithm of hash is not supported"); break; default: pam_syslog(pamh, LOG_ERR, "Unknown verify_password() error: %i", r); pam_error(pamh, "Unknown verify_password() error: %i", r); break; } return PAM_SYSTEM_ERR; } } else return PAM_SYSTEM_ERR; } return PAM_SUCCESS; } void log_authentication_failure(pam_handle_t *pamh, const char *user) { const void *ruser = NULL; const void *rhost = NULL; const void *tty = NULL; const char *login_name; pam_get_item(pamh, PAM_RUSER, &ruser); pam_get_item(pamh, PAM_RHOST, &rhost); pam_get_item(pamh, PAM_TTY, &tty); login_name = pam_modutil_getlogin(pamh); pam_syslog(pamh, LOG_NOTICE, "authentication failure; " "logname=%s uid=%u euid=%u " "tty=%s ruser=%s rhost=%s " "user=%s%s", strna(login_name), getuid(), geteuid(), strna(tty), strna(ruser), strna(rhost), user, no_new_privs_enabled()?", no_new_privs=1":""); } void log_runtime_ms(pam_handle_t *pamh, const char *type, int retval, struct timespec start, struct timespec stop) { uint64_t delta_ms = timespec_diff_ms(start, stop); pam_syslog(pamh, LOG_DEBUG, "%s finished (%s), executed in %lu milliseconds", type, pam_strerror(pamh, retval), delta_ms); } int errno_to_pam(int e) { if (e < 0) e = -e; switch(e) { case ENOMEM: return PAM_BUF_ERR; default: break; } return PAM_SERVICE_ERR; } account-utils-1.0.1/src/pam_unix_ng-passwd.c000066400000000000000000000311101513410760700210410ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include #include "basics.h" #include "pam_unix_ng.h" #include "pwaccess.h" #include "verify.h" #include "files.h" #define MAX_PASSWD_TRIES 3 static inline void secure_freep(char **p) { if (*p) explicit_bzero(*p, strlen(*p)); *p = mfree(*p); } static int get_local_user_record(pam_handle_t *pamh, const char *user, struct passwd **ret_pw, struct spwd **ret_sp) { _cleanup_fclose_ FILE *fp = NULL; struct passwd pw; struct spwd sp; struct passwd *pw_ptr = NULL; struct spwd *sp_ptr = NULL; _cleanup_free_ char *pwbuf; long pwbufsize = 0; _cleanup_free_ char *spbuf; long spbufsize = 0; int r; assert(user); assert(ret_pw); assert(ret_sp); *ret_pw = NULL; *ret_sp = NULL; /* Get passwd entry */ if ((fp = fopen("/etc/passwd", "r")) == NULL) return -errno; r = alloc_getxxnam_buffer(pamh, &pwbuf, &pwbufsize); if (r != PAM_SUCCESS) return r; /* Loop over all passwd entries */ r = 0; while (r == 0) { r = fgetpwent_r(fp, &pw, pwbuf, pwbufsize, &pw_ptr); if (pw_ptr != NULL) { if(streq(pw_ptr->pw_name, user)) break; } } if (r != 0) return -errno; r = fclose(fp); fp = NULL; if (r < 0) return -errno; /* Get shadow entry */ if ((fp = fopen("/etc/shadow", "r")) == NULL) return -errno; r = alloc_getxxnam_buffer(pamh, &spbuf, &spbufsize); if (r != PAM_SUCCESS) return r; /* Loop over all shadow entries */ r = 0; while (r == 0) { r = fgetspent_r(fp, &sp, spbuf, spbufsize, &sp_ptr); if (sp_ptr != NULL) { if (streq(sp_ptr->sp_namp, user)) break; } } if (r != 0) return -errno; r = fclose(fp); fp = NULL; if (r < 0) return -errno; /* ret_pw != NULL -> pw contains valid entry, duplicate that */ if (pw_ptr) { _cleanup_(struct_passwd_freep) struct passwd *tmp = NULL; tmp = calloc(1, sizeof(struct passwd)); if (tmp == NULL) return -ENOMEM; tmp->pw_name = strdup(pw.pw_name); tmp->pw_passwd = strdup(strempty(pw.pw_passwd)); tmp->pw_uid = pw.pw_uid; tmp->pw_gid = pw.pw_gid; tmp->pw_gecos = strdup(strempty(pw.pw_gecos)); tmp->pw_dir = strdup(strempty(pw.pw_dir)); tmp->pw_shell = strdup(strempty(pw.pw_shell)); /* if any of the string pointer is NULL, strdup failed */ if (!tmp->pw_name || !tmp->pw_passwd || !tmp->pw_gecos || !tmp->pw_dir || !tmp->pw_shell) return -ENOMEM; *ret_pw = TAKE_PTR(tmp); } if (sp_ptr) { _cleanup_(struct_shadow_freep) struct spwd *tmp; tmp = calloc(1, sizeof(struct spwd)); if (tmp == NULL) return -ENOMEM; tmp->sp_namp = strdup(sp.sp_namp); tmp->sp_pwdp = strdup(strempty(sp.sp_pwdp)); tmp->sp_lstchg = sp.sp_lstchg; tmp->sp_min = sp.sp_min; tmp->sp_max = sp.sp_max; tmp->sp_warn = sp.sp_warn; tmp->sp_inact = sp.sp_inact; tmp->sp_expire = sp.sp_expire; tmp->sp_flag = sp.sp_flag; if (!tmp->sp_namp || !tmp->sp_pwdp) return -ENOMEM; *ret_sp = TAKE_PTR(tmp); } return 0; } static bool i_am_root_detect(int flags) { bool root = false; /* The test for PAM_CHANGE_EXPIRED_AUTHTOK is here, because login runs as root and we need the old password in this case. */ root = (getuid() == 0 && !(flags & PAM_CHANGE_EXPIRED_AUTHTOK)); return root; } static int unix_chauthtok_prelim_check(pam_handle_t *pamh, int flags, struct config_t *cfg, const char *user, struct passwd *pw, struct spwd *sp, bool i_am_root) { const char *pass_old = NULL; bool authenticated = false; bool pwchangeable = true; _cleanup_free_ char *error = NULL; int r; /* instruct user what is happening */ if (!(cfg->ctrl & ARG_QUIET)) { r = pam_info(pamh, "Changing password for %s.", user); if (r != PAM_SUCCESS) return r; } /* If this is being run by root and we change a local password, we don't need to get the old password. */ if (i_am_root) { if (cfg->ctrl & ARG_DEBUG) pam_syslog(pamh, LOG_DEBUG, "process run by root, do nothing"); return PAM_SUCCESS; } /* don't ask for the old password if it is empty */ if (is_blank_password(pw, sp)) { if (cfg->ctrl & ARG_DEBUG) pam_syslog(pamh, LOG_DEBUG, "Old password is empty, skip"); return PAM_SUCCESS; } r = expired_check(sp, NULL, &pwchangeable); if (!pwchangeable && !i_am_root) { pam_error(pamh, "You must wait longer to change your password."); return PAM_AUTHTOK_ERR; } if (cfg->ctrl & ARG_DEBUG) pam_syslog(pamh, LOG_DEBUG, "expired_check=%i", r); if (r == PWA_EXPIRED_NO && (flags & PAM_CHANGE_EXPIRED_AUTHTOK)) { pam_error(pamh, "Password not expired"); return PAM_AUTHTOK_ERR; } r = pam_get_authtok(pamh, PAM_OLDAUTHTOK, &pass_old, NULL); if (r != PAM_SUCCESS) { pam_syslog(pamh, LOG_NOTICE, "password - old token not obtained"); return r; } r = authenticate_user(pamh, cfg->ctrl, user, pass_old, &authenticated, &error); pass_old = NULL; if (r != PAM_SUCCESS || !authenticated) { if (error) pam_syslog(pamh, LOG_ERR, "authentication error: %s", error); log_authentication_failure(pamh, user); if (r != PAM_SUCCESS) return r; return PAM_AUTH_ERR; } return PAM_SUCCESS; } static int unix_chauthtok_update_authtok(pam_handle_t *pamh, struct config_t *cfg, const char *user, struct passwd *pw, struct spwd *sp, bool i_am_root) { const char *pass_old = NULL; const char *pass_new = NULL; const void *item; int retry = 0; int r; /* Get the old password again. */ r = pam_get_item(pamh, PAM_OLDAUTHTOK, &item); if (r != PAM_SUCCESS) { pam_syslog(pamh, LOG_NOTICE, "User %s not authenticated: %s", user, pam_strerror(pamh, r)); return r; } pass_old = item; r = PAM_AUTHTOK_ERR; while ((r != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) { const char *no_new_pass_msg = "No new password has been supplied"; /* use_authtok is to force the use of a previously entered password -- needed for pluggable password strength checking */ r = pam_get_authtok(pamh, PAM_AUTHTOK, &pass_new, NULL); if (r == PAM_TRY_AGAIN) /* New authentication tokens mismatch. */ continue; if (r != PAM_SUCCESS) { if (cfg->ctrl & ARG_DEBUG) pam_syslog(pamh, LOG_DEBUG, "%s - %s", no_new_pass_msg, pam_strerror(pamh, r)); pass_old = NULL; return r; } if (isempty(pass_new) || (pass_old && streq(pass_new, pass_old))) { /* remove new password */ pam_set_item(pamh, PAM_AUTHTOK, NULL); pass_new = NULL; if (cfg->ctrl & ARG_DEBUG) pam_syslog(pamh, LOG_DEBUG, "%s", no_new_pass_msg); pam_error(pamh, "%s.", no_new_pass_msg); r = PAM_AUTHTOK_ERR; } else if (strlen(strempty(pass_new)) > PAM_MAX_RESP_SIZE) { /* remove new password */ pam_set_item(pamh, PAM_AUTHTOK, NULL); pass_new = NULL; pam_syslog(pamh, LOG_NOTICE, "supplied password to long"); pam_error(pamh, "You must choose a shorter password."); r = PAM_AUTHTOK_ERR; } else if (strlen(strempty(pass_new)) < (size_t)cfg->minlen) { pam_syslog(pamh, LOG_NOTICE, "supplied password for %s too short", user); if (!i_am_root) { /* remove new password */ pam_set_item(pamh, PAM_AUTHTOK, NULL); pass_new = NULL; pam_error(pamh, "You must choose a longer password."); r = PAM_AUTHTOK_ERR; } } } if (r != PAM_SUCCESS) { pam_syslog(pamh, LOG_NOTICE, "new password not acceptable"); pass_new = pass_old = NULL; /* cleanup */ return r; } /* We have an approved password, create new hash and change the database */ if (cfg->ctrl & ARG_DEBUG) pam_syslog(pamh, LOG_DEBUG, "Create hash with prefix=%s, count=%lu", cfg->crypt_prefix, cfg->crypt_count); _cleanup_free_ char *error = NULL; _cleanup_(secure_freep) char *new_hash = NULL; r = create_hash(pass_new, cfg->crypt_prefix, cfg->crypt_count, &new_hash, &error); if (r < 0 || new_hash == NULL) { if (r == -ENOMEM) { pam_syslog(pamh, LOG_CRIT, "Out of memory"); return PAM_BUF_ERR; } else { if (error) pam_syslog(pamh, LOG_ERR, "crypt() failure: %s", error); else pam_syslog(pamh, LOG_ERR, "crypt() failure for new password"); } pass_new = pass_old = NULL; /* cleanup */ return PAM_SYSTEM_ERR; } if (sp && (is_shadow(pw) || isempty(pw->pw_passwd))) { /* we use _cleanup_ for this struct */ free(sp->sp_pwdp); sp->sp_pwdp = strdup(new_hash); if (sp->sp_pwdp == NULL) return PAM_BUF_ERR; sp->sp_lstchg = time(NULL) / (60 * 60 * 24); if (sp->sp_lstchg == 0) sp->sp_lstchg = -1; /* Don't request passwort change only because time isn't set yet. */ r = update_shadow(sp, NULL); if (r == 0 && !streq("x", strempty(pw->pw_passwd))) { if (pw->pw_passwd) free(pw->pw_passwd); pw->pw_passwd = strdup("x"); r = update_passwd(pw, NULL); } } else { /* we use _cleanup_ for this struct */ free(pw->pw_passwd); pw->pw_passwd = strdup(new_hash); if (pw->pw_passwd == NULL) return PAM_BUF_ERR; r = update_passwd(pw, NULL); } pass_old = pass_new = NULL; if (r < 0) return PAM_AUTHTOK_ERR; else return PAM_SUCCESS; } static int unix_chauthtok(pam_handle_t *pamh, int flags, struct config_t *cfg) { _cleanup_(struct_passwd_freep) struct passwd *pw = NULL; _cleanup_(struct_shadow_freep) struct spwd *sp = NULL; bool i_am_root = i_am_root_detect(flags); const char *only_expired_authtok = ""; const char *run_as_root = ""; const char *user = NULL; int r; if (i_am_root) run_as_root = ", root"; /* Validate flags */ if (flags & PAM_CHANGE_EXPIRED_AUTHTOK) only_expired_authtok = ", only expired authtok"; if (flags & PAM_PRELIM_CHECK) { if (cfg->ctrl & ARG_DEBUG) pam_syslog(pamh, LOG_DEBUG, "chauthtok called (prelim check%s%s)", only_expired_authtok, run_as_root); } else if (flags & PAM_UPDATE_AUTHTOK) { if (cfg->ctrl & ARG_DEBUG) pam_syslog(pamh, LOG_DEBUG, "chauthtok called (update authtok%s%s)", only_expired_authtok, run_as_root); } else { pam_syslog(pamh, LOG_ERR, "chauthtok called without flag!"); return PAM_ABORT; } /* We must be root to update passwd and shadow. */ if (geteuid() != 0) { const char *no_root = "Calling process must be root!"; pam_syslog(pamh, LOG_ERR, "%s (euid=%u,uid=%u)", no_root, geteuid(), getuid()); pam_error(pamh, "%s", no_root); return PAM_CRED_INSUFFICIENT; } /* Get login name */ r = pam_get_user(pamh, &user, NULL /* prompt=xxx */); if (r != PAM_SUCCESS) { if (cfg->ctrl & ARG_DEBUG) pam_syslog(pamh, LOG_DEBUG, "pam_get_user failed: return %d", r); return (r == PAM_CONV_AGAIN ? PAM_INCOMPLETE:r); } if (isempty(user)) return PAM_USER_UNKNOWN; if (!valid_name(user)) { pam_syslog(pamh, LOG_ERR, "username contains invalid characters"); return PAM_USER_UNKNOWN; } else if (cfg->ctrl & ARG_DEBUG) pam_syslog(pamh, LOG_DEBUG, "username [%s]", user); r = get_local_user_record(pamh, user, &pw, &sp); if (r < 0) { if (r == -ENOENT) { pam_syslog(pamh, LOG_ERR, "%s is no local user", user); pam_error(pamh, "You can only change local passwords."); } else { pam_syslog(pamh, LOG_ERR, "getting local user records failed: %s", strerror(-r)); pam_error(pamh, "Error getting user records"); } return PAM_AUTHTOK_RECOVERY_ERR; } if (flags & PAM_PRELIM_CHECK) return unix_chauthtok_prelim_check(pamh, flags, cfg, user, pw, sp, i_am_root); else if (flags & PAM_UPDATE_AUTHTOK) return unix_chauthtok_update_authtok(pamh, cfg, user, pw, sp, i_am_root); else { pam_syslog(pamh, LOG_CRIT, "pam_sm_chauthtok received unknown request (flags=%i)", flags); return PAM_ABORT; } return PAM_SUCCESS; } int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) { struct timespec start, stop; struct config_t cfg; int r; r = parse_args(pamh, flags, argc, argv, &cfg, true); if (r < 0) return errno_to_pam(r); if (cfg.ctrl & ARG_DEBUG) { clock_gettime(CLOCK_MONOTONIC, &start); pam_syslog(pamh, LOG_DEBUG, "chauthtok called"); } r = unix_chauthtok(pamh, flags, &cfg); if (cfg.ctrl & ARG_DEBUG) { clock_gettime(CLOCK_MONOTONIC, &stop); log_runtime_ms(pamh, "chauthtok", r, start, stop); } return r; } account-utils-1.0.1/src/pam_unix_ng-session.c000066400000000000000000000052161513410760700212330ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause #include #include #include #include #include "basics.h" #include "pam_unix_ng.h" #include "pwaccess.h" #include "verify.h" int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { char lognamebuf[LOGIN_NAME_MAX+1]; const char *logname = lognamebuf; struct passwd pwdbuf; struct passwd *pw = NULL; _cleanup_free_ char *pwbuf = NULL; long pwbufsize; const void *void_str; const char *user; struct config_t cfg; int r; r = parse_args(pamh, flags, argc, argv, &cfg, false); if (r < 0) return errno_to_pam(r); if (cfg.ctrl & ARG_DEBUG) pam_syslog(pamh, LOG_DEBUG, "open_session called"); /* don't do anything if we don't log it */ if (cfg.ctrl & ARG_QUIET) return PAM_SUCCESS; r = pam_get_item(pamh, PAM_USER, &void_str); if (r != PAM_SUCCESS || isempty(void_str)) { pam_syslog(pamh, LOG_ERR, "open_session - user is not known?"); return PAM_SESSION_ERR; } user = void_str; /* lognamebuf is bigger than max allowed username length */ if (getlogin_r(lognamebuf, sizeof(lognamebuf)) != 0) logname = strerror(errno); r = alloc_getxxnam_buffer(pamh, &pwbuf, &pwbufsize); if (r != PAM_SUCCESS) return r; r = getpwnam_r(user, &pwdbuf, pwbuf, pwbufsize, &pw); if (pw == NULL) { if (r == 0) { const char *cp; if (!valid_name(user)) cp = ""; else cp = user; pam_syslog(pamh, LOG_INFO, "User '%s' not found", strna(cp)); return PAM_USER_UNKNOWN; } pam_syslog(pamh, LOG_WARNING, "getpwnam_r(): %s", strerror(r)); pam_error(pamh, "getpwnam_r(): %s", strerror(r)); return PAM_SYSTEM_ERR; } pam_syslog(pamh, LOG_INFO, "session opened for user %s(uid=%lu) by %s(uid=%lu)", user, (long unsigned)pw->pw_uid, logname, (long unsigned)getuid()); return PAM_SUCCESS; } int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { const void *void_str; const char *user; struct config_t cfg; int r; r = parse_args(pamh, flags, argc, argv, &cfg, false); if (r < 0) return errno_to_pam(r); if (cfg.ctrl & ARG_DEBUG) pam_syslog(pamh, LOG_DEBUG, "close_session called"); /* don't do anything if we don't log it */ if (cfg.ctrl & ARG_QUIET) return PAM_SUCCESS; r = pam_get_item(pamh, PAM_USER, &void_str); if (r != PAM_SUCCESS || isempty(void_str)) { pam_syslog(pamh, LOG_ERR, "close_session - user is not known?"); return PAM_SESSION_ERR; } user = void_str; pam_syslog(pamh, LOG_INFO, "session closed for user %s", user); return PAM_SUCCESS; } account-utils-1.0.1/src/pam_unix_ng.h000066400000000000000000000027041513410760700175560ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause #pragma once #include #include #include #include #include #define ARG_DEBUG 1 /* send info to syslog(3) */ #define ARG_QUIET 2 /* keep quiet about things */ #define ARG_NULLOK 4 /* allow blank passwords */ #define ARG_NONULL 8 /* don't allow blank passwords */ struct config_t { uint32_t ctrl; uint32_t fail_delay; /* sleep of milliseconds in case of auth failure */ int minlen; /* minimal length of new password */ const char *crypt_prefix; /* see man crypt(5) */ unsigned long crypt_count; /* see man crypt(5) */ }; extern int parse_args(pam_handle_t *pamh, int flags, int argc, const char **argv, struct config_t *cfg, bool init_crypt); extern int alloc_getxxnam_buffer(pam_handle_t *pamh, char **buf, long *size); extern int authenticate_user(pam_handle_t *pamh, uint32_t ctrl, const char *user, const char *password, bool *ret_authenticated, char **error); extern int errno_to_pam(int e); extern void log_authentication_failure(pam_handle_t *pamh, const char *user); extern void log_runtime_ms(pam_handle_t *pamh, const char *type, int retval, struct timespec start, struct timespec stop); static inline uint64_t timespec_diff_ms(struct timespec start, struct timespec stop) { return ((stop.tv_sec - start.tv_sec) * 1000000000 + (stop.tv_nsec - start.tv_nsec)) / 1000 / 1000; } account-utils-1.0.1/src/pam_unix_ng.map000066400000000000000000000002471513410760700201040ustar00rootroot00000000000000{ global: pam_sm_acct_mgmt; pam_sm_authenticate; pam_sm_chauthtok; pam_sm_close_session; pam_sm_open_session; pam_sm_setcred; local: *; }; account-utils-1.0.1/src/passwd.c000066400000000000000000000425431513410760700165530ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include "config.h" #include #include #include #include #include #include #include #include "basics.h" #include "pwaccess.h" #include "varlink-client-common.h" #include "verify.h" #include "chauthtok.h" #include "get_logindefs.h" #include "drop_privs.h" #define ARG_DELETE_PASSWORD 1 #define ARG_EXPIRE 2 #define ARG_LOCK_PASSWORD 4 #define ARG_UNLOCK_PASSWORD 8 #define ARG_STATUS_ACCOUNT 16 #define ARG_PASSWORD_STDIN 32 static int oom(void) { fprintf(stderr, "Out of memory!\n"); return ENOMEM; } static void print_usage(FILE *stream) { fprintf(stream, "Usage: passwd [options] [user]\n"); } static void print_help(void) { fprintf(stdout, "passwd - change user password\n\n"); print_usage(stdout); fputs(" -d, --delete Delete password\n", stdout); fputs(" -e, --expire Immediately expire password\n", stdout); fputs(" -h, --help Give this help list\n", stdout); fputs(" -I, --inactive Lock expired account after inactive days\n", stdout); fputs(" -k, --keep-tokens Change only expired passwords\n", stdout); fputs(" -l, --lock Lock password\n", stdout); fputs(" -m, --mindays Minimum # of days before password can be changed\n", stdout); fputs(" -M, --maxdays Maximum # of days before password can be canged\n", stdout); fputs(" -q, --quiet Be silent\n", stdout); fputs(" -s, --stdin Read new password from stdin\n", stdout); fputs(" -S, --status Display account status\n", stdout); fputs(" -u, --unlock Unlock password\n", stdout); fputs(" -v, --version Print program version\n", stdout); fputs(" -w, --warndays # days of warning before password expires\n", stdout); } static void print_error(void) { fprintf(stderr, "Try `passwd --help' for more information.\n"); } #define DAY (24L*3600L) #define SCALE DAY static inline char * date2str(time_t date) { static char buf[12]; struct tm tm; if (date < 0) strcpy(buf, "never"); else if (!gmtime_r(&date, &tm)) strcpy(buf, "future"); else strftime(buf, sizeof(buf), "%Y-%m-%d", &tm); return buf; } static const char * pw_status(const char *pass) { if (startswith(pass, "*") || startswith(pass, "!")) return "L"; if (isempty(pass)) return "NP"; return "P"; } static int print_account_status(const struct passwd *pw, const struct spwd *sp) { if (sp) printf("%s %s %s %ld %ld %ld %ld\n", pw->pw_name, pw_status(sp->sp_pwdp), date2str(sp->sp_lstchg * SCALE), sp->sp_min, sp->sp_max, sp->sp_warn, sp->sp_inact); else if (pw->pw_passwd) printf("%s %s\n", pw->pw_name, pw_status(pw->pw_passwd)); else { fprintf(stderr, "Malformed password data obtained for user '%s'.\n", pw->pw_name); return EINVAL; } return 0; } static int modify_account(struct passwd *pw, struct spwd *sp, int args, const char *inactive, const char *mindays, const char *maxdays, const char *warndays, bool quiet) { _cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *passwd = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *shadow = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; _cleanup_free_ char *error = NULL; _cleanup_(struct_result_free) struct result p = { .success = false, .error = NULL, }; static const sd_json_dispatch_field dispatch_table[] = { { "Success", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct result, success), 0 }, { "ErrorMsg", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct result, error), 0 }, {} }; int has_change = 0; int r; if (args & ARG_DELETE_PASSWORD) { if (pw->pw_passwd) pw->pw_passwd = mfree(pw->pw_passwd); pw->pw_passwd = strdup(""); if (pw->pw_passwd == NULL) { fprintf(stderr, "Out of memory!\n"); return ENOMEM; } if (sp) { if (sp->sp_pwdp) sp->sp_pwdp = mfree(sp->sp_pwdp); sp->sp_pwdp = strdup(""); if (sp->sp_pwdp == NULL) { fprintf(stderr, "Out of memory!\n"); return ENOMEM; } } has_change = 1; } if ((args & ARG_EXPIRE) && sp) { sp->sp_lstchg = 0; has_change = 1; } if (args & ARG_LOCK_PASSWORD) { char *newpw = NULL; if (is_shadow(pw)) { if (asprintf(&newpw, "!%s", strempty(sp->sp_pwdp)) < 0) return ENOMEM; free(sp->sp_pwdp); sp->sp_pwdp = newpw; } else { if (asprintf(&newpw, "!%s", strempty(pw->pw_passwd)) < 0) return ENOMEM; free(pw->pw_passwd); pw->pw_passwd = newpw; } has_change = 1; } if (args & ARG_UNLOCK_PASSWORD) { char *newpw = NULL; if (is_shadow(pw) && startswith(sp->sp_pwdp, "!")) { newpw=strdup(&(sp->sp_pwdp)[1]); if (!newpw) return ENOMEM; free(sp->sp_pwdp); sp->sp_pwdp = newpw; has_change = 1; } else if (startswith(pw->pw_passwd, "!")) { newpw=strdup(&(pw->pw_passwd)[1]); if (!newpw) return ENOMEM; free(pw->pw_passwd); pw->pw_passwd = newpw; has_change = 1; } } if (inactive || mindays || maxdays || warndays) { char *ep; if (!sp) { sp = calloc(1, sizeof(struct spwd)); if (!sp) return oom(); sp->sp_namp = strdup(pw->pw_name); if (!sp->sp_namp) return oom(); sp->sp_pwdp = pw->pw_passwd; pw->pw_passwd = strdup("x"); if (!pw->pw_passwd) return oom(); sp->sp_lstchg = time(NULL) / DAY; /* disable instead of requesting password change */ if (!sp->sp_lstchg) sp->sp_lstchg = -1; sp->sp_min = get_logindefs_num("PASS_MIN_DAYS", -1); sp->sp_max = get_logindefs_num("PASS_MAX_DAYS", -1); sp->sp_warn = get_logindefs_num("PASS_WARN_AGE", -1); sp->sp_inact = -1; sp->sp_expire = -1; } if (inactive) { long l; errno = 0; l = strtol(inactive, &ep, 10); if (errno == ERANGE || l < -1 || inactive == ep || *ep != '\0') { fprintf(stderr, "Cannot parse 'inactive=%s'\n", inactive); return EINVAL; } sp->sp_inact = l; } if (mindays) { long l; errno = 0; l = strtol(mindays, &ep, 10); if (errno == ERANGE || l < -1 || mindays == ep || *ep != '\0') { fprintf(stderr, "Cannot parse 'mindays=%s'\n", mindays); return EINVAL; } sp->sp_min = l; } if (maxdays) { long l; errno = 0; l = strtol(maxdays, &ep, 10); if (errno == ERANGE || l < -1 || maxdays == ep || *ep != '\0') { fprintf(stderr, "Cannot parse 'maxdays=%s'\n", maxdays); return EINVAL; } sp->sp_max = l; } if (warndays) { long l; errno = 0; l = strtol(warndays, &ep, 10); if (errno == ERANGE || l < -1 || warndays == ep || *ep != '\0') { fprintf(stderr, "Cannot parse 'warndays=%s'\n", warndays); return EINVAL; } sp->sp_warn = l; } has_change = 1; } if (!has_change) { if (!quiet) printf("Nothing to change.\n"); return 0; } r = connect_to_pwupdd(&link, _VARLINK_PWUPD_SOCKET, &error); if (r < 0) { if (error) fprintf(stderr, "%s\n", error); else fprintf(stderr, "Cannot connect to pwupd! (%s)\n", strerror(-r)); return -r; } r = sd_json_variant_merge_objectbo(&passwd, SD_JSON_BUILD_PAIR_STRING("name", pw->pw_name), SD_JSON_BUILD_PAIR_STRING("passwd", pw->pw_passwd), SD_JSON_BUILD_PAIR_INTEGER("UID", pw->pw_uid), SD_JSON_BUILD_PAIR_INTEGER("GID", pw->pw_gid), SD_JSON_BUILD_PAIR_STRING("GECOS", pw->pw_gecos), SD_JSON_BUILD_PAIR_STRING("dir", pw->pw_dir), SD_JSON_BUILD_PAIR_STRING("shell", pw->pw_shell)); if (r < 0) { fprintf(stderr, "Error building passwd data: %s\n", strerror(-r)); return -r; } if (sp) { r = sd_json_variant_merge_objectbo(&shadow, SD_JSON_BUILD_PAIR_STRING("name", sp->sp_namp), SD_JSON_BUILD_PAIR_STRING("passwd", sp->sp_pwdp), SD_JSON_BUILD_PAIR_INTEGER("lstchg", sp->sp_lstchg), SD_JSON_BUILD_PAIR_INTEGER("min", sp->sp_min), SD_JSON_BUILD_PAIR_INTEGER("max", sp->sp_max), SD_JSON_BUILD_PAIR_INTEGER("warn", sp->sp_warn), SD_JSON_BUILD_PAIR_INTEGER("inact", sp->sp_inact), SD_JSON_BUILD_PAIR_INTEGER("expire", sp->sp_expire), SD_JSON_BUILD_PAIR_INTEGER("flag", sp->sp_flag)); if (r < 0) { fprintf(stderr, "Error building shadow data: %s\n", strerror(-r)); return -r; } } r = sd_json_variant_merge_objectbo(¶ms, SD_JSON_BUILD_PAIR_VARIANT("passwd", passwd)); if (r >= 0 && shadow) r = sd_json_variant_merge_objectbo(¶ms, SD_JSON_BUILD_PAIR_VARIANT("shadow", shadow)); if (r < 0) { fprintf(stderr, "JSON merge result object failed: %s", strerror(-r)); return -r; } const char *error_id = NULL; r = sd_varlink_call(link, "org.openSUSE.pwupd.UpdatePasswdShadow", params, &result, &error_id); if (r < 0) { fprintf(stderr, "Failed to call UpdatePasswdShadow method: %s\n", strerror(-r)); return -r; } /* dispatch before checking error_id, we may need the result for the error message */ r = sd_json_dispatch(result, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p); if (r < 0) { fprintf(stderr, "Failed to parse JSON answer: %s\n", strerror(-r)); return -r; } if (error_id && strlen(error_id) > 0) { if (p.error) fprintf(stderr, "Error updating account information:\n%s\n", p.error); else fprintf(stderr, "Error updating account information:\n%s\n", error_id); return EIO; } if (!quiet) printf("Account information updated.\n"); return 0; } /* A conversation function which uses an internally-stored value for * the responses. */ static int stdin_conv (int num_msg, const struct pam_message **msg, struct pam_response **response, void *appdata_ptr) { struct pam_response *reply; const char *stdin_input = appdata_ptr; size_t count; *response = NULL; /* Sanity test. */ if (num_msg <= 0) return PAM_CONV_ERR; /* Allocate memory for the responses. */ reply = calloc (num_msg, sizeof (struct pam_response)); if (reply == NULL) return PAM_CONV_ERR; /* Each prompt elicits the same response. */ for (count = 0; count < (size_t)num_msg; count++) { switch (msg[count]->msg_style) { case PAM_PROMPT_ECHO_ON: /* unsupported, free memory and return error */ fprintf(stderr, "PAM_PROMPT_ECHO_ON unsupported together with --stdin\n"); for (size_t i = 0; i < count; i++) if (reply[i].resp) { explicit_bzero(reply[i].resp, strlen(reply[i].resp)); reply[i].resp = mfree(reply[i].resp); } reply = mfree(reply); return PAM_CONV_ERR; break; case PAM_PROMPT_ECHO_OFF: reply[count].resp_retcode = 0; reply[count].resp = strdup(stdin_input); break; case PAM_ERROR_MSG: fprintf(stderr, "%s\n", msg[count]->msg); break; case PAM_TEXT_INFO: fprintf(stdout, "%s\n", msg[count]->msg); break; default: fprintf(stderr, "Erroneous conversation (%d)\n", msg[count]->msg_style); for (size_t i = 0; i < count; i++) if (reply[i].resp) { explicit_bzero(reply[i].resp, strlen(reply[i].resp)); reply[i].resp = mfree(reply[i].resp); } reply = mfree(reply); return PAM_CONV_ERR; break; } } /* Set the pointers in the response structure and return. */ *response = reply; return PAM_SUCCESS; } int main(int argc, char **argv) { _cleanup_(struct_passwd_freep) struct passwd *pw = NULL; _cleanup_(struct_shadow_freep) struct spwd *sp = NULL; _cleanup_free_ char *error = NULL; bool complete = false; const char *inactive = NULL; const char *mindays = NULL; const char *maxdays = NULL; const char *warndays = NULL; const char *user = NULL; int args = 0; int pam_flags = 0; bool quiet = false; int r; setlocale(LC_ALL, ""); while (1) { int c; int option_index = 0; static struct option long_options[] = { {"delete", no_argument, NULL, 'd' }, {"expire", no_argument, NULL, 'e' }, {"help", no_argument, NULL, 'h' }, {"inactive", required_argument, NULL, 'I' }, {"keep-tokens", no_argument, NULL, 'k' }, {"lock", no_argument, NULL, 'l' }, {"mindays", required_argument, NULL, 'm' }, {"maxdays", required_argument, NULL, 'M' }, {"quiet", no_argument, NULL, 'q' }, {"stdin", no_argument, NULL, 's' }, {"status", no_argument, NULL, 'S' }, {"unlock", no_argument, NULL, 'u' }, {"version", no_argument, NULL, 'v' }, {"warndays", required_argument, NULL, 'w' }, {NULL, 0, NULL, '\0'} }; c = getopt_long(argc, argv, "dehI:klm:M:qSuvw:", long_options, &option_index); if (c == (-1)) break; switch (c) { case 'd': args |= ARG_DELETE_PASSWORD; break; case 'e': args |= ARG_EXPIRE; break; case 'h': print_help(); return 0; case 'I': inactive = optarg; break; case 'k': pam_flags |= PAM_CHANGE_EXPIRED_AUTHTOK; break; case 'l': args |= ARG_LOCK_PASSWORD; break; case 'm': mindays = optarg; break; case 'M': maxdays = optarg; break; case 'q': quiet = true; pam_flags |= PAM_SILENT; break; case 's': args |= ARG_PASSWORD_STDIN; break; case 'S': args |= ARG_STATUS_ACCOUNT; break; case 'u': args |= ARG_UNLOCK_PASSWORD; break; case 'v': printf("passwd (%s) %s\n", PACKAGE, VERSION); return 0; case 'w': warndays = optarg; break; default: print_error(); return 1; } } argc -= optind; argv += optind; if (argc == 1) user = argv[0]; if (argc > 1) { fprintf(stderr, "passwd: Too many arguments.\n"); print_error(); return EINVAL; } r = check_and_drop_privs(); if (r < 0) return -r; /* get user account data */ r = pwaccess_get_user_record(user?-1:(int64_t)getuid(), user?user:NULL, &pw, &sp, &complete, &error); if (r < 0) { if (error && streq(error, "org.openSUSE.pwaccess.NoEntryFound")) { if (user) fprintf(stderr, "The user '%s' does not exist.\n", user); else fprintf(stderr, "No user for UID '%u' found.\n", getuid()); } else fprintf(stderr, "Cannot get user account data: %s\n", error?error:strerror(-r)); return -r; } if (pw == NULL) { fprintf(stderr, "ERROR: Unknown user '%s'.\n", user); return ENODATA; } if (!complete) { fprintf(stderr, "Permission denied.\n"); return EPERM; } /* if no user provided on commandline */ if (!user) user = pw->pw_name; if (args & ARG_STATUS_ACCOUNT) return print_account_status(pw, sp); else if (args & (ARG_DELETE_PASSWORD | ARG_EXPIRE | ARG_LOCK_PASSWORD | ARG_UNLOCK_PASSWORD | ARG_STATUS_ACCOUNT)) return modify_account(pw, sp, args, inactive, mindays, maxdays, warndays, quiet); else { pam_handle_t *pamh = NULL; if (args & ARG_PASSWORD_STDIN) { char *ptr; char password[PAM_MAX_RESP_SIZE]; /* independent of crypt type, PAM will not accept anything longer */ r = read(STDIN_FILENO, password, sizeof(password) - 1); if (r < 0) { r = errno; fprintf(stderr, "Error reading from stdin: %s\n", strerror(r)); return r; } password[r] = '\0'; /* Remove trailing \n. */ ptr = strchr(password, '\n'); if (ptr) *ptr = 0; conv.conv = stdin_conv; conv.appdata_ptr = strdup(password); if (conv.appdata_ptr == NULL) return oom(); } r = chauthtok(user, pam_flags); if (!PWACCESS_IS_NOT_RUNNING(-r)) return r; /* Fallback, run PAM stack ourself if we are root. That's needed to allow root to fix his password if system got booted with e.g. init=/bin/bash */ if (geteuid() != 0) return r; /* return error accessing pwupdd */ r = pam_start("passwd", user, &conv, &pamh); if (r != PAM_SUCCESS) { fprintf(stderr, "pam_start(\"passwd\", %s) failed: %s", user, pam_strerror(NULL, r)); return r; } r = pam_chauthtok(pamh, pam_flags); if (r != PAM_SUCCESS) { pam_end(pamh, r); fprintf(stderr, "pam_chauthtok() failed: %s", pam_strerror(NULL, r)); return r; } r = pam_end(pamh, 0); if (r != PAM_SUCCESS) { fprintf(stderr, "pam_end() failed: %s", pam_strerror(NULL, r)); return r; } } return 0; } account-utils-1.0.1/src/pwaccessd.c000066400000000000000000000572021513410760700172240ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "pwaccess.h" #include "basics.h" #include "mkdir_p.h" #include "verify.h" #include "check_caller_perms.h" #include "varlink-service-common.h" #include "read_config.h" #include "context.h" #include "varlink-org.openSUSE.pwaccess.h" #define USEC_PER_SEC ((uint64_t) 1000000ULL) #define DEFAULT_EXIT_USEC (30*USEC_PER_SEC) static int error_user_not_found(sd_varlink *link, int64_t uid, const char *name, int errcode) { if (errcode == 0) { if (uid >= 0) log_msg(LOG_INFO, "User (%" PRId64 ") not found", uid); else { const char *cp; if (!valid_name(name)) cp = ""; else cp = name; log_msg(LOG_INFO, "User '%s' not found", strna(cp)); } return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.NoEntryFound", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false)); } else { _cleanup_free_ char *error = NULL; if (asprintf(&error, "user not found: %s", strerror(errcode)) < 0) error = NULL; log_msg(LOG_ERR, "%s", stroom(error)); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } } struct parameters { int64_t uid; char *name; char *password; bool nullok; }; static void parameters_free(struct parameters *var) { var->name = mfree(var->name); if (var->password) { explicit_bzero(var->password, strlen(var->password)); var->password = mfree(var->password); } } static int vl_method_get_account_name(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t _unused_(flags), void _unused_(*userdata)) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; _cleanup_(parameters_free) struct parameters p = { .uid = -1, .name = NULL, .password = NULL, }; static const sd_json_dispatch_field dispatch_table[] = { { "uid", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct parameters, uid), SD_JSON_MANDATORY}, {} }; struct passwd *pw = NULL; int r; log_msg(LOG_INFO, "Varlink method \"GetAccountName\" called..."); r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); if (r < 0) { log_msg(LOG_ERR, "GetAccountName request: varlink dispatch failed: %s", strerror(-r)); return r; } log_msg(LOG_DEBUG, "GetAccountName(%" PRId64 ")", p.uid); if (p.uid < 0 || p.uid > UINT32_MAX) { log_msg(LOG_ERR, "GetAccountName request: UID is out of range"); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InvalidParameter", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "UID is out of range")); } errno = 0; /* to find out if getpwuid succeed and there is no entry if there was an error */ pw = getpwuid(p.uid); if (pw == NULL) return error_user_not_found(link, p.uid, NULL, errno); return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Success", true), SD_JSON_BUILD_PAIR_STRING("userName", pw->pw_name)); } static int vl_method_get_user_record(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t _unused_(flags), void *userdata) { struct context_t *ctx = userdata; _cleanup_(sd_json_variant_unrefp) sd_json_variant *passwd = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *shadow = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL; _cleanup_(parameters_free) struct parameters p = { .uid = -1, .name = NULL, .password = NULL, }; static const sd_json_dispatch_field dispatch_table[] = { { "uid", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct parameters, uid), 0}, { "userName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct parameters, name), 0}, {} }; struct passwd *pw = NULL; struct spwd *sp = NULL; bool complete = true; uid_t peer_uid; int r; log_msg(LOG_INFO, "Varlink method \"GetUserRecord\" called..."); r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) { log_msg(LOG_ERR, "Failed to get peer UID: %s", strerror(-r)); return r; } r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); if (r < 0) { log_msg(LOG_ERR, "GetUserRecord request: varlink dispatch failed: %s", strerror(-r)); return r; } log_msg(LOG_DEBUG, "GetUserRecord(%" PRId64 ",%s)", p.uid, strna(p.name)); if (p.uid == -1 && isempty(p.name)) { log_msg(LOG_ERR, "GetUserRecord request: no UID nor user name specified"); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InvalidParameter", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "No UID nor user name specified")); } if (p.uid != -1) { if (!isempty(p.name)) { log_msg(LOG_ERR, "GetUserRecord request: UID and user name specified"); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InvalidParameter", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "UID and user name specified")); } if (p.uid < 0 || p.uid > UINT32_MAX) { log_msg(LOG_ERR, "GetUserRecord request: UID is out of range"); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InvalidParameter", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "UID is out of range")); } } errno = 0; /* to find out if getpwuid/getpwnam succeed and there is no entry if there was an error */ if (p.uid != -1) pw = getpwuid(p.uid); else pw = getpwnam(p.name); if (pw == NULL) return error_user_not_found(link, p.uid, p.name, errno); /* Don't return password if query does not come from root and result is not the one of the calling user */ if (!check_caller_perms(peer_uid, pw->pw_uid, ctx->cfg.allow_get_user_record)) { log_msg(LOG_DEBUG, "Peer UID %u is not allowed to access data of '%s'", peer_uid, pw->pw_name); pw->pw_passwd = NULL; complete = false; /* no shadow entries for others */ sp = NULL; } else { /* Get shadow entry */ errno = 0; sp = getspnam(pw->pw_name); if (sp == NULL && errno != 0) { _cleanup_free_ char *error = NULL; if (asprintf(&error, "getspnam() failed: %m") < 0) error = NULL; log_msg(LOG_ERR, "%s", stroom(error)); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } } r = sd_json_variant_merge_objectbo(&passwd, SD_JSON_BUILD_PAIR_STRING("name", pw->pw_name), SD_JSON_BUILD_PAIR_STRING("passwd", pw->pw_passwd), SD_JSON_BUILD_PAIR_INTEGER("UID", pw->pw_uid), SD_JSON_BUILD_PAIR_INTEGER("GID", pw->pw_gid), SD_JSON_BUILD_PAIR_STRING("GECOS", pw->pw_gecos), SD_JSON_BUILD_PAIR_STRING("dir", pw->pw_dir), SD_JSON_BUILD_PAIR_STRING("shell", pw->pw_shell)); if (r < 0) { _cleanup_free_ char *error = NULL; if (asprintf(&error, "JSON merge object passwd failed: %s", strerror(-r)) < 0) error = NULL; log_msg(LOG_ERR, "%s", stroom(error)); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } if (sp) { r = sd_json_variant_merge_objectbo(&shadow, SD_JSON_BUILD_PAIR_STRING("name", sp->sp_namp), SD_JSON_BUILD_PAIR_STRING("passwd", sp->sp_pwdp), SD_JSON_BUILD_PAIR_INTEGER("lstchg", sp->sp_lstchg), SD_JSON_BUILD_PAIR_INTEGER("min", sp->sp_min), SD_JSON_BUILD_PAIR_INTEGER("max", sp->sp_max), SD_JSON_BUILD_PAIR_INTEGER("warn", sp->sp_warn), SD_JSON_BUILD_PAIR_INTEGER("inact", sp->sp_inact), SD_JSON_BUILD_PAIR_INTEGER("expire", sp->sp_expire), SD_JSON_BUILD_PAIR_INTEGER("flag", sp->sp_flag)); if (r < 0) { _cleanup_free_ char *error = NULL; if (asprintf(&error, "JSON merge object shadow failed: %s", strerror(-r)) < 0) error = NULL; log_msg(LOG_ERR, "%s", stroom(error)); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } } r = sd_json_variant_merge_objectbo(&result, SD_JSON_BUILD_PAIR_BOOLEAN("Success", true)); if (r >= 0 && (passwd || shadow)) r = sd_json_variant_merge_objectbo(&result, SD_JSON_BUILD_PAIR_BOOLEAN("Complete", complete)); if (r >= 0 && passwd) r = sd_json_variant_merge_objectbo(&result, SD_JSON_BUILD_PAIR_VARIANT("passwd", passwd)); if (r >= 0 && shadow) r = sd_json_variant_merge_objectbo(&result, SD_JSON_BUILD_PAIR_VARIANT("shadow", shadow)); if (r < 0) { _cleanup_free_ char *error = NULL; if (asprintf(&error, "JSON merge result object failed: %s", strerror(-r)) < 0) error = NULL; log_msg(LOG_ERR, "%s", stroom(error)); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } return sd_varlink_reply(link, result); } static int vl_method_verify_password(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t _unused_(flags), void *userdata) { struct context_t *ctx = userdata; _cleanup_(parameters_free) struct parameters p = { .name = NULL, .password = NULL, .nullok = false }; static const sd_json_dispatch_field dispatch_table[] = { { "userName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct parameters, name), SD_JSON_MANDATORY}, { "password", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct parameters, password), SD_JSON_MANDATORY}, { "nullOK", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(struct parameters, nullok), 0}, {} }; uid_t peer_uid; int r; log_msg(LOG_INFO, "Varlink method \"VerifyPassword\" called..."); r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) { log_msg(LOG_ERR, "Failed to get peer UID: %s", strerror(-r)); return r; } r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); if (r < 0) { log_msg(LOG_ERR, "VerifyPassword request: varlink dispatch failed: %s", strerror(-r)); return r; } if (isempty(p.name)) { log_msg(LOG_ERR, "VerifyPassword request: no user name specified"); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InvalidParameter", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "No user name specified")); } struct passwd *pw = NULL; errno = 0; /* to find out if getpwnam succeed and there is no entry or if there was an error */ pw = getpwnam(p.name); if (pw == NULL) return error_user_not_found(link, -1, p.name, errno); if (!check_caller_perms(peer_uid, pw->pw_uid, ctx->cfg.allow_verify_password)) { _cleanup_free_ char *error = NULL; if (asprintf(&error, "Peer UID %u is not allowed to verify password of '%s'", peer_uid, p.name) < 0) error = NULL; log_msg(LOG_ERR, "%s", stroom(error)); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } const char *hash = pw->pw_passwd; if (is_shadow(pw)) { /* Get shadow entry */ errno = 0; struct spwd *sp = getspnam(pw->pw_name); if (sp == NULL) { /* errno == 0 => no shadow entry exists, do nothing */ if (errno != 0) { _cleanup_free_ char *error = NULL; if (asprintf(&error, "getspnam() failed: %m") < 0) error = NULL; log_msg(LOG_ERR, "%s", stroom(error)); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } } else hash = sp->sp_pwdp; } r = verify_password(hash, p.password, p.nullok); if (r < 0) { _cleanup_free_ char *error = NULL; if (asprintf(&error, "verify_password() failed: %s", strerror(-r)) < 0) error = NULL; log_msg(LOG_ERR, "verify_password: %s", stroom(error)); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } else if (r > 0) { if (r == VERIFY_FAILED) /* password does not match */ { log_msg(LOG_DEBUG, "verify_password (%s): password does not match", strna(pw->pw_name)); return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Success", false)); } else /* libcrypt/internal error */ { const char *error = NULL; if (r == VERIFY_CRYPT_DISABLED) error = "The used salt is disabled in libcrypt"; else if (r == VERIFY_CRYPT_INVALID) error = "The used salt is not supported by libcrypt"; log_msg(LOG_ERR, "verify_password failed: %s", strna(error)); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", strna(error))); } } log_msg(LOG_DEBUG, "verify_password (%s): password matches", strna(pw->pw_name)); return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Success", true)); } static int vl_method_expired_check(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t _unused_(flags), void *userdata) { struct context_t *ctx = userdata; _cleanup_(parameters_free) struct parameters p = { .name = NULL, .password = NULL, }; static const sd_json_dispatch_field dispatch_table[] = { { "userName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct parameters, name), SD_JSON_MANDATORY}, {} }; uid_t peer_uid; long daysleft; bool pwchangeable; int r; log_msg(LOG_INFO, "Varlink method \"ExpiredCheck\" called..."); r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) { log_msg(LOG_ERR, "Failed to get peer UID: %s", strerror(-r)); return r; } r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); if (r < 0) { log_msg(LOG_ERR, "ExpiredCheck request: varlink dispatch failed: %s", strerror(-r)); return r; } if (isempty(p.name)) { log_msg(LOG_ERR, "ExpiredCheck request: no user name specified"); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InvalidParameter", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "No user name specified")); } struct passwd *pw; errno = 0; /* to find out if getpwnam succeed and there is no entry or if there was an error */ pw = getpwnam(p.name); if (pw == NULL) return error_user_not_found(link, -1, p.name, errno); /* Don't verify password if query does not come from root and result is not the one of the calling user */ if (!check_caller_perms(peer_uid, pw->pw_uid, ctx->cfg.allow_expired_check)) { _cleanup_free_ char *error = NULL; if (asprintf(&error, "Peer UID %u is not allowed to access data of '%s'", peer_uid, p.name) < 0) error = NULL; log_msg(LOG_ERR, "ExpiredCheck: %s", stroom(error)); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } /* Get shadow entry */ errno = 0; struct spwd *sp = getspnam(pw->pw_name); if (sp == NULL) { if (errno != 0) { _cleanup_free_ char *error = NULL; if (asprintf(&error, "getspnam() failed: %m") < 0) error = NULL; log_msg(LOG_ERR, "%s", stroom(error)); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } log_msg(LOG_DEBUG, "ExpiredCheck: no shadow entry for %s", pw->pw_name); return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Success", true), SD_JSON_BUILD_PAIR_INTEGER("Expired", PWA_EXPIRED_NO)); } r = expired_check(sp, &daysleft, &pwchangeable); if (r < 0) { _cleanup_free_ char *error = NULL; if (asprintf(&error, "expired_check() failed: %s", strerror(-r)) < 0) error = NULL; log_msg(LOG_ERR, "expired_check: %s", stroom(error)); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } log_msg(LOG_DEBUG, "expired_check(%s): expired: %d, daysleft: %ld", strna(p.name), r, daysleft); return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Success", true), SD_JSON_BUILD_PAIR_INTEGER("DaysLeft", daysleft), SD_JSON_BUILD_PAIR_INTEGER("Expired", r), SD_JSON_BUILD_PAIR_BOOLEAN("PWChangeAble", pwchangeable)); } /* Send a messages to systemd daemon, that inicialization of daemon is finished and daemon is ready to accept connections. */ static void announce_ready (void) { int r = sd_notify (0, "READY=1\n" "STATUS=Processing requests..."); if (r < 0) log_msg (LOG_ERR, "sd_notify(READY) failed: %s", strerror(-r)); } static void announce_stopping (void) { int r = sd_notify (0, "STOPPING=1\n" "STATUS=Shutting down..."); if (r < 0) log_msg (LOG_ERR, "sd_notify(STOPPING) failed: %s", strerror(-r)); } /* event loop which quits after 30 seconds idle time */ #define DEFAULT_EXIT_USEC (30*USEC_PER_SEC) static int varlink_event_loop_with_idle(sd_event *e, sd_varlink_server *s) { int r, code; for (;;) { r = sd_event_get_state(e); if (r < 0) return r; if (r == SD_EVENT_FINISHED) break; r = sd_event_run(e, DEFAULT_EXIT_USEC); if (r < 0) return r; if (r == 0 && (sd_varlink_server_current_connections(s) == 0)) sd_event_exit(e, 0); } r = sd_event_get_exit_code(e, &code); if (r < 0) return r; return code; } static int run_varlink(bool socket_activation, struct context_t *ctx) { int r; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; r = mkdir_p(_VARLINK_PWACCESS_SOCKET_DIR, 0755); if (r < 0) { log_msg(LOG_ERR, "Failed to create directory '"_VARLINK_PWACCESS_SOCKET_DIR"' for Varlink socket: %s", strerror(-r)); return r; } r = sd_event_new(&event); if (r < 0) { log_msg(LOG_ERR, "Failed to create new event: %s", strerror(-r)); return r; } ctx->loop = TAKE_PTR(event); r = sd_varlink_server_new(&varlink_server, SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA|SD_VARLINK_SERVER_INPUT_SENSITIVE); if (r < 0) { log_msg(LOG_ERR, "Failed to allocate varlink server: %s", strerror (-r)); return r; } r = sd_varlink_server_set_description(varlink_server, "pwaccessd"); if (r < 0) { log_msg(LOG_ERR, "Failed to set varlink server description: %s", strerror(-r)); return r; } r = sd_varlink_server_set_info (varlink_server, NULL, PACKAGE" (pwaccessd)", VERSION, "https://github.com/thkukuk/pwaccess"); if (r < 0) return r; r = sd_varlink_server_add_interface (varlink_server, &vl_interface_org_openSUSE_pwaccess); if (r < 0) { log_msg(LOG_ERR, "Failed to add interface: %s", strerror(-r)); return r; } sd_varlink_server_set_userdata(varlink_server, ctx); r = sd_varlink_server_bind_method_many(varlink_server, "org.openSUSE.pwaccess.GetAccountName", vl_method_get_account_name, "org.openSUSE.pwaccess.GetUserRecord", vl_method_get_user_record, "org.openSUSE.pwaccess.VerifyPassword", vl_method_verify_password, "org.openSUSE.pwaccess.ExpiredCheck", vl_method_expired_check, "org.openSUSE.pwaccess.GetEnvironment", vl_method_get_environment, "org.openSUSE.pwaccess.Ping", vl_method_ping, "org.openSUSE.pwaccess.Quit", vl_method_quit, "org.openSUSE.pwaccess.SetLogLevel", vl_method_set_log_level); if (r < 0) { log_msg(LOG_ERR, "Failed to bind Varlink methods: %s", strerror(-r)); return r; } r = sd_varlink_server_attach_event(varlink_server, ctx->loop, SD_EVENT_PRIORITY_NORMAL); if (r < 0) { log_msg(LOG_ERR, "Failed to attach to event: %s", strerror(-r)); return r; } r = sd_varlink_server_listen_auto(varlink_server); if (r < 0) { log_msg (LOG_ERR, "Failed to listen: %s", strerror(-r)); return r; } if (!socket_activation) { r = sd_varlink_server_listen_address(varlink_server, _VARLINK_PWACCESS_SOCKET, 0666); if (r < 0) { log_msg(LOG_ERR, "Failed to bind to Varlink socket: %s", strerror(-r)); return r; } } announce_ready(); if (socket_activation) r = varlink_event_loop_with_idle(ctx->loop, varlink_server); else r = sd_event_loop(ctx->loop); announce_stopping(); return r; } static void print_help(void) { printf("pwaccessd - manage passwd and shadow\n"); printf(" -s, --socket Activation through socket\n"); printf(" -d, --debug Debug mode\n"); printf(" -v, --verbose Verbose logging\n"); printf(" -?, --help Give this help list\n"); printf(" --version Print program version\n"); } static void struct_context_free(struct context_t *var) { struct_config_free(&(var->cfg)); sd_event_unrefp(&(var->loop)); } int main(int argc, char **argv) { int socket_activation = false; _cleanup_(struct_context_free) struct context_t ctx = { {NULL, NULL, NULL}, NULL }; econf_err error = read_config(&ctx.cfg); if (error != ECONF_SUCCESS) log_msg(LOG_NOTICE, "Error reading config file: %s", econf_errString(error)); while (1) { int c; int option_index = 0; static struct option long_options[] = { {"socket", no_argument, NULL, 's'}, {"debug", no_argument, NULL, 'd'}, {"verbose", no_argument, NULL, 'v'}, {"version", no_argument, NULL, '\255'}, {"usage", no_argument, NULL, '?'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, '\0'} }; c = getopt_long (argc, argv, "sdvh?", long_options, &option_index); if (c == (-1)) break; switch (c) { case 's': socket_activation = true; break; case 'd': set_max_log_level(LOG_DEBUG); break; case '?': case 'h': print_help (); return 0; case 'v': set_max_log_level(LOG_INFO); break; case '\255': fprintf (stdout, "pwaccessd (%s) %s\n", PACKAGE, VERSION); return 0; default: print_help (); return 1; } } argc -= optind; argv += optind; if (argc > 1) { fprintf (stderr, "Try `pwaccessd --help' for more information.\n"); return 1; } log_msg (LOG_INFO, "Starting pwaccessd (%s) %s...", PACKAGE, VERSION); int r = run_varlink (socket_activation, &ctx); if (r < 0) { log_msg (LOG_ERR, "ERROR: varlink loop failed: %s", strerror (-r)); return -r; } log_msg (LOG_INFO, "pwaccessd stopped."); return 0; } account-utils-1.0.1/src/pwupdd.c000066400000000000000000001222731513410760700165540ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pwaccess.h" #include "basics.h" #include "mkdir_p.h" #include "varlink-service-common.h" #include "files.h" #include "verify.h" #include "chfn_checks.h" #include "check_caller_perms.h" #include "varlink-org.openSUSE.pwupd.h" static int error_user_not_found(sd_varlink *link, int64_t uid, const char *name) { if (errno == 0) { const char *cp; if (!valid_name(name)) cp = ""; else cp = name; if (uid >= 0) log_msg(LOG_INFO, "User (%" PRId64 "|%s) not found", uid, strna(cp)); else log_msg(LOG_INFO, "User '%s' not found", strna(cp)); return sd_varlink_errorbo(link, "org.openSUSE.pwupd.NoEntryFound", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false)); } else { _cleanup_free_ char *error = NULL; if (asprintf(&error, "user not found: %m") < 0) error = NULL; log_msg(LOG_ERR, "%s", stroom(error)); return sd_varlink_errorbo(link, "org.openSUSE.pwupd.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } } static int return_errno_error(sd_varlink *link, const char *function, int r) { _cleanup_free_ char *error = NULL; const char *varlink_error = "org.openSUSE.pwupd.InternalError"; if (r < 0) r = -r; if (r == EPERM) varlink_error = "org.openSUSE.pwupd.PermissionDenied"; if (asprintf(&error, "%s failed: %s", function, strerror(r)) < 0) error = NULL; log_msg(LOG_ERR, "%s", stroom(error)); return sd_varlink_errorbo(link, varlink_error, SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } struct parameters { const char *pam_service; char *name; char *shell; char *full_name; char *home_phone; char *other; char *room; char *work_phone; char *old_gecos; char *response; int flags; sd_json_variant *content_passwd; sd_json_variant *content_shadow; sd_varlink *link; }; static void parameters_free(struct parameters *var) { var->name = mfree(var->name); var->shell = mfree(var->shell); var->full_name = mfree(var->full_name); var->home_phone = mfree(var->home_phone); var->other = mfree(var->other); var->room = mfree(var->room); var->work_phone = mfree(var->work_phone); var->old_gecos = mfree(var->old_gecos); var->response = mfree(var->response); var->content_passwd = sd_json_variant_unref(var->content_passwd); var->content_shadow = sd_json_variant_unref(var->content_shadow); } static sd_json_variant *send_v = NULL; static pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; static char *answer = NULL; static bool got_answer = false; static bool broadcast_called = false; static int varlink_conv(int num_msg, const struct pam_message **msgm, struct pam_response **response, void *appdata_ptr) { struct parameters *p = appdata_ptr; int r; log_msg(LOG_DEBUG, "varlink_conv with %i messages called", num_msg); assert(p); if (num_msg <= 0) return PAM_CONV_ERR; log_msg(LOG_DEBUG, "style=%i, msg=%s", msgm[0]->msg_style, msgm[0]->msg); for (int count = 0; count < num_msg; ++count) { switch (msgm[count]->msg_style) { case PAM_PROMPT_ECHO_ON: case PAM_PROMPT_ECHO_OFF: pthread_mutex_lock(&mut); send_v = sd_json_variant_unref(send_v); r = sd_json_variant_merge_objectbo(&send_v, SD_JSON_BUILD_PAIR_INTEGER("msg_style", msgm[count]->msg_style), SD_JSON_BUILD_PAIR("message", SD_JSON_BUILD_STRING(msgm[count]->msg))); if (r < 0) log_msg(LOG_ERR, "Failed to build send_v list: %s\n", strerror(-r)); broadcast_called = true; pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mut); *response = calloc(num_msg, sizeof(struct pam_response)); if (*response == NULL) { log_msg(LOG_ERR, "Out of memory!"); return PAM_BUF_ERR; } /* waiting for answer */ pthread_mutex_lock(&mut); log_msg(LOG_DEBUG, "varlink_conv: wait for answer from client"); while (!got_answer) pthread_cond_wait(&cond, &mut); response[0]->resp_retcode = 0; response[0]->resp = TAKE_PTR(answer); got_answer = false; pthread_mutex_unlock(&mut); log_msg(LOG_DEBUG, "varlink_conv: after mutex"); break; case PAM_ERROR_MSG: case PAM_TEXT_INFO: r = sd_varlink_notifybo(p->link, SD_JSON_BUILD_PAIR_INTEGER("msg_style", msgm[count]->msg_style), SD_JSON_BUILD_PAIR("message", SD_JSON_BUILD_STRING(msgm[count]->msg))); sd_varlink_flush(p->link); if (r < 0) { log_msg(LOG_ERR, "Failed to send notify: %s\n", strerror(-r)); return PAM_SYSTEM_ERR; } break; default: log_msg(LOG_ERR, "Unknown msg style: %i\n", msgm[count]->msg_style); return PAM_SYSTEM_ERR; } } return PAM_SUCCESS; } static void * broadcast_and_return(intptr_t r) { pthread_mutex_lock(&mut); broadcast_called = true; pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mut); return (void *)r; } static void * run_pam_auth(void *arg) { struct parameters *param = arg; _cleanup_(parameters_free) struct parameters p = { .pam_service = param->pam_service, .name = param->name, .shell = param->shell, .full_name = param->full_name, .home_phone = param->home_phone, .other = param->other, .room = param->room, .work_phone = param->work_phone, .old_gecos = param->old_gecos, .response = NULL, .flags = param->flags, .content_passwd = NULL, .content_shadow = NULL, .link = param->link, }; const struct pam_conv conv = { varlink_conv, &p, }; pam_handle_t *pamh = NULL; intptr_t r; r = pam_start(p.pam_service, p.name, &conv, &pamh); if (r != PAM_SUCCESS) { log_msg(LOG_ERR, "pam_start(\"%s\", %s) failed: %s", p.pam_service, p.name, pam_strerror(NULL, r)); return broadcast_and_return(r); } r = pam_authenticate(pamh, 0); if (r != PAM_SUCCESS) { pam_end (pamh, r); log_msg(LOG_ERR, "pam_authenticate() failed: %s", pam_strerror(NULL, r)); return broadcast_and_return(r); } r = pam_acct_mgmt(pamh, 0); if (r == PAM_NEW_AUTHTOK_REQD) { r = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); if (r != PAM_SUCCESS) { pam_end (pamh, r); log_msg(LOG_ERR, "pam_chauthtok() failed: %s", pam_strerror(NULL, r)); return broadcast_and_return(r); } } else if (r != PAM_SUCCESS) { pam_end (pamh, r); log_msg(LOG_ERR, "pam_acct_mgmt() failed: %s", pam_strerror(NULL, r)); return broadcast_and_return(r); } r = pam_end(pamh, 0); if (r != PAM_SUCCESS) { log_msg(LOG_ERR, "pam_end() failed: %s", pam_strerror(NULL, r)); return broadcast_and_return(r); } if (!isempty(p.shell)) { struct passwd pw; memset(&pw, 0, sizeof(pw)); pw.pw_name = p.name; pw.pw_shell = p.shell; r = update_passwd(&pw, NULL); if (r < 0) { log_msg(LOG_ERR, "update_passwd() failed: %s", strerror(-r)); return broadcast_and_return(PAM_SYSTEM_ERR); } log_msg(LOG_INFO, "chsh: changed shell for '%s' to '%s'", p.name, p.shell); } else if (p.full_name || p.home_phone || p.other || p.room || p.work_phone) { const char *full_name = NULL; const char *home_phone = NULL; const char *other = NULL; const char *room = NULL; const char *work_phone = NULL; struct passwd pw; char *cp; const char *f; size_t s; _cleanup_free_ char *new_gecos = NULL; /* Split old GECOS field and overwrite single parts */ cp = p.old_gecos; f = strsep(&cp, ","); full_name = f; f = strsep(&cp, ","); room = f; f = strsep(&cp, ","); work_phone = f; f = strsep(&cp, ","); home_phone = f; /* Anything left over is "other". */ other = cp; if (p.full_name != NULL) full_name = p.full_name; if (p.room != NULL) room = p.room; if (p.work_phone != NULL) work_phone = p.work_phone; if (p.home_phone != NULL) home_phone = p.home_phone; if (p.other) other = p.other; if (asprintf(&new_gecos, "%s,%s,%s,%s,%s", strempty(full_name), strempty(room), strempty(work_phone), strempty(home_phone), strempty(other)) < 0) return broadcast_and_return(PAM_BUF_ERR); /* remove trailing ',' */ s = strlen(new_gecos); while (s > 0 && new_gecos[s-1] == ',') { new_gecos[s-1] = '\0'; s--; } memset(&pw, 0, sizeof(pw)); pw.pw_name = p.name; pw.pw_gecos = new_gecos; r = update_passwd(&pw, NULL); if (r < 0) { log_msg(LOG_ERR, "update_passwd() failed: %s", strerror(-r)); return broadcast_and_return(PAM_SYSTEM_ERR); } log_msg(LOG_INFO, "chfn: changed GECOS for '%s' to '%s'", p.name, strempty(new_gecos)); } else log_msg(LOG_INFO, "chfn/chsh: nothing to update"); return broadcast_and_return(PAM_SUCCESS); } static pthread_t pam_thread; static bool pam_thread_is_valid = false; static int thread_is_running(void) { if (pam_thread_is_valid) { int r = pthread_kill(pam_thread, 0); return -r; } return -ENOENT; } static int vl_method_chfn(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t _unused_(flags), void _unused_(*userdata)) { /* don't free via _cleanup_, can be still in use by the pam thread */ struct parameters p = { .pam_service = "pwupd-chfn", .name = NULL, .shell = NULL, .full_name = NULL, .home_phone = NULL, .other = NULL, .room = NULL, .work_phone = NULL, .old_gecos = NULL, .response = NULL, .flags = 0, .content_passwd = NULL, .content_shadow = NULL, .link = link, }; static const sd_json_dispatch_field dispatch_table[] = { { "userName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct parameters, name), SD_JSON_MANDATORY}, { "fullName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct parameters, full_name), SD_JSON_NULLABLE}, { "homePhone", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct parameters, home_phone), SD_JSON_NULLABLE}, { "other", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct parameters, other), SD_JSON_NULLABLE}, { "room", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct parameters, room), SD_JSON_NULLABLE}, { "workPhone", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct parameters, work_phone), SD_JSON_NULLABLE}, {} }; _cleanup_free_ char *error = NULL; struct passwd *pw = NULL; uid_t peer_uid; int r; log_msg(LOG_INFO, "Varlink method \"chfn\" called..."); /* If there is already a thread running, quit with error. The conv method needs to be called to continue. */ r = thread_is_running(); if (r == 0) { log_msg(LOG_ERR, "chfn method called while already running!"); return -EPERM; } errno = 0; /* to find out if getpwnam succeed and there is no entry or if there was an error */ pw = getpwnam(p.name); if (pw == NULL) { r = error_user_not_found(link, -1, p.name); parameters_free(&p); return r; } r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) return return_errno_error(link, "Get peer UID", r); r = sd_varlink_dispatch(p.link, parameters, dispatch_table, &p); if (r < 0) { log_msg(LOG_ERR, "chfn request: varlink dispatch failed: %s", strerror(-r)); return r; } if (isempty(p.name)) { parameters_free(&p); log_msg(LOG_ERR, "chfn request: no user name specified"); return sd_varlink_errorbo(link, "org.openSUSE.pwupd.InvalidParameter", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "No user name specified")); } /* Don't change GECOS if query does not come from root and result is not the one of the calling user */ if (!check_caller_perms(peer_uid, pw->pw_uid, NULL)) { if (asprintf(&error, "Peer UID %u is not allowed to access data of '%s'", peer_uid, p.name) < 0) error = NULL; log_msg(LOG_ERR, "chfn: %s", stroom(error)); parameters_free(&p); return sd_varlink_errorbo(link, "org.openSUSE.pwupd.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } /* Now that we know the caller is allowed to make changes, check if the caller is allowed to change the single fields and the new values are valid */ if (p.full_name) { if (!may_change_field(peer_uid, 'f', &error)) { if (error) log_msg(LOG_ERR, "chfn (full name): %s", error); parameters_free(&p); return return_errno_error(link, "permission check (full name)", -EPERM); } if (!chfn_check_string(p.full_name, ":,=", &error)) { if (error) log_msg(LOG_ERR, "chfn (full name): %s", error); parameters_free(&p); return return_errno_error(link, "character check (full name)", -EINVAL); } } if (p.home_phone) { if (!may_change_field(peer_uid, 'h', &error)) { if (error) log_msg(LOG_ERR, "chfn (home phone): %s", error); parameters_free(&p); return return_errno_error(link, "permission check (home phone)", -EPERM); } if (!chfn_check_string(p.home_phone, ":,=", &error)) { if (error) log_msg(LOG_ERR, "chfn (home phone): %s", error); parameters_free(&p); return return_errno_error(link, "character check (home phone)", -EINVAL); } } if (p.other) { if (!may_change_field(peer_uid, 'o', &error)) { if (error) log_msg(LOG_ERR, "chfn (other): %s", error); parameters_free(&p); return return_errno_error(link, "permission check (other)", -EPERM); } if (!chfn_check_string(p.other, ":", &error)) { if (error) log_msg(LOG_ERR, "chfn (other): %s", error); parameters_free(&p); return return_errno_error(link, "character check (other)", -EINVAL); } } if (p.room) { if (!may_change_field(peer_uid, 'r', &error)) { if (error) log_msg(LOG_ERR, "chfn (room): %s", error); parameters_free(&p); return return_errno_error(link, "permission check (room)", -EPERM); } if (!chfn_check_string(p.room, ":,=", &error)) { if (error) log_msg(LOG_ERR, "chfn (room): %s", error); parameters_free(&p); return return_errno_error(link, "character check (room)", -EINVAL); } } if (p.work_phone) { if (!may_change_field(peer_uid, 'w', &error)) { if (error) log_msg(LOG_ERR, "chfn (work phone): %s", error); parameters_free(&p); return return_errno_error(link, "permission check (work phone)", -EPERM); } if (!chfn_check_string(p.work_phone, ":,=", &error)) { if (error) log_msg(LOG_ERR, "chfn (work phone): %s", error); parameters_free(&p); return return_errno_error(link, "character check (work phone)", -EINVAL); } } p.old_gecos = strdup(strempty(pw->pw_gecos)); if (p.old_gecos == NULL) { parameters_free(&p); return return_errno_error(link, "strdup", -ENOMEM); } /* Run under the UID of the caller, else pam_unix will not ask for old password and pam_rootok will wrongly match. */ if (peer_uid != 0) { log_msg(LOG_DEBUG, "Calling setresuid(%u,0,0)", peer_uid); if (setresuid(peer_uid, 0, 0) != 0) { parameters_free(&p); return return_errno_error(link, "setresuid", errno); } } r = pthread_create(&pam_thread, NULL, &run_pam_auth, &p); if (r != 0) return return_errno_error(link, "pthread_create", r); pam_thread_is_valid = true; pthread_mutex_lock(&mut); log_msg(LOG_DEBUG, "chfn: waiting for PAM thread"); while(!broadcast_called) pthread_cond_wait(&cond, &mut); broadcast_called = false; /* we need input from the user, quit method and send prompt back */ if (send_v != NULL) { r = sd_varlink_reply(link, send_v); pthread_mutex_unlock(&mut); return r; } pthread_mutex_unlock(&mut); intptr_t *thread_res = NULL; r = pthread_join(pam_thread, (void **)&thread_res); if (r != 0) return return_errno_error(link, "pthread_join", r); if (thread_res != PAM_SUCCESS) { int64_t t = (int64_t)thread_res; if (t > 0) { if (asprintf(&error, "PAM authentication failed: %s", pam_strerror(NULL, t)) < 0) error = NULL; } else { if (asprintf(&error, "Updating passwd/shadow failed: %s", strerror(-t)) < 0) error = NULL; } return sd_varlink_errorbo(link, "org.openSUSE.pwupd.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Success", true)); } /* XXX move all code access /etc/shells into one file */ static bool is_known_shell(const char *shell) { _cleanup_(econf_freeFilep) econf_file *key_file = NULL; _cleanup_(econf_freeArrayp) char **keys = NULL; size_t size = 0; econf_err error; error = econf_readConfig(&key_file, NULL /* project */, _PATH_VENDORDIR /* usr_conf_dir */, "shells" /* config_name */, NULL /* config_suffix */, "" /* delim, key only */, "#" /* comment */); if (error != ECONF_SUCCESS) { log_msg(LOG_ERR, "Cannot parse shell files: %s", econf_errString(error)); return false; } error = econf_getKeys(key_file, NULL, &size, &keys); if (error) { log_msg(LOG_ERR, "Cannot evaluate entries in shell files: %s", econf_errString(error)); return false; } for (size_t i = 0; i < size; i++) if (streq(keys[i], shell)) return true; return false; } /* If the shell is completely invalid, print an error and return false. If root changes the shell, print only a warning. Only exception: Invalid characters are always not allowed. */ static bool valid_shell(const char *shell, uid_t uid, char **msg) { assert(msg); /* Keep /etc/passwd clean. This is always required, even for root. */ for (size_t i = 0; i < strlen(shell); i++) { char c = shell[i]; if (iscntrl (c)) { *msg = strdup("Error: control characters are not allowed."); return false; } if (c == ',' || c == ':' || c == '=' || c == '"') { if (asprintf(msg, "Error: '%c' is not allowed.", c) < 0) *msg = NULL; return false; } } /* Check if the shell exists and is known. Error for normal users, warning only for root */ if (*shell != '/') { if (asprintf(msg, "%s: shell must be a full path name.", uid?"Error":"Warning") < 0) *msg = NULL; if (uid) return false; } else if (access(shell, F_OK) < 0) { if (asprintf(msg, "%s: '%s' does not exist.", uid?"Error":"Warning", shell) < 0) *msg = NULL; if (uid) return false; } else if (access(shell, X_OK) < 0) { if (asprintf(msg, "%s: '%s' is not executable.", uid?"Error":"Warning", shell) < 0) *msg = NULL; if (uid) return false; } else if (!is_known_shell(shell)) { if (asprintf(msg, "%s: '%s' is not listed as valid login shell.", uid?"Error":"Warning", shell) < 0) *msg = NULL; if (uid) return false; } return true; } static int vl_method_chsh(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t _unused_(flags), void _unused_(*userdata)) { /* don't free, can be still in use by the pam thread */ struct parameters p = { .pam_service = "pwupd-chsh", .name = NULL, .shell = NULL, .full_name = NULL, .home_phone = NULL, .other = NULL, .room = NULL, .work_phone = NULL, .old_gecos = NULL, .response = NULL, .flags = 0, .content_passwd = NULL, .content_shadow = NULL, .link = link, }; static const sd_json_dispatch_field dispatch_table[] = { { "userName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct parameters, name), SD_JSON_MANDATORY}, { "shell", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct parameters, shell), SD_JSON_MANDATORY}, {} }; uid_t peer_uid; int r; log_msg(LOG_INFO, "Varlink method \"chsh\" called..."); /* If there is already a thread running, quit with error. The conv method needs to be called to continue. */ r = thread_is_running(); if (r == 0) { log_msg(LOG_ERR, "chsh method called while already running!"); return -EPERM; } r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) return return_errno_error(link, "Get peer UID", r); r = sd_varlink_dispatch(p.link, parameters, dispatch_table, &p); if (r < 0) return return_errno_error(link, "chsh request: varlink dispatch", r); if (isempty(p.name)) { log_msg(LOG_ERR, "chsh request: no user name specified"); return sd_varlink_errorbo(link, "org.openSUSE.pwupd.InvalidParameter", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "No user name specified")); } struct passwd *pw = NULL; errno = 0; /* to find out if getpwnam succeed and there is no entry or if there was an error */ pw = getpwnam(p.name); if (pw == NULL) { r = error_user_not_found(link, -1, p.name); parameters_free(&p); return r; } /* Don't change shell if query does not come from root and result is not the one of the calling user */ if (!check_caller_perms(peer_uid, pw->pw_uid, NULL)) { _cleanup_free_ char *error = NULL; if (asprintf(&error, "Peer UID %u is not allowed to access data of '%s'", peer_uid, p.name) < 0) error = NULL; log_msg(LOG_ERR, "chsh: %s", stroom(error)); parameters_free(&p); return sd_varlink_errorbo(link, "org.openSUSE.pwupd.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } _cleanup_free_ char *msg = NULL; if (!valid_shell(p.shell, peer_uid, &msg)) { log_msg(LOG_ERR, "%s", stroom(msg)); parameters_free(&p); return sd_varlink_errorbo(link, "org.openSUSE.pwupd.InvalidShell", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(msg))); } if (msg) /* ignore if it fails or not */ sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_INTEGER("msg_style", PAM_TEXT_INFO), SD_JSON_BUILD_PAIR_STRING("message", msg)); /* Run under the UID of the caller, else pam_unix will not ask for old password and pam_rootok will wrongly match. */ if (peer_uid != 0) { log_msg(LOG_DEBUG, "Calling setresuid(%u,0,0)", peer_uid); if (setresuid(peer_uid, 0, 0) != 0) { parameters_free(&p); return return_errno_error(link, "setresuid", errno); } } r = pthread_create(&pam_thread, NULL, &run_pam_auth, &p); if (r != 0) return return_errno_error(link, "pthread_create", r); pam_thread_is_valid = true; pthread_mutex_lock(&mut); log_msg(LOG_DEBUG, "chsh: waiting for PAM thread"); while(!broadcast_called) pthread_cond_wait(&cond, &mut); broadcast_called = false; /* we need input from the user, quit method and send prompt back */ if (send_v != NULL) { r = sd_varlink_reply(link, send_v); pthread_mutex_unlock(&mut); return r; } pthread_mutex_unlock(&mut); intptr_t *thread_res = NULL; r = pthread_join(pam_thread, (void **)&thread_res); if (r != 0) return return_errno_error(link, "pthread_join", r); if (thread_res != PAM_SUCCESS) { _cleanup_free_ char *error = NULL; int64_t t = (int64_t)thread_res; if (t > 0) { if (asprintf(&error, "PAM authentication failed: %s", pam_strerror(NULL, t)) < 0) error = NULL; } else { if (asprintf(&error, "Updating passwd/shadow failed: %s", strerror(-t)) < 0) error = NULL; } return sd_varlink_errorbo(link, "org.openSUSE.pwupd.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Success", true)); } static void * run_pam_chauthtok(void *arg) { struct parameters *param = arg; _cleanup_(parameters_free) struct parameters p = { .pam_service = param->pam_service, .name = param->name, .shell = param->shell, .full_name = param->full_name, .home_phone = param->home_phone, .other = param->other, .room = param->room, .work_phone = param->work_phone, .old_gecos = param->old_gecos, .response = NULL, .flags = param->flags, .content_passwd = NULL, .content_shadow = NULL, .link = param->link, }; const struct pam_conv conv = { varlink_conv, &p, }; pam_handle_t *pamh = NULL; intptr_t r; r = pam_start(p.pam_service, p.name, &conv, &pamh); if (r != PAM_SUCCESS) { log_msg(LOG_ERR, "pam_start(\"%s\", %s) failed: %s", p.pam_service, p.name, pam_strerror(NULL, r)); return broadcast_and_return(r); } log_msg(LOG_DEBUG, "pam_chauthtok(pamh, %i)", p.flags); r = pam_chauthtok(pamh, p.flags); if (r != PAM_SUCCESS) { pam_end(pamh, r); log_msg(LOG_ERR, "pam_chauthtok() failed: %s", pam_strerror(NULL, r)); return broadcast_and_return(r); } r = pam_end(pamh, 0); if (r != PAM_SUCCESS) { log_msg(LOG_ERR, "pam_end() failed: %s", pam_strerror(NULL, r)); return broadcast_and_return(r); } return broadcast_and_return(r); } static int vl_method_chauthtok(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t _unused_(flags), void _unused_(*userdata)) { /* don't free, can be still in use by the pam thread */ struct parameters p = { .pam_service = "pwupd-passwd", .name = NULL, .shell = NULL, .full_name = NULL, .home_phone = NULL, .other = NULL, .room = NULL, .work_phone = NULL, .old_gecos = NULL, .response = NULL, .flags = 0, .content_passwd = NULL, .content_shadow = NULL, .link = link, }; static const sd_json_dispatch_field dispatch_table[] = { { "userName", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct parameters, name), SD_JSON_MANDATORY}, { "flags", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int, offsetof(struct parameters, flags), 0}, {} }; uid_t peer_uid; int r; log_msg(LOG_INFO, "Varlink method \"chauthtok\" called..."); /* If there is already a thread running, quit with error. The conv method needs to be called to continue. */ r = thread_is_running(); if (r == 0) { log_msg(LOG_ERR, "chauthtok method called while already running!"); return -EPERM; } r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) return return_errno_error(link, "Get peer UID", r); r = sd_varlink_dispatch(p.link, parameters, dispatch_table, &p); if (r < 0) return return_errno_error(link, "chauthtok: varlink dispatch", r); if (isempty(p.name)) { log_msg(LOG_ERR, "chauthtok request: no user name specified"); return sd_varlink_errorbo(link, "org.openSUSE.pwupd.InvalidParameter", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", "No user name specified")); } struct passwd *pw = NULL; errno = 0; /* to find out if getpwnam succeed and there is no entry or if there was an error */ pw = getpwnam(p.name); if (pw == NULL) { r = error_user_not_found(link, -1, p.name); parameters_free(&p); return r; } /* Don't change password if query does not come from root and result is not the one of the calling user */ if (!check_caller_perms(peer_uid, pw->pw_uid, NULL)) { _cleanup_free_ char *error = NULL; if (asprintf(&error, "Peer UID %u is not allowed to access data of '%s'", peer_uid, p.name) < 0) error = NULL; log_msg(LOG_ERR, "chauthtok: %s", stroom(error)); parameters_free(&p); return sd_varlink_errorbo(link, "org.openSUSE.pwupd.PermissionDenied", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } /* Run under the UID of the caller, else pam_unix will not ask for old password and pam_rootok will wrongly match. */ if (peer_uid != 0) { log_msg(LOG_DEBUG, "Calling setresuid(%u,0,0)", peer_uid); if (setresuid(peer_uid, 0, 0) != 0) { parameters_free(&p); return return_errno_error(link, "setresuid", errno); } } r = pthread_create(&pam_thread, NULL, &run_pam_chauthtok, &p); if (r != 0) return return_errno_error(link, "pthread_create", errno); pam_thread_is_valid = true; pthread_mutex_lock(&mut); log_msg(LOG_DEBUG, "chauthtok: waiting for PAM thread"); while(!broadcast_called) pthread_cond_wait(&cond, &mut); broadcast_called = false; /* we need input from the user, quit method and send prompt back */ if (send_v != NULL) { r = sd_varlink_reply(link, send_v); pthread_mutex_unlock(&mut); return r; } pthread_mutex_unlock(&mut); intptr_t *thread_res = NULL; r = pthread_join(pam_thread, (void **)&thread_res); if (r != 0) return return_errno_error(link, "pthread_join", errno); if (thread_res != PAM_SUCCESS) { _cleanup_free_ char *error = NULL; int64_t t = (int64_t)thread_res; if (asprintf(&error, "PAM authentication failed: %s", pam_strerror(NULL, t)) < 0) error = NULL; return sd_varlink_errorbo(link, "org.openSUSE.pwupd.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Success", true)); } static int vl_method_conv(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t _unused_(flags), void _unused_(*userdata)) { _cleanup_(parameters_free) struct parameters p = { .name = NULL, .shell = NULL, .full_name = NULL, .home_phone = NULL, .other = NULL, .room = NULL, .work_phone = NULL, .old_gecos = NULL, .response = NULL, .flags = 0, .content_passwd = NULL, .content_shadow = NULL, .link = link, }; static const sd_json_dispatch_field dispatch_table[] = { { "response", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct parameters, response), SD_JSON_NULLABLE}, {} }; int r; log_msg(LOG_INFO, "Varlink method \"conv\" called..."); /* make sure there is a pam_start() thread running! */ r = thread_is_running(); if (r != 0) return return_errno_error(link, "Finding PAM thread", r); r = sd_varlink_dispatch(p.link, parameters, dispatch_table, &p); if (r < 0) return return_errno_error(link, "Conv request: varlink dispatch", r); /* set pam_response */ pthread_mutex_lock(&mut); log_msg(LOG_DEBUG, "method_conv: set response and send cond_broadcast"); send_v = sd_json_variant_unref(send_v); if (p.response) answer = strdup(p.response); else answer = NULL; got_answer = true; /* inform PAM thread about answer */ pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mut); /* wait for next PAM_PROMPT_ECHO_* message or exit */ pthread_mutex_lock(&mut); log_msg(LOG_DEBUG, "method_conv: waiting for PAM thread"); while(!broadcast_called) pthread_cond_wait(&cond, &mut); broadcast_called = false; /* we need input from the user, quit method and send prompt back */ if (send_v != NULL) { r = sd_varlink_reply(link, send_v); pthread_mutex_unlock(&mut); return r; } pthread_mutex_unlock(&mut); intptr_t *thread_res = NULL; r = pthread_join(pam_thread, (void **)&thread_res); if (r != 0) return return_errno_error(link, "pthread_join", r); if (thread_res != PAM_SUCCESS) { _cleanup_free_ char *error = NULL; int64_t t = (int64_t)thread_res; if (asprintf(&error, "Password change aborted: %s", pam_strerror(NULL, t)) < 0) error = NULL; log_msg(LOG_ERR, "%s", stroom(error)); return sd_varlink_errorbo(link, "org.openSUSE.pwupd.PasswordChangeAborted", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false), SD_JSON_BUILD_PAIR_STRING("ErrorMsg", stroom(error))); } return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Success", true)); } static int vl_method_UpdatePasswdShadow(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t _unused_(flags), void _unused_(*userdata)) { _cleanup_(parameters_free) struct parameters p = { .name = NULL, .shell = NULL, .full_name = NULL, .home_phone = NULL, .other = NULL, .room = NULL, .work_phone = NULL, .old_gecos = NULL, .response = NULL, .flags = 0, .content_passwd = NULL, .content_shadow = NULL, .link = link, }; static const sd_json_dispatch_field dispatch_table[] = { { "passwd", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant, offsetof(struct parameters, content_passwd), SD_JSON_NULLABLE }, { "shadow", SD_JSON_VARIANT_OBJECT, sd_json_dispatch_variant, offsetof(struct parameters, content_shadow), SD_JSON_NULLABLE }, {} }; static const sd_json_dispatch_field dispatch_passwd_table[] = { { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct passwd, pw_name), SD_JSON_MANDATORY }, { "passwd", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct passwd, pw_passwd), SD_JSON_NULLABLE }, { "UID", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int, offsetof(struct passwd, pw_uid), SD_JSON_MANDATORY }, { "GID", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int, offsetof(struct passwd, pw_gid), SD_JSON_MANDATORY }, { "GECOS", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct passwd, pw_gecos), SD_JSON_NULLABLE }, { "dir", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct passwd, pw_dir), SD_JSON_NULLABLE }, { "shell", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct passwd, pw_shell), SD_JSON_NULLABLE }, {} }; static const sd_json_dispatch_field dispatch_shadow_table[] = { { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct spwd, sp_namp), SD_JSON_MANDATORY }, { "passwd", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(struct spwd, sp_pwdp), SD_JSON_NULLABLE }, { "lstchg", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct spwd, sp_lstchg), 0 }, { "min", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct spwd, sp_min), 0 }, { "max", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct spwd, sp_max), 0 }, { "warn", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct spwd, sp_warn), 0 }, { "inact", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct spwd, sp_inact), 0 }, { "expire", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct spwd, sp_expire), 0 }, { "flag", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int64, offsetof(struct spwd, sp_flag), 0 }, {} }; _cleanup_(struct_passwd_freep) struct passwd *pw = NULL; _cleanup_(struct_shadow_freep) struct spwd *sp = NULL; uid_t peer_uid; int r; log_msg(LOG_INFO, "Varlink method \"UpdatePasswdShadow\" called..."); r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) return return_errno_error(link, "Get peer UID", r); if (peer_uid != 0) return return_errno_error(link, "UpdatePasswdShadow", -EPERM); r = sd_varlink_dispatch(p.link, parameters, dispatch_table, &p); if (r < 0) return return_errno_error(link, "UpdatePasswdShadow - varlink dispatch", r); if (sd_json_variant_is_null(p.content_passwd)) { log_msg(LOG_ERR, "UpdatePasswdShadow request: no entry found\n"); return 0; } if (!sd_json_variant_is_null(p.content_passwd) && sd_json_variant_elements(p.content_passwd) > 0) { pw = calloc(1, sizeof(struct passwd)); if (pw == NULL) return -ENOMEM; r = sd_json_dispatch(p.content_passwd, dispatch_passwd_table, SD_JSON_ALLOW_EXTENSIONS, pw); if (r < 0) return return_errno_error(link, "Parsing JSON passwd entry", r); } if (!sd_json_variant_is_null(p.content_shadow) && sd_json_variant_elements(p.content_shadow) > 0) { sp = calloc(1, sizeof(struct spwd)); if (sp == NULL) return -ENOMEM; r = sd_json_dispatch(p.content_shadow, dispatch_shadow_table, SD_JSON_ALLOW_EXTENSIONS, sp); if (r < 0) return return_errno_error(link, "Parsing JSON shadow entry", r); } /* Check that pw->pw_name and sp->sp_namp are identical if both are provided */ if (pw && sp && !streq(pw->pw_name, sp->sp_namp)) return return_errno_error(link, "Check for identical account names", -EINVAL); if (pw) { r = update_passwd(pw, NULL); if (r < 0) return return_errno_error(link, "Update of passwd", r); } if (sp) { r = update_shadow(sp, NULL); if (r < 0) return return_errno_error(link, "Update of shadow", r); } return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Success", true)); } static int run_varlink (void) { int r; _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; r = mkdir_p(_VARLINK_PWUPD_SOCKET_DIR, 0755); if (r < 0) { log_msg(LOG_ERR, "Failed to create directory '"_VARLINK_PWUPD_SOCKET_DIR"' for Varlink socket: %s", strerror(-r)); return r; } r = sd_varlink_server_new (&varlink_server, SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA|SD_VARLINK_SERVER_INPUT_SENSITIVE); if (r < 0) { log_msg (LOG_ERR, "Failed to allocate varlink server: %s", strerror (-r)); return r; } r = sd_varlink_server_set_description (varlink_server, "pwupdd"); if (r < 0) { log_msg (LOG_ERR, "Failed to set varlink server description: %s", strerror (-r)); return r; } r = sd_varlink_server_set_info (varlink_server, NULL, PACKAGE" (pwupdd)", VERSION, "https://github.com/thkukuk/pwaccess"); if (r < 0) return r; r = sd_varlink_server_add_interface (varlink_server, &vl_interface_org_openSUSE_pwupd); if (r < 0) { log_msg(LOG_ERR, "Failed to add interface: %s", strerror(-r)); return r; } r = sd_varlink_server_bind_method_many (varlink_server, "org.openSUSE.pwupd.Chauthtok", vl_method_chauthtok, "org.openSUSE.pwupd.Chfn", vl_method_chfn, "org.openSUSE.pwupd.Chsh", vl_method_chsh, "org.openSUSE.pwupd.Conv", vl_method_conv, "org.openSUSE.pwupd.UpdatePasswdShadow", vl_method_UpdatePasswdShadow, "org.openSUSE.pwupd.GetEnvironment", vl_method_get_environment, "org.openSUSE.pwupd.Ping", vl_method_ping, "org.openSUSE.pwupd.Quit", vl_method_quit, "org.openSUSE.pwupd.SetLogLevel", vl_method_set_log_level); if (r < 0) { log_msg(LOG_ERR, "Failed to bind Varlink methods: %s", strerror(-r)); return r; } r = sd_varlink_server_loop_auto(varlink_server); if (r == -EPERM) { log_msg(LOG_ERR, "Invoked by unprivileged Varlink peer, refusing."); return r; } if (r < 0) { log_msg(LOG_ERR, "Failed to run Varlink event loop: %s", strerror(-r)); return r; } return 0; } static void print_help (void) { printf("pwupd - manage updating passwd and shadow entries\n"); printf(" -d, --debug Debug mode\n"); printf(" -v, --verbose Verbose logging\n"); printf(" -?, --help Give this help list\n"); printf(" --version Print program version\n"); } int main (int argc, char **argv) { while (1) { int c; int option_index = 0; static struct option long_options[] = { {"socket", no_argument, NULL, 's'}, {"debug", no_argument, NULL, 'd'}, {"verbose", no_argument, NULL, 'v'}, {"version", no_argument, NULL, '\255'}, {"usage", no_argument, NULL, '?'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, '\0'} }; c = getopt_long (argc, argv, "sdvh?", long_options, &option_index); if (c == (-1)) break; switch (c) { case 'd': set_max_log_level(LOG_DEBUG); break; case '?': case 'h': print_help (); return 0; case 'v': set_max_log_level(LOG_INFO); break; case '\255': fprintf (stdout, "pwupdd (%s) %s\n", PACKAGE, VERSION); return 0; default: print_help (); return 1; } } argc -= optind; argv += optind; if (argc > 1) { fprintf (stderr, "Try `pwupdd --help' for more information.\n"); return 1; } log_msg (LOG_INFO, "Starting pwupdd (%s) %s...", PACKAGE, VERSION); int r = run_varlink (); if (r < 0) return -r; log_msg (LOG_INFO, "pwupdd stopped."); return 0; } account-utils-1.0.1/src/varlink-org.openSUSE.newidmapd.c000066400000000000000000000062261513410760700231120ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include "varlink-org.openSUSE.pwaccess.h" static SD_VARLINK_DEFINE_STRUCT_TYPE(MapRange, SD_VARLINK_FIELD_COMMENT("struct map_range"), SD_VARLINK_DEFINE_FIELD(upper, SD_VARLINK_INT, 0), SD_VARLINK_DEFINE_FIELD(lower, SD_VARLINK_INT, 0), SD_VARLINK_DEFINE_FIELD(count, SD_VARLINK_INT, 0)); static SD_VARLINK_DEFINE_METHOD( WriteMappings, SD_VARLINK_FIELD_COMMENT("PID for which to set the map range"), SD_VARLINK_DEFINE_INPUT(PID, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("Which map to use: 'uid_map' or 'gid_map'"), SD_VARLINK_DEFINE_INPUT(Map, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("The map ranges"), SD_VARLINK_DEFINE_INPUT_BY_TYPE(MapRanges, MapRange, SD_VARLINK_ARRAY), SD_VARLINK_FIELD_COMMENT("If call succeeded"), SD_VARLINK_DEFINE_OUTPUT(Success, SD_VARLINK_BOOL, 0), SD_VARLINK_FIELD_COMMENT("Error Message"), SD_VARLINK_DEFINE_OUTPUT(ErrorMsg, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( Quit, SD_VARLINK_FIELD_COMMENT("Stop the daemon"), SD_VARLINK_DEFINE_INPUT(ExitCode, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_OUTPUT(Success, SD_VARLINK_BOOL, 0)); static SD_VARLINK_DEFINE_METHOD( Ping, SD_VARLINK_FIELD_COMMENT("Check if service is alive"), SD_VARLINK_DEFINE_OUTPUT(Alive, SD_VARLINK_BOOL, 0)); static SD_VARLINK_DEFINE_METHOD( SetLogLevel, SD_VARLINK_FIELD_COMMENT("The maximum log level, using BSD syslog log level integers."), SD_VARLINK_DEFINE_INPUT(Level, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( GetEnvironment, SD_VARLINK_FIELD_COMMENT("Returns the current environment block, i.e. the contents of environ[]."), SD_VARLINK_DEFINE_OUTPUT(Environment, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY)); static SD_VARLINK_DEFINE_ERROR(PermissionDenied); static SD_VARLINK_DEFINE_ERROR(InvalidParameter); static SD_VARLINK_DEFINE_ERROR(InternalError); SD_VARLINK_DEFINE_INTERFACE( org_openSUSE_newidmapd, "org.openSUSE.newidmapd", SD_VARLINK_INTERFACE_COMMENT("newidmapd control APIs"), SD_VARLINK_SYMBOL_COMMENT("Describe map_range entry"), &vl_type_MapRange, SD_VARLINK_SYMBOL_COMMENT("Set map ranges"), &vl_method_WriteMappings, SD_VARLINK_SYMBOL_COMMENT("Stop the daemon"), &vl_method_Quit, SD_VARLINK_SYMBOL_COMMENT("Check if the service is running."), &vl_method_Ping, SD_VARLINK_SYMBOL_COMMENT("Set the maximum log level."), &vl_method_SetLogLevel, SD_VARLINK_SYMBOL_COMMENT("Get current environment block."), &vl_method_GetEnvironment, SD_VARLINK_SYMBOL_COMMENT("Permission Denied"), &vl_error_PermissionDenied, SD_VARLINK_SYMBOL_COMMENT("Invalid parameter for varlink function call"), &vl_error_InvalidParameter, SD_VARLINK_SYMBOL_COMMENT("Internal Error"), &vl_error_InternalError); account-utils-1.0.1/src/varlink-org.openSUSE.newidmapd.h000066400000000000000000000002471513410760700231140ustar00rootroot00000000000000//SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include extern const sd_varlink_interface vl_interface_org_openSUSE_newidmapd; account-utils-1.0.1/src/varlink-org.openSUSE.pwaccess.c000066400000000000000000000157501513410760700227540ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include "varlink-org.openSUSE.pwaccess.h" static SD_VARLINK_DEFINE_STRUCT_TYPE(PasswdEntry, SD_VARLINK_FIELD_COMMENT("User's login name"), SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, 0), SD_VARLINK_DEFINE_FIELD(passwd, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(UID, SD_VARLINK_INT, 0), SD_VARLINK_DEFINE_FIELD(GID, SD_VARLINK_INT, 0), SD_VARLINK_DEFINE_FIELD(GECOS, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(dir, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(shell, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_STRUCT_TYPE(ShadowEntry, SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, 0), SD_VARLINK_DEFINE_FIELD(passwd, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(lstchg, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(min, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(max, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(warn, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(inact, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(expire, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(flag, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( GetAccountName, SD_VARLINK_FIELD_COMMENT("The numeric 32bit UNIX UID of the account"), SD_VARLINK_DEFINE_INPUT(uid, SD_VARLINK_INT, 0), SD_VARLINK_FIELD_COMMENT("The account name of the UID."), SD_VARLINK_DEFINE_OUTPUT(userName, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("If call succeeded"), SD_VARLINK_DEFINE_OUTPUT(Success, SD_VARLINK_BOOL, 0), SD_VARLINK_FIELD_COMMENT("Error Message"), SD_VARLINK_DEFINE_OUTPUT(ErrorMsg, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( GetUserRecord, SD_VARLINK_FIELD_COMMENT("The numeric 32bit UNIX UID of the record, if look-up by UID is desired."), SD_VARLINK_DEFINE_INPUT(uid, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The UNIX user name of the record, if look-up by name is desired."), SD_VARLINK_DEFINE_INPUT(userName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("passwd entry"), SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(passwd, PasswdEntry, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("shadow entry"), SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(shadow, ShadowEntry, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("If all data got replied (depends on UID)"), SD_VARLINK_DEFINE_OUTPUT(Complete, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("If call succeeded"), SD_VARLINK_DEFINE_OUTPUT(Success, SD_VARLINK_BOOL, 0), SD_VARLINK_FIELD_COMMENT("Error Message"), SD_VARLINK_DEFINE_OUTPUT(ErrorMsg, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( GetGroupRecord, SD_VARLINK_FIELD_COMMENT("The numeric 32bit UNIX GID of the record, if look-up by GID is desired."), SD_VARLINK_DEFINE_INPUT(gid, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The UNIX group name of the record, if look-up by name is desired."), SD_VARLINK_DEFINE_INPUT(groupName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( VerifyPassword, SD_VARLINK_FIELD_COMMENT("The account of the user to verify the password."), SD_VARLINK_DEFINE_INPUT(userName, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("The password of the user to verify."), SD_VARLINK_DEFINE_INPUT(password, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("If empty password is ok, default false"), SD_VARLINK_DEFINE_INPUT(nullOK, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( ExpiredCheck, SD_VARLINK_FIELD_COMMENT("The account to check if expired."), SD_VARLINK_DEFINE_INPUT(userName, SD_VARLINK_STRING, 0)); static SD_VARLINK_DEFINE_METHOD( Quit, SD_VARLINK_FIELD_COMMENT("Stop the daemon"), SD_VARLINK_DEFINE_INPUT(ExitCode, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_OUTPUT(Success, SD_VARLINK_BOOL, 0)); static SD_VARLINK_DEFINE_METHOD( Ping, SD_VARLINK_FIELD_COMMENT("Check if service is alive"), SD_VARLINK_DEFINE_OUTPUT(Alive, SD_VARLINK_BOOL, 0)); static SD_VARLINK_DEFINE_METHOD( SetLogLevel, SD_VARLINK_FIELD_COMMENT("The maximum log level, using BSD syslog log level integers."), SD_VARLINK_DEFINE_INPUT(Level, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( GetEnvironment, SD_VARLINK_FIELD_COMMENT("Returns the current environment block, i.e. the contents of environ[]."), SD_VARLINK_DEFINE_OUTPUT(Environment, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY)); static SD_VARLINK_DEFINE_ERROR(NoEntryFound); static SD_VARLINK_DEFINE_ERROR(InvalidParameter); static SD_VARLINK_DEFINE_ERROR(InternalError); SD_VARLINK_DEFINE_INTERFACE( org_openSUSE_pwaccess, "org.openSUSE.pwaccess", SD_VARLINK_INTERFACE_COMMENT("PWAccessD control APIs"), SD_VARLINK_SYMBOL_COMMENT("Describe passwd entry"), &vl_type_PasswdEntry, SD_VARLINK_SYMBOL_COMMENT("Describe shadow entry"), &vl_type_ShadowEntry, SD_VARLINK_SYMBOL_COMMENT("Get account name for UID"), &vl_method_GetAccountName, SD_VARLINK_SYMBOL_COMMENT("Get user entries from passwd and shadow"), &vl_method_GetUserRecord, SD_VARLINK_SYMBOL_COMMENT("Get group entries from group and gshadow"), &vl_method_GetGroupRecord, SD_VARLINK_SYMBOL_COMMENT("Verify password of account"), &vl_method_VerifyPassword, SD_VARLINK_SYMBOL_COMMENT("Check if account is expired"), &vl_method_ExpiredCheck, SD_VARLINK_SYMBOL_COMMENT("Stop the daemon"), &vl_method_Quit, SD_VARLINK_SYMBOL_COMMENT("Check if the service is running."), &vl_method_Ping, SD_VARLINK_SYMBOL_COMMENT("Set the maximum log level."), &vl_method_SetLogLevel, SD_VARLINK_SYMBOL_COMMENT("Get current environment block."), &vl_method_GetEnvironment, SD_VARLINK_SYMBOL_COMMENT("No entry found"), &vl_error_NoEntryFound, SD_VARLINK_SYMBOL_COMMENT("Invalid parameter for varlink function call"), &vl_error_InvalidParameter, SD_VARLINK_SYMBOL_COMMENT("Internal Error"), &vl_error_InternalError); account-utils-1.0.1/src/varlink-org.openSUSE.pwaccess.h000066400000000000000000000002461513410760700227530ustar00rootroot00000000000000//SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include extern const sd_varlink_interface vl_interface_org_openSUSE_pwaccess; account-utils-1.0.1/src/varlink-org.openSUSE.pwupd.c000066400000000000000000000154601513410760700223010ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include "varlink-org.openSUSE.pwupd.h" static SD_VARLINK_DEFINE_STRUCT_TYPE(PasswdEntry, SD_VARLINK_FIELD_COMMENT("User's login name"), SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, 0), SD_VARLINK_DEFINE_FIELD(passwd, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(UID, SD_VARLINK_INT, 0), SD_VARLINK_DEFINE_FIELD(GID, SD_VARLINK_INT, 0), SD_VARLINK_DEFINE_FIELD(GECOS, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(dir, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(shell, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_STRUCT_TYPE(ShadowEntry, SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, 0), SD_VARLINK_DEFINE_FIELD(passwd, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(lstchg, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(min, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(max, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(warn, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(inact, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(expire, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_FIELD(flag, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( Chauthtok, SD_VARLINK_FIELD_COMMENT("The account of the user to change the password."), SD_VARLINK_DEFINE_INPUT(userName, SD_VARLINK_STRING, 0), SD_VARLINK_DEFINE_INPUT(flags, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( Chfn, SD_VARLINK_FIELD_COMMENT("The account of the user to change the GECOS information."), SD_VARLINK_DEFINE_INPUT(userName, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("The new full name."), SD_VARLINK_DEFINE_INPUT(fullName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The new room number."), SD_VARLINK_DEFINE_INPUT(room, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The new work phone number."), SD_VARLINK_DEFINE_INPUT(workPhone, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The new private phone number."), SD_VARLINK_DEFINE_INPUT(homePhone, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("The new other field."), SD_VARLINK_DEFINE_INPUT(other, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( Chsh, SD_VARLINK_FIELD_COMMENT("The account of the user to change the shell."), SD_VARLINK_DEFINE_INPUT(userName, SD_VARLINK_STRING, 0), SD_VARLINK_FIELD_COMMENT("The new shell of the user."), SD_VARLINK_DEFINE_INPUT(shell, SD_VARLINK_STRING, 0)); static SD_VARLINK_DEFINE_METHOD( Conv, SD_VARLINK_FIELD_COMMENT("Response for PAM_PROMPT_ECHO_*."), SD_VARLINK_DEFINE_INPUT(response, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( UpdatePasswdShadow, SD_VARLINK_FIELD_COMMENT("Update passwd and shadow entries."), SD_VARLINK_FIELD_COMMENT("passwd entry"), SD_VARLINK_DEFINE_INPUT_BY_TYPE(passwd, PasswdEntry, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("shadow entry"), SD_VARLINK_DEFINE_INPUT_BY_TYPE(shadow, ShadowEntry, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( Quit, SD_VARLINK_FIELD_COMMENT("Stop the daemon"), SD_VARLINK_DEFINE_INPUT(ExitCode, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_OUTPUT(Success, SD_VARLINK_BOOL, 0)); static SD_VARLINK_DEFINE_METHOD( Ping, SD_VARLINK_FIELD_COMMENT("Check if service is alive"), SD_VARLINK_DEFINE_OUTPUT(Alive, SD_VARLINK_BOOL, 0)); static SD_VARLINK_DEFINE_METHOD( SetLogLevel, SD_VARLINK_FIELD_COMMENT("The maximum log level, using BSD syslog log level integers."), SD_VARLINK_DEFINE_INPUT(Level, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD( GetEnvironment, SD_VARLINK_FIELD_COMMENT("Returns the current environment block, i.e. the contents of environ[]."), SD_VARLINK_DEFINE_OUTPUT(Environment, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY)); static SD_VARLINK_DEFINE_ERROR(NoEntryFound); static SD_VARLINK_DEFINE_ERROR(InvalidParameter); static SD_VARLINK_DEFINE_ERROR(InternalError); static SD_VARLINK_DEFINE_ERROR(AuthenticationFailed); static SD_VARLINK_DEFINE_ERROR(InvalidShell); static SD_VARLINK_DEFINE_ERROR(PasswordChangeAborted); static SD_VARLINK_DEFINE_ERROR(PermissionDenied); SD_VARLINK_DEFINE_INTERFACE( org_openSUSE_pwupd, "org.openSUSE.pwupd", SD_VARLINK_INTERFACE_COMMENT("PWUpdD control APIs"), SD_VARLINK_SYMBOL_COMMENT("Describe passwd entry"), &vl_type_PasswdEntry, SD_VARLINK_SYMBOL_COMMENT("Describe shadow entry"), &vl_type_ShadowEntry, SD_VARLINK_SYMBOL_COMMENT("Change password via PAM module"), &vl_method_Chauthtok, SD_VARLINK_SYMBOL_COMMENT("Change GECOS information of account"), &vl_method_Chfn, SD_VARLINK_SYMBOL_COMMENT("Change shell of account"), &vl_method_Chsh, SD_VARLINK_SYMBOL_COMMENT("Provide response for PAM_PROMPT_ECHO_*"), &vl_method_Conv, SD_VARLINK_SYMBOL_COMMENT("Update passwd and/or shadow file"), &vl_method_UpdatePasswdShadow, SD_VARLINK_SYMBOL_COMMENT("Stop the daemon"), &vl_method_Quit, SD_VARLINK_SYMBOL_COMMENT("Check if the service is running"), &vl_method_Ping, SD_VARLINK_SYMBOL_COMMENT("Set the maximum log level"), &vl_method_SetLogLevel, SD_VARLINK_SYMBOL_COMMENT("Get current environment block"), &vl_method_GetEnvironment, SD_VARLINK_SYMBOL_COMMENT("Authentication failure"), &vl_error_AuthenticationFailed, SD_VARLINK_SYMBOL_COMMENT("No entry found"), &vl_error_NoEntryFound, SD_VARLINK_SYMBOL_COMMENT("Invalid parameter for varlink function call"), &vl_error_InvalidParameter, SD_VARLINK_SYMBOL_COMMENT("Invalid shell"), &vl_error_InvalidShell, SD_VARLINK_SYMBOL_COMMENT("Password change aborted"), &vl_error_PasswordChangeAborted, SD_VARLINK_SYMBOL_COMMENT("Permission denied"), &vl_error_PermissionDenied, SD_VARLINK_SYMBOL_COMMENT("Internal Error"), &vl_error_InternalError); account-utils-1.0.1/src/varlink-org.openSUSE.pwupd.h000066400000000000000000000002431513410760700222770ustar00rootroot00000000000000//SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include extern const sd_varlink_interface vl_interface_org_openSUSE_pwupd; account-utils-1.0.1/src/varlink-service-common.c000066400000000000000000000113641513410760700216410ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include "config.h" #include #include #include #include "basics.h" #include "varlink-service-common.h" #include "context.h" static int log_level = LOG_WARNING; void set_max_log_level(int level) { log_level = level; } void log_msg(int priority, const char *fmt, ...) { static int is_tty = -1; if (priority > log_level) return; if (is_tty == -1) is_tty = isatty(STDOUT_FILENO); va_list ap; va_start(ap, fmt); if (is_tty) { if (priority <= LOG_ERR) { vfprintf(stderr, fmt, ap); fputc('\n', stderr); } else { vprintf(fmt, ap); putchar('\n'); } } else sd_journal_printv(priority, fmt, ap); va_end(ap); } int vl_method_ping(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t _unused_(flags), void _unused_(*userdata)) { int r; log_msg(LOG_INFO, "Varlink method \"Ping\" called..."); r = sd_varlink_dispatch(link, parameters, NULL, NULL); if (r != 0) return r; return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Alive", true)); } int vl_method_set_log_level(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t _unused_(flags), void _unused_(*userdata)) { static const sd_json_dispatch_field dispatch_table[] = { { "Level", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int, 0, SD_JSON_MANDATORY }, {} }; int r, level; log_msg(LOG_INFO, "Varlink method \"SetLogLevel\" called..."); r = sd_varlink_dispatch(link, parameters, dispatch_table, &level); if (r != 0) return r; log_msg(LOG_DEBUG, "Log level %i requested", level); uid_t peer_uid; r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) { log_msg(LOG_ERR, "Failed to get peer UID: %s", strerror(-r)); return r; } if (peer_uid != 0) { log_msg(LOG_WARNING, "SetLogLevel: peer UID %i denied", peer_uid); return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, parameters); } set_max_log_level(level); log_msg(LOG_INFO, "New log setting: level=%i", level); return sd_varlink_reply(link, NULL); } int vl_method_get_environment(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t _unused_(flags), void _unused_(*userdata)) { int r; log_msg(LOG_INFO, "Varlink method \"GetEnvironment\" called..."); uid_t peer_uid; r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) { log_msg(LOG_ERR, "Failed to get peer UID: %s", strerror(-r)); return r; } if (peer_uid != 0) { log_msg(LOG_WARNING, "GetEnvironment: peer UID %i denied", peer_uid); return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, parameters); } r = sd_varlink_dispatch(link, parameters, NULL, NULL); if (r != 0) return r; #if 0 /* XXX */ for (char **e = environ; *e != 0; e++) { if (!env_assignment_is_valid(*e)) goto invalid; if (!utf8_is_valid(*e)) goto invalid; } #endif return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRV("Environment", environ)); #if 0 invalid: return sd_varlink_error(link, "io.systemd.service.InconsistentEnvironment", parameters); #endif } int vl_method_quit(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t _unused_(flags), void *userdata) { static const sd_json_dispatch_field dispatch_table[] = { { "ExitCode", SD_JSON_VARIANT_INTEGER, sd_json_dispatch_int, 0, 0 }, {} }; uid_t peer_uid; struct context_t *ctx = userdata; int exit_code = 0; int r; log_msg(LOG_INFO, "Varlink method \"Quit\" called..."); r = sd_varlink_get_peer_uid(link, &peer_uid); if (r < 0) { log_msg(LOG_ERR, "Failed to get peer UID: %s", strerror(-r)); return r; } if (peer_uid != 0) { log_msg(LOG_WARNING, "Quit: peer UID %i denied", peer_uid); return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, parameters); } r = sd_varlink_dispatch(link, parameters, dispatch_table, &exit_code); if (r != 0) { log_msg (LOG_ERR, "Quit request: varlink dispatch failed: %s", strerror(-r)); return r; } /* exit code must be negative, systemd will convert that to a positive value */ if (exit_code > 0) exit_code = -exit_code; r = sd_event_exit(ctx->loop, exit_code); if (r != 0) { log_msg(LOG_ERR, "Quit request: disabling event loop failed: %s", strerror(-r)); return sd_varlink_errorbo(link, "org.openSUSE.pwaccess.InternalError", SD_JSON_BUILD_PAIR_BOOLEAN("Success", false)); } return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("Success", true)); } account-utils-1.0.1/src/varlink-service-common.h000066400000000000000000000013771513410760700216510ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include extern void log_msg (int priority, const char *fmt, ...) __attribute__ ((__format__ (__printf__, 2, 3))); extern void set_max_log_level (int level); extern int vl_method_ping(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); extern int vl_method_set_log_level(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); extern int vl_method_get_environment(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); extern int vl_method_quit(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); account-utils-1.0.1/tests/000077500000000000000000000000001513410760700154515ustar00rootroot00000000000000account-utils-1.0.1/tests/meson.build000066400000000000000000000022021513410760700176070ustar00rootroot00000000000000# This file builds and runs the unit tests testdir = join_paths(meson.project_source_root(), 'tests/') test_args = ['-DTESTSDIR="' + testdir + '"'] srcdir = include_directories('../src') libdl = cc.find_library('dl') tst_dlopen_exe = executable('tst-dlopen', 'tst-dlopen.c', dependencies : libdl, include_directories : inc) test('tst-dlopen', tst_dlopen_exe, args : ['pam_unix_ng.so']) test('tst-dlopen', tst_dlopen_exe, args : ['pam_debuginfo.so']) tst_update_passwd_exe = executable('tst-update_passwd', 'tst-update_passwd.c', include_directories : [inc, srcdir], dependencies : [libselinux], c_args: test_args) test('tst-update_passwd', tst_update_passwd_exe) tst_read_config_exe = executable('tst-read_config', 'tst-read_config.c', '../libcommon/read_config.c', include_directories : [inc, srcdir], link_with : [libcommon], dependencies : [libeconf], c_args: ['-DTESTSDIR="' + testdir + 'tst-read_config-data' + '"']) test('tst-read_config', tst_read_config_exe) account-utils-1.0.1/tests/tst-dlopen.c000066400000000000000000000021311513410760700177030ustar00rootroot00000000000000/* Copyright (C) Nalin Dahyabhai 2003 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. */ #include #include #include #include #include "basics.h" /* Simple program to see if dlopen() would succeed. */ int main(int argc, char **argv) { int i; int rc; struct stat st; _cleanup_(freep) char *buf = NULL; for (i = 1; i < argc; i++) { if (dlopen(argv[i], RTLD_NOW)) { fprintf(stdout, "dlopen() of \"%s\" succeeded.\n", argv[i]); } else { rc = asprintf(&buf, "./%s", argv[i]); if (rc >= 0 && (stat(buf, &st) == 0) && dlopen(buf, RTLD_NOW)) { fprintf(stdout, "dlopen() of \"./%s\" " "succeeded.\n", argv[i]); } else { fprintf(stdout, "dlopen() of \"%s\" failed: " "%s\n", argv[i], dlerror()); return 1; } } } return 0; } account-utils-1.0.1/tests/tst-pam_unix_ng.sh000077500000000000000000000102271513410760700211260ustar00rootroot00000000000000#!/bin/bash # --- Configuration --- TEST_USER="pam_testuser" TEST_PASSWD=$(tr -dc A-Za-z0-9 /dev/null; then userdel -r "$TEST_USER" &>/dev/null fi } # Ensure cleanup runs on exit or script failure trap cleanup EXIT function expect_success { if [ $? -eq 0 ]; then echo -e "\033[32mSUCCESS:\033[0m $1" else echo -e "\033[31mFAILURE:\033[0m $1" exit 1 fi } function expect_failure { if [ $? -ne 0 ]; then echo -e "\033[32mSUCCESS:\033[0m $1" else echo -e "\033[31mFAILURE:\033[0m $1" exit 1 fi } # Create Test User echo "--- Creating user '$TEST_USER' and setting password" # user with empty password useradd -m "$TEST_USER" -p '' expect_success "User creation" echo "--- Login with empty password (DISALLOW_NULL_AUTTHOK)" echo '' | pamtester "$PAM_SERVICE" "$TEST_USER" "authenticate(PAM_DISALLOW_NULL_AUTHTOK)" expect_failure "Failure with empty password" echo "--- Login with empty password (nullok)" pamtester "$PAM_SERVICE" "$TEST_USER" authenticate expect_success "Login with empty password" # Set the 12-character password non-interactively echo "$TEST_USER:$TEST_PASSWD" | chpasswd expect_success "Password set to 12 characters" echo "--- Verifying initial login" echo "$TEST_PASSWD" | pamtester "$PAM_SERVICE" "$TEST_USER" authenticate acct_mgmt open_session close_session expect_success "Initial login successful via pamtester" echo "--- Verifying password expiration warning message" # The message pattern we expect depends on EXPIRY_DAYS EXPECTED_WARNING_PATTERN="your password will expire in $EXPIRY_DAYS days" echo "---- Setting password expiry periods" chage -M "$EXPIRY_DAYS" "$TEST_USER" expect_success "Set maximum password age to $EXPIRY_DAYS days" chage -W "$WARNING_DAYS" "$TEST_USER" expect_success "Set password warning period to $WARNING_DAYS days" echo "Current password settings for $TEST_USER:" chage -l "$TEST_USER" # Run pamtester in verbose mode and pipe output to grep if echo "$TEST_PASSWD" | pamtester --verbose "$PAM_SERVICE" "$TEST_USER" authenticate acct_mgmt 2>&1 | grep -q -i "$EXPECTED_WARNING_PATTERN"; then expect_success "Warning message successfully found: '$EXPECTED_WARNING_PATTERN'" else # Try one last time and print the full output for debugging failure echo "Debugging failure: Full pamtester output:" echo "$TEST_PASSWD" | pamtester --verbose "$PAM_SERVICE" "$TEST_USER" authenticate acct_mgmt 2>&1 # Check for the exit code of grep -q, not the full command. expect_success "Warning message verification failed (check output above)" fi echo "--- Verify inactive password" # The message pattern we expect depends on EXPIRY_DAYS EXPECTED_EXPIRED_PASSWORD_MSG="Authentication token expired" echo "---- Setting password expiry periods" chage --lastday "$(date -d "14 days ago" +%Y-%m-%d)" "$TEST_USER" expect_success "Set lastday to 14 days ago" chage --maxdays 7 "$TEST_USER" expect_success "Set maximum password age to $EXPIRY_DAYS days" chage --inactive 4 "$TEST_USER" expect_success "Set password inactive days to 4" echo "Current password settings for $TEST_USER:" chage -l "$TEST_USER" # Run pamtester in verbose mode and pipe output to grep if echo "$TEST_PASSWD" | pamtester --verbose "$PAM_SERVICE" "$TEST_USER" authenticate acct_mgmt 2>&1 | grep -q -i "$EXPECTED_EXPIRED_PASSWORD_MSG"; then expect_success "Expired message successfully found: '$EXPECTED_EXPIRED_PASSWORD_MSG'" else # Try one last time and print the full output for debugging failure echo "Debugging failure: Full pamtester output:" echo "$TEST_PASSWD" | pamtester --verbose "$PAM_SERVICE" "$TEST_USER" authenticate acct_mgmt 2>&1 # Check for the exit code of grep -q, not the full command. expect_success "Warning message verification failed (check output above)" fi echo "--- Script finished successfully" account-utils-1.0.1/tests/tst-read_config-data/000077500000000000000000000000001513410760700214305ustar00rootroot00000000000000account-utils-1.0.1/tests/tst-read_config-data/etc/000077500000000000000000000000001513410760700222035ustar00rootroot00000000000000account-utils-1.0.1/tests/tst-read_config-data/etc/account-utils/000077500000000000000000000000001513410760700247755ustar00rootroot00000000000000account-utils-1.0.1/tests/tst-read_config-data/etc/account-utils/pwaccessd.conf.d/000077500000000000000000000000001513410760700301175ustar00rootroot00000000000000account-utils-1.0.1/tests/tst-read_config-data/etc/account-utils/pwaccessd.conf.d/test0.conf000066400000000000000000000000371513410760700320250ustar00rootroot00000000000000[GetUserRecord] allow= nobody account-utils-1.0.1/tests/tst-read_config.c000066400000000000000000000030671513410760700206730ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include #include "basics.h" #include "read_config.h" static void print_allow(const char *group, uid_t *list) { printf("Group [%s]:", group); if (list == NULL) { fputs(" \n", stdout); return; } for (size_t i = 0; list[i] != 0; i++) printf(" %u", list[i]); fputs("\n", stdout); return; } int main(int argc _unused_, char **argv _unused_) { _cleanup_(struct_config_free) struct config_t cfg = {NULL, NULL, NULL}; econf_err error = read_config(&cfg); if (error != ECONF_SUCCESS) { fprintf(stderr, "read_config failed: %s\n", econf_errString(error)); return error; } print_allow("GetUserRecord", cfg.allow_get_user_record); print_allow("VerifyPassword", cfg.allow_verify_password); print_allow("ExpiredCheck", cfg.allow_expired_check); if (cfg.allow_get_user_record == NULL) { printf("GetUserRecord: UID of nobody not found!\n"); return 1; } if (cfg.allow_get_user_record[0] != 65534) { printf("GetUserRecord: first UID is not nobody but '%u'!\n", cfg.allow_get_user_record[0]); return 1; } if (cfg.allow_get_user_record[1] != 0) { printf("GetUserRecord: second entry is not 0 but '%u'!\n", cfg.allow_get_user_record[1]); return 1; } if (cfg.allow_verify_password != NULL) { printf("VerifyPassword is not NULL!\n"); return 1; } if (cfg.allow_expired_check != NULL) { printf("ExpiredCheck is not NULL!\n"); return 1; } return 0; } account-utils-1.0.1/tests/tst-update_passwd.c000066400000000000000000000012061513410760700212670ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include #include "basics.h" #include "files.h" #include "files.c" #include "string-util-fundamental.c" int main(int argc _unused_, char **argv _unused_) { struct passwd pwd; int r; r = update_passwd(NULL, NULL); if (r != -EINVAL) { fprintf(stderr, "update_passwd(NULL, NULL) did return %i\n", r); return 1; } memset(&pwd, 0, sizeof(pwd)); pwd.pw_name = "test0"; r = update_passwd(&pwd, TESTSDIR"tst-update_passwd/etc"); if (r != 0) { fprintf(stderr, "update_passwd() did return %i\n", r); return 1; } return 0; } account-utils-1.0.1/tests/tst-update_passwd/000077500000000000000000000000001513410760700211245ustar00rootroot00000000000000account-utils-1.0.1/tests/tst-update_passwd/etc/000077500000000000000000000000001513410760700216775ustar00rootroot00000000000000account-utils-1.0.1/tests/tst-update_passwd/etc/passwd000066400000000000000000000000511513410760700231170ustar00rootroot00000000000000test0:x:1001:1001::/home/test0:/bin/bash account-utils-1.0.1/tools/000077500000000000000000000000001513410760700154475ustar00rootroot00000000000000account-utils-1.0.1/tools/Dockerfile000066400000000000000000000016261513410760700174460ustar00rootroot00000000000000#!BuildTag: dump-privs:0.4.0 #!BuildTag: dump-privs:latest #!BuildTag: dump-privs:0.4.0-%RELEASE% #!UseOBSRepositories FROM opensuse/tumbleweed:latest AS build-stage WORKDIR /src RUN zypper clean && zypper ref -f && zypper --non-interactive install --no-recommends gcc libselinux-devel COPY dump-privs.c . RUN gcc -Wall -O2 dump-privs.c -o dump-privs -lselinux FROM opensuse/busybox:latest LABEL maintainer="Thorsten Kukuk " ARG BUILDTIME= ARG VERSION=0.4.0 LABEL org.opencontainers.image.title="dump-privs container" LABEL org.opencontainers.image.description="Container printing all relevant privileges of an application inside the container" LABEL org.opencontainers.image.created=$BUILDTIME LABEL org.opencontainers.image.version=$VERSION COPY --from=build-stage /src/dump-privs /usr/bin RUN chmod u+s,g+s /usr/bin/dump-privs RUN adduser -S -D -H dump-privs USER dump-privs CMD ["dump-privs"] account-utils-1.0.1/tools/dump-privs.c000066400000000000000000000114141513410760700177220ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include "config.h" #include #include #include #include #include #include #include #include #ifdef WITH_SELINUX #include #endif static void print_usage(FILE *stream) { fprintf(stream, "Usage: dump-privs [options]\n"); } static void print_help(void) { fprintf(stdout, "dump-privs - dump process privileges\n\n"); print_usage(stdout); fputs(" -e, --enivronment Print enviornment variables\n", stdout); fputs(" -w, --wait Wait for before exit\n", stdout); fputs(" -h, --help Give this help list\n", stdout); fputs(" -v, --version Print program version\n", stdout); } static void print_error(void) { fprintf (stderr, "Try `dump-privs --help' for more information.\n"); } /* conpare function for qsort */ static int compare_str(const void *a, const void *b) { return strcmp(*(const char **)a, *(const char **)b); } static int agetgroups(int *ngids, gid_t **res) { gid_t *gids; int n; *ngids = 0; *res = NULL; n = getgroups(0, NULL); if (n == -1) return -errno; gids = calloc(n, sizeof(gid_t)); if (gids == NULL) return -ENOMEM; n = getgroups(n, gids); if (n == -1) { int r = errno; free(gids); return -r; } *ngids = n; *res = gids; return 0; } static const char * selinux_status(void) { #ifdef WITH_SELINUX if (is_selinux_enabled() > 0) { int r = security_getenforce(); switch (r) { case 1: return "enforcing"; break; case 0: return "permissive"; break; default: fprintf(stderr, "selinux error: %s\n", strerror(errno)); return "error"; break; } } else return "off"; #else return "not available"; #endif } int main(int argc, char **argv) { bool wait = false; bool printenv = false; int ngids = 0; gid_t *gids = NULL; char *secon = NULL; char *cwd = NULL; int no_new_privs = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0); const char *sestatus = selinux_status(); int r; while (1) { int c; int option_index = 0; static struct option long_options[] = { {"environment", no_argument, NULL, 'e' }, {"help", no_argument, NULL, 'h' }, {"version", no_argument, NULL, 'v' }, {"wait", no_argument, NULL, 'w' }, {NULL, 0, NULL, '\0'} }; c = getopt_long (argc, argv, "ehvw", long_options, &option_index); if (c == (-1)) break; switch (c) { case 'e': printenv = true; break; case 'h': print_help(); return 0; case 'v': printf("dump-privs (%s) %s\n", PACKAGE, VERSION); return 0; case 'w': wait = true; break; default: print_error(); return 1; } } argc -= optind; argv += optind; if (argc > 0) { fprintf(stderr, "dump-privs: Too many arguments.\n"); print_error(); return EINVAL; } r = agetgroups(&ngids, &gids); if (r != 0) fprintf(stderr, "getgroups() failed: %s\n", strerror(-r)); printf("🔎 Process Security Information\n"); printf("-------------------------------\n"); printf("Real UID: %d\n", getuid()); printf("Effective UID: %d\n", geteuid()); printf("Real GID: %d\n", getgid()); printf("Effective GID: %d\n", getegid()); printf("Group memberships:"); for (int i = 0; i < ngids; i++) { if (i != 0) putchar(','); printf(" %jd", (intmax_t) gids[i]); } putchar('\n'); printf("SELinux Status: %s\n", sestatus); if (getcon(&secon) == 0) { size_t secon_len = strlen(secon); if (secon[secon_len-1] == '\n') secon[secon_len-1] = '\0'; printf("SELinux Context: %s\n", secon); freecon(secon); /* Free the memory allocated by getcon() */ } else fprintf(stderr, "SELinux Context: %s\n", strerror(errno)); printf("NoNewPrivs Status: %s\n", no_new_privs==0?"off":"on"); cwd = get_current_dir_name(); if (cwd == NULL) fprintf(stderr, "Current directory: %s\n", strerror(errno)); else { printf("Current directory: %s\n", cwd); free(cwd); } if (printenv) { int count = 0; char **envp = environ; while (*envp != NULL) { count++; envp++; } qsort(environ, count, sizeof(char *), compare_str); envp = environ; fputs("Environement variables:\n", stdout); while (*envp != NULL) { printf("%s\n", *envp); envp++; } } if (wait) getchar(); return 0; } account-utils-1.0.1/tools/meson.build000066400000000000000000000004751513410760700176170ustar00rootroot00000000000000executable('dump-privs', 'dump-privs.c', include_directories : inc, dependencies : [libselinux], install : get_option('tools')) executable('scan-caps', 'scan-caps.c', include_directories : inc, dependencies : [libcap], install : get_option('tools')) account-utils-1.0.1/tools/scan-caps.c000066400000000000000000000115151513410760700174660ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-or-later #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "basics.h" /* paths to check for setuid binaries or binaries with capabilities */ const char *search_paths[] = { "/usr/bin", "/usr/sbin", "/usr/lib", "/usr/lib64", "/usr/libexec", "/usr/local/bin", "/usr/local/libexec", NULL }; static void print_usage(FILE *stream) { fprintf(stream, "Usage: scan-caps [path]\n"); } static void print_help(void) { fprintf(stdout, "scan-caps - scan system for binaries with capabilities\n\n"); print_usage(stdout); fputs(" -h, --help Give this help list\n", stdout); fputs(" -v, --version Print program version\n", stdout); } static void print_error(void) { fprintf (stderr, "Try `scan-caps --help' for more information.\n"); } static void free_cap_text(char **p) { if (p == NULL || *p == NULL) return; cap_free(*p); *p = NULL; } /* Checks a specific file for setuid, setgid, or capabilities */ static int check_file(const char *file, struct stat st) { _cleanup_(free_cap_text) char *caps_text = NULL; cap_t caps; int is_suid = 0; int is_sgid = 0; int has_caps = 0; int r; /* check regular files only */ if (!S_ISREG(st.st_mode)) return 0; if (st.st_mode & S_ISUID) is_suid = 1; if (st.st_mode & S_ISGID) is_sgid = 1; /* cap_get_file returns NULL if the file has no capabilities or on error */ caps = cap_get_file(file); if (caps) { /* We have a cap object, but we need to ensure it's not empty */ caps_text = cap_to_text(caps, NULL); if (caps_text && strlen(caps_text) > 0) has_caps = 1; cap_free(caps); } else if (errno != ENODATA) { r = -errno; fprintf(stderr, "cap_get_file(%s) failed: %s\n", file, strerror(-r)); return r; } if (is_suid || is_sgid || has_caps) { printf("Found: %s [", file); if (is_suid) printf(" SUID "); if (is_sgid) printf(" SGID "); if (has_caps) printf(" CAP: %s ", caps_text); printf("]\n"); } return 0; } /* Recursively walks a directory */ static int walk_directory(const char *dir_path) { DIR *dir; struct dirent *entry; struct stat st; int r; if (!(dir = opendir(dir_path))) { r = -errno; fprintf(stderr, "Cannot open directory '%s': %s\n", dir_path, strerror(-r)); return r; } while ((entry = readdir(dir)) != NULL) { _cleanup_free_ char *path = NULL; /* Skip . and .. */ if (streq(entry->d_name, ".") || streq(entry->d_name, "..")) continue; if (asprintf(&path, "%s/%s", dir_path, entry->d_name) < 0) return -ENOMEM; /* Don't follow symlinks */ r = lstat(path, &st); if (r < 0) { r = -errno; fprintf(stderr, "lstat(%s) failed: %s\n", path, strerror(-r)); return r; } if (S_ISLNK(st.st_mode)) continue; if (S_ISDIR(st.st_mode)) { r = walk_directory(path); /* Recurse into subdirectory */ if (r < 0) return r; } else { r = check_file(path, st); if (r < 0) return r; } } closedir(dir); return 0; } int main(int argc, char **argv) { int r; while (1) { int c; int option_index = 0; static struct option long_options[] = { {"help", no_argument, NULL, 'h' }, {"version", no_argument, NULL, 'v' }, {NULL, 0, NULL, '\0'} }; c = getopt_long (argc, argv, "hv", long_options, &option_index); if (c == (-1)) break; switch (c) { case 'h': print_help(); return 0; case 'v': printf("scan-caps (%s) %s\n", PACKAGE, VERSION); return 0; default: print_error(); return 1; } } argc -= optind; argv += optind; printf("🔎 Scanning for binaries with setuid, setgid, or capabilities...\n"); printf("----------------------------------------------------------------\n"); const char **scan_list = (argc > 0) ? (const char **)argv : search_paths; for (int i = 0; scan_list[i] != NULL; i++) { // If scanning argv, ensure we stop exactly at argc to match original logic // (Standard C guarantees argv[argc] is NULL, but this is defensively safe) if (argc > 0 && i >= argc) break; r = walk_directory(scan_list[i]); if (r < 0) { printf("Scan aborted.\n"); return -r; } } printf("----------------------------------------------------------------\n"); printf("Scan complete.\n"); return 0; } account-utils-1.0.1/units/000077500000000000000000000000001513410760700154515ustar00rootroot00000000000000account-utils-1.0.1/units/meson.build000066400000000000000000000013331513410760700176130ustar00rootroot00000000000000configure_file( input: 'pwaccessd.service.in', output: 'pwaccessd.service', configuration: { 'LIBEXECDIR': libexecdir, }, install: true, install_dir: systemunitdir, ) install_data('pwaccessd.socket', install_dir : systemunitdir) configure_file( input: 'pwupdd@.service.in', output: 'pwupdd@.service', configuration: { 'LIBEXECDIR': libexecdir, }, install: true, install_dir: systemunitdir, ) install_data('pwupdd.socket', install_dir : systemunitdir) configure_file( input: 'newidmapd.service.in', output: 'newidmapd.service', configuration: { 'LIBEXECDIR': libexecdir, }, install: true, install_dir: systemunitdir, ) install_data('newidmapd.socket', install_dir : systemunitdir) account-utils-1.0.1/units/newidmapd.service.in000066400000000000000000000013411513410760700214070ustar00rootroot00000000000000[Unit] Description=Daemon to set map ranges Documentation=man:newidmapd(8) [Service] Type=notify Environment="NEWIDMAPD_OPTS=" EnvironmentFile=-/etc/default/account-utils ExecStart=@LIBEXECDIR@/newidmapd -s $NEWIDMAPD_OPTS IPAddressDeny=any LockPersonality=yes MemoryDenyWriteExecute=yes NoNewPrivileges=yes PrivateDevices=yes PrivateNetwork=yes PrivateTmp=yes ProcSubset=pid ProtectClock=yes ProtectControlGroups=yes ProtectHome=yes ProtectHostname=yes ProtectKernelLogs=yes ProtectKernelModules=yes ProtectKernelTunables=yes ProtectProc=invisible ProtectSystem=strict ReadWritePaths=/run/account /proc /etc/default RestrictAddressFamilies=AF_UNIX RestrictNamespaces=yes RestrictRealtime=true RestrictRealtime=yes RestrictSUIDSGID=yes account-utils-1.0.1/units/newidmapd.socket000066400000000000000000000003401513410760700206300ustar00rootroot00000000000000[Unit] Description=newidmap daemon socket Documentation=man:newidmapd(8) [Socket] ListenStream=/run/account/newidmapd-socket FileDescriptorName=varlink SocketMode=0666 DirectoryMode=0755 [Install] WantedBy=sockets.target account-utils-1.0.1/units/pwaccessd.service.in000066400000000000000000000014321513410760700214140ustar00rootroot00000000000000[Unit] Description=Daemon to access passwd, shadow and for authentication Documentation=man:pwaccessd(8) [Service] Type=notify Environment="PWACCESSD_OPTS=" EnvironmentFile=-/etc/default/account-utils ExecStart=@LIBEXECDIR@/pwaccessd -s $PWACCESSD_OPTS LockPersonality=yes MemoryDenyWriteExecute=yes NoNewPrivileges=yes PrivateDevices=yes PrivateTmp=yes ProcSubset=pid ProtectClock=yes ProtectControlGroups=yes ProtectHome=yes ProtectHostname=yes ProtectKernelLogs=yes ProtectKernelModules=yes ProtectKernelTunables=yes ProtectProc=invisible ProtectSystem=strict ReadWritePaths=/run/account /etc RestrictNamespaces=yes RestrictRealtime=true RestrictRealtime=yes RestrictSUIDSGID=yes # This don't work with NIS, LDAP, ... #IPAddressDeny=any #PrivateNetwork=yes #RestrictAddressFamilies=AF_UNIX account-utils-1.0.1/units/pwaccessd.socket000066400000000000000000000003371513410760700206420ustar00rootroot00000000000000[Unit] Description=pwaccess daemon socket Documentation=man:pwaccessd(8) [Socket] ListenStream=/run/account/pwaccess-socket FileDescriptorName=varlink SocketMode=0666 DirectoryMode=0755 [Install] WantedBy=sockets.target account-utils-1.0.1/units/pwupdd.socket000066400000000000000000000004421513410760700201660ustar00rootroot00000000000000[Unit] Description=Daemon to update passwd and shadow entries After=local-fs.target Before=sockets.target [Socket] ListenStream=/run/account/pwupd-socket FileDescriptorName=varlink SocketMode=0666 DirectoryMode=0755 Accept=yes MaxConnectionsPerSource=16 [Install] WantedBy=sockets.target account-utils-1.0.1/units/pwupdd@.service.in000066400000000000000000000013551513410760700210470ustar00rootroot00000000000000[Unit] Description=Daemon to update passwd and shadow entries After=local-fs.target [Service] Environment="PWUPDD_OPTS=" EnvironmentFile=-/etc/default/account-utils ExecStart=@LIBEXECDIR@/pwupdd $PWUPDD_OPTS LockPersonality=yes MemoryDenyWriteExecute=yes NoNewPrivileges=yes PrivateDevices=yes PrivateTmp=yes ProcSubset=pid ProtectClock=yes ProtectControlGroups=yes ProtectHome=yes ProtectHostname=yes ProtectKernelLogs=yes ProtectKernelModules=yes ProtectKernelTunables=yes ProtectProc=invisible ProtectSystem=strict ReadWritePaths=/run/account /etc RestrictNamespaces=yes RestrictRealtime=true RestrictRealtime=yes RestrictSUIDSGID=yes # This don't work with NIS, LDAP, ... #IPAddressDeny=any #PrivateNetwork=yes #RestrictAddressFamilies=AF_UNIX