/* -*- mode: c++; indent-tabs-mode: nil -*- */
/*
  ql_pwd.qpp

  Qore Programming Language

  Copyright (C) 2003 - 2023 Qore Technologies, s.r.o.

  Permission is hereby granted, free of charge, to any person obtaining a
  copy of this software and associated documentation files (the "Software"),
  to deal in the Software without restriction, including without limitation
  the rights to use, copy, modify, merge, publish, distribute, sublicense,
  and/or sell copies of the Software, and to permit persons to whom the
  Software is furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  DEALINGS IN THE SOFTWARE.

  Note that the Qore library is released under a choice of three open-source
  licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
  information.
*/

#include <qore/Qore.h>
#include "qore/intern/ql_env.h"
#include "qore/intern/QoreHashNodeIntern.h"

#include <cerrno>
#include <cstdio>

#ifdef HAVE_PWD_H
#include <pwd.h>
#endif

/** @defgroup pwd_functions UNIX User and Group Functions
    UNIX user and group functions

    @section group_info_hash Group Information Hash
    |!Key|!Type|!Description
    |\c gr_name|@ref string_type|The name of the group
    |\c gr_passwd|@ref string_type|The encrypted password for the group
    |\c gr_gid|@ref int_type|The group id
    |\c gr_mem|@ref list_type|List of strings giving the usernames of members of the group

    @section password_info_hash Password Information Hash
    |!Key|!Type|!Description
    |\c pw_name|@ref string_type|The username of the user
    |\c pw_passwd|@ref string_type|The encrypted password for the user
    |\c pw_gecos|@ref string_type|The real name or description of the user
    |\c pw_dir|@ref string_type|The user's home directory
    |\c pw_shell|@ref string_type|The user's login shell
    |\c pw_uid|@ref int_type|The user's userid
    |\c pw_gid|@ref int_type|The group id of the user's primary group
 */
///@{
//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing getpwuid() [flags=RUNTIME_NOOP;dom=EXTERNAL_INFO] {
}

//! Returns a @ref password_info_hash "password information hash" representing the user information for the user ID passed, or, if the user ID does not exist @ref nothing is returned
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_USERMGT

    @param uid the user ID to look up

    @return a @ref password_info_hash "password information hash" representing the user information for the user ID passed, or, if the user ID does not exist @ref nothing is returned

    @par Example:
    @code{.py}
*hash hash = getpwuid(0);
    @endcode

    @see getpwuid2() for a similar function that throws an exception if the user ID is invalid
 */
*hash getpwuid(softint uid) [flags=CONSTANT;dom=EXTERNAL_INFO] {
#ifdef HAVE_PWD_H
   return q_getpwuid(uid);
#else
   return missing_function_error("getpwuid", "UNIX_USERMGT", xsink);
#endif
}

//! Returns a @ref password_info_hash "password information hash" representing the user information for the user ID passed, or, if the user ID does not exist, a \c GETPWUID2-ERROR exception is thrown
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_USERMGT

    @param uid the user ID to look up

    @return a @ref password_info_hash "password information hash" representing the user information for the user ID passed, or, if the user ID does not exist, a \c GETPWUID2-ERROR exception is thrown

    @par Example:
    @code{.py}
*hash hash = getpwuid2(0);
    @endcode

    @throw GETPWUID2-ERROR invalid uid or error reading user information

    @see getpwuid() for a similar function that does not throw an exception if the user ID is invalid
 */
hash getpwuid2(softint uid) [flags=RET_VALUE_ONLY;dom=EXTERNAL_INFO] {
#ifdef HAVE_PWD_H
   errno = 0;
   QoreHashNode *h = q_getpwuid(uid);
   if (!h) {
      if (!errno)
         xsink->raiseException("GETPPWUID2-ERROR", "uid %d not found", uid);
      else
         xsink->raiseException("GETPPWUID2-ERROR", q_strerror(errno));
   }
   return h;
#else
   return missing_function_error("getpwuid2", "UNIX_USERMGT", xsink);
#endif
}

//! Returns a @ref password_info_hash "password information hash" representing the user information for the user name passed, or, if the user does not exist @ref nothing is returned
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_USERMGT

    @param name the user name to look up

    @return a @ref password_info_hash "password information hash" representing the user information for the user name passed, or, if the user does not exist @ref nothing is returned

    @par Example:
    @code{.py}
*hash hash = getpwnam("root");
    @endcode

    @see getpwnam2() for a similar function that throws an exception if the user is invalid
 */
hash getpwnam(string name) [flags=CONSTANT;dom=EXTERNAL_INFO] {
#ifdef HAVE_PWD_H
   return q_getpwnam(name->getBuffer());
#else
   return missing_function_error("getpwnam", "UNIX_USERMGT", xsink);
#endif
}

//! Returns a @ref password_info_hash "password information hash" representing the user information for the user name passed, or, if the user does not exist, a \c GETPWNAM2-ERROR exception is thrown
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_USERMGT

    @param name the user name to look up

    @return a @ref password_info_hash "password information hash" representing the user information for the user name passed, or, if the user does not exist, a \c GETPWNAM2-ERROR exception is thrown

    @par Example:
    @code{.py}
*hash hash = getpwnam2("root");
    @endcode

    @throw GETPWNAM2-ERROR invalid user or error reading user information

    @see getpwnam() for a similar function that does not throw an exception if the user is invalid
 */
hash getpwnam2(string name) [flags=RET_VALUE_ONLY;dom=EXTERNAL_INFO] {
#ifdef HAVE_PWD_H
   errno = 0;
   QoreHashNode *h = q_getpwnam(name->getBuffer());
   if (!h) {
      if (!errno)
         xsink->raiseException("GETPPWNAM2-ERROR", "user '%s' not found", name->getBuffer());
      else
         xsink->raiseException("GETPPWNAM2-ERROR", q_strerror(errno));
   }
   return h;
#else
   return missing_function_error("getpwnam2", "UNIX_USERMGT", xsink);
#endif
}

//! Returns a @ref group_info_hash "group information hash" representing the group information for the group ID passed, or, if the group ID does not exist @ref nothing is returned
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_USERMGT

    @param gid the group ID to look up

    @return a @ref group_info_hash "group information hash" representing the group information for the group ID passed, or, if the group ID does not exist @ref nothing is returned

    @par Example:
    @code{.py}
*hash hash = getgrgid(0);
    @endcode

    @see getgrgid2() for a similar function that throws an exception if the group ID is invalid
 */
*hash getgrgid(softint gid) [flags=CONSTANT;dom=EXTERNAL_INFO] {
#ifdef HAVE_PWD_H
   return q_getgrgid(gid);
#else
   return missing_function_error("getgrgid", "UNIX_USERMGT", xsink);
#endif
}

//! Returns a @ref group_info_hash "group information hash" representing the group information for the group ID passed, or, if the group ID does not exist, a \c GETGRGID2-ERROR exception is thrown
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_USERMGT

    @param gid the group ID to look up

    @return a @ref group_info_hash "group information hash" representing the group information for the group ID passed, or, if the group ID does not exist, a \c GETGRGID2-ERROR exception is thrown

    @par Example:
    @code{.py}
*hash hash = getgrgid2(0);
    @endcode

    @throw GETGRGID2-ERROR invalid gid or error reading group information

    @see getgrgid() for a similar function that does not throw an exception if the group ID is invalid
 */
hash getgrgid2(softint gid) [flags=RET_VALUE_ONLY;dom=EXTERNAL_INFO] {
#ifdef HAVE_PWD_H
   errno = 0;
   QoreHashNode *h = q_getgrgid(gid);
   if (!h) {
      if (!errno)
         xsink->raiseException("GETGRGID2-ERROR", "gid %d not found", gid);
      else
         xsink->raiseException("GETGRGID2-ERROR", q_strerror(errno));
   }
   return h;
#else
   return missing_function_error("getgrgid2", "UNIX_USERMGT", xsink);
#endif
}

//! Returns a @ref group_info_hash "group information hash" representing the group information for the group name passed, or, if the group does not exist @ref nothing is returned
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_USERMGT

    @param name the group name to look up

    @return a @ref group_info_hash "group information hash" representing the group information for the group name passed, or, if the group does not exist @ref nothing is returned

    @par Example:
    @code{.py}
*hash hash = getgrnam("root");
    @endcode

    @see getgrnam2() for a similar function that throws an exception if the group is invalid
 */
*hash getgrnam(string name) [flags=CONSTANT;dom=EXTERNAL_INFO] {
#ifdef HAVE_PWD_H
   return q_getgrnam(name->getBuffer());
#else
   return missing_function_error("getgrnam", "UNIX_USERMGT", xsink);
#endif
}

//! Returns a @ref group_info_hash "group information hash" representing the group information for the group name passed, or, if the group does not exist, a \c GETGRNAM2-ERROR exception is thrown
/**
    @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_USERMGT

    @param name the group name to look up

    @return a @ref group_info_hash "group information hash" representing the group information for the group name passed, or, if the group does not exist, a \c GETGRNAM2-ERROR exception is thrown

    @par Example:
    @code{.py}
*hash hash = getgrnam2("root");
    @endcode

    @throw GETGRNAM2-ERROR invalid group or error reading group information

    @see getgrnam() for a similar function that does not throw an exception if the group is invalid
 */
hash getgrnam2(string name) [flags=RET_VALUE_ONLY;dom=EXTERNAL_INFO] {
#ifdef HAVE_PWD_H
   errno = 0;
   QoreHashNode *h = q_getgrnam(name->getBuffer());
   if (!h) {
      if (!errno)
         xsink->raiseException("GETPGRNAM2-ERROR", "group '%s' not found", name->getBuffer());
      else
         xsink->raiseException("GETPGRNAM2-ERROR", q_strerror(errno));
   }
   return h;
#else
   return missing_function_error("getgrnam2", "UNIX_USERMGT", xsink);
#endif
}
///@}

/** @defgroup user_and_group_functions Platform-Independent User and Group Functions
    Platform-Independent user and group functions
*/
///@{
//! returns the current username
/** @par Example:
    @code{.py}
string name = getusername();
    @endcode

    @return the current username

    @since %Qore 0.8.12
*/
string getusername() [flags=RET_VALUE_ONLY;dom=EXTERNAL_INFO] {
#ifdef HAVE_PWD_H
    errno = 0;
    ReferenceHolder<QoreHashNode> h(q_getpwuid(geteuid()), xsink);
    if (!h) {
        if (!errno)
            return xsink->raiseException("GET-USERNAME-ERROR", "failed to retrieve information for UID %d", geteuid());
        return xsink->raiseErrnoException("GET-USERNAME-ERROR", errno, "failed to retrieve information for UID %d", geteuid());
    }
    return qore_hash_private::get(**h)->takeKeyValueIntern("pw_name");
#else
#ifdef _Q_WINDOWS
#define USERNAME_MAX 255
    SimpleRefHolder<QoreStringNode> name(new QoreStringNode);
    name->reserve(USERNAME_MAX);
    DWORD size = USERNAME_MAX + 1;
    if (!GetUserName((LPSTR)name->getBuffer(), &size)) {
        DWORD ec = GetLastError();
        char* ebuf;
        QoreStringNode* desc = new QoreStringNode("failed to retrieve username for current Windows user: ");
        // get windows error message
        if (!FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER, 0, ec, LANG_USER_DEFAULT, (LPTSTR)&ebuf, 0, 0)) {
            assert(!ebuf);
            desc->sprintf("Windows FormatMessage() failed on error code %d", ec);
        }
        else {
            assert(ebuf);
            desc->concat(ebuf);
        }

        return xsink->raiseException("GET-USERNAME-ERROR", desc);
    }
    name->terminate(size ? size - 1 : 0);
    return name.release();
#else
#error need getusername() support for this platform
#endif
#endif
}
///@}
