浏览代码

LibC: Implement support for getspnam() and friends

Gunnar Beutner 4 年之前
父节点
当前提交
302f9798ee

+ 12 - 2
Base/etc/shadow

@@ -1,2 +1,12 @@
-root:
-anon:$5$zFiQBeTD88m/mhbU$ecHDSdRd5yNV45BzIRXwtRpxJtMpVI5twjRRXO8X03Q=
+root::18727::::::
+anon:$5$zFiQBeTD88m/mhbU$ecHDSdRd5yNV45BzIRXwtRpxJtMpVI5twjRRXO8X03Q=:18727:0:99999::::
+lookup:!*:18727::::::
+request:!*:18727::::::
+notify:!*:18727::::::
+window:!*:18727::::::
+clipboard:!*:18727::::::
+webcontent:!*:18727::::::
+image:!*:18727::::::
+symbol:!*:18727::::::
+websocket:!*:18727::::::
+nona:!*:18727::::::

+ 1 - 0
Userland/Libraries/LibC/CMakeLists.txt

@@ -29,6 +29,7 @@ set(LIBC_SOURCES
     scanf.cpp
     scanf.cpp
     sched.cpp
     sched.cpp
     serenity.cpp
     serenity.cpp
+    shadow.cpp
     signal.cpp
     signal.cpp
     spawn.cpp
     spawn.cpp
     stat.cpp
     stat.cpp

+ 248 - 0
Userland/Libraries/LibC/shadow.cpp

@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, Gunnar Beutner <gbeutner@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/String.h>
+#include <AK/TemporaryChange.h>
+#include <AK/Vector.h>
+#include <errno_numbers.h>
+#include <shadow.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+extern "C" {
+
+static FILE* s_stream = nullptr;
+static unsigned s_line_number = 0;
+static struct spwd s_shadow_entry;
+
+static String s_name;
+static String s_pwdp;
+
+void setspent()
+{
+    s_line_number = 0;
+    if (s_stream) {
+        rewind(s_stream);
+    } else {
+        s_stream = fopen("/etc/shadow", "r");
+        if (!s_stream) {
+            perror("open /etc/shadow");
+        }
+    }
+}
+
+void endspent()
+{
+    s_line_number = 0;
+    if (s_stream) {
+        fclose(s_stream);
+        s_stream = nullptr;
+    }
+
+    memset(&s_shadow_entry, 0, sizeof(s_shadow_entry));
+
+    s_name = {};
+    s_pwdp = {};
+}
+
+struct spwd* getspnam(const char* name)
+{
+    setspent();
+    while (auto* sp = getspent()) {
+        if (!strcmp(sp->sp_namp, name)) {
+            return sp;
+        }
+    }
+    return nullptr;
+}
+
+static bool parse_shadow_entry(const String& line)
+{
+    auto parts = line.split_view(':', true);
+    if (parts.size() != 9) {
+        dbgln("getspent(): Malformed entry on line {}", s_line_number);
+        return false;
+    }
+
+    s_name = parts[0];
+    s_pwdp = parts[1];
+    auto& lstchg_string = parts[2];
+    auto& min_string = parts[3];
+    auto& max_string = parts[4];
+    auto& warn_string = parts[5];
+    auto& inact_string = parts[6];
+    auto& expire_string = parts[7];
+    auto& flag_string = parts[8];
+
+    auto lstchg = lstchg_string.to_int();
+    if (!lstchg.has_value()) {
+        dbgln("getspent(): Malformed lstchg on line {}", s_line_number);
+        return false;
+    }
+
+    if (min_string.is_empty())
+        min_string = "-1";
+    auto min_value = min_string.to_int();
+    if (!min_value.has_value()) {
+        dbgln("getspent(): Malformed min value on line {}", s_line_number);
+        return false;
+    }
+
+    if (max_string.is_empty())
+        max_string = "-1";
+    auto max_value = max_string.to_int();
+    if (!max_value.has_value()) {
+        dbgln("getspent(): Malformed max value on line {}", s_line_number);
+        return false;
+    }
+
+    if (warn_string.is_empty())
+        warn_string = "-1";
+    auto warn = warn_string.to_int();
+    if (!warn.has_value()) {
+        dbgln("getspent(): Malformed warn on line {}", s_line_number);
+        return false;
+    }
+
+    if (inact_string.is_empty())
+        inact_string = "-1";
+    auto inact = inact_string.to_int();
+    if (!inact.has_value()) {
+        dbgln("getspent(): Malformed inact on line {}", s_line_number);
+        return false;
+    }
+
+    if (expire_string.is_empty())
+        expire_string = "-1";
+    auto expire = expire_string.to_int();
+    if (!expire.has_value()) {
+        dbgln("getspent(): Malformed expire on line {}", s_line_number);
+        return false;
+    }
+
+    if (flag_string.is_empty())
+        flag_string = "0";
+    auto flag = flag_string.to_int();
+    if (!flag.has_value()) {
+        dbgln("getspent(): Malformed flag on line {}", s_line_number);
+        return false;
+    }
+
+    s_shadow_entry.sp_namp = const_cast<char*>(s_name.characters());
+    s_shadow_entry.sp_pwdp = const_cast<char*>(s_pwdp.characters());
+    s_shadow_entry.sp_lstchg = lstchg.value();
+    s_shadow_entry.sp_min = min_value.value();
+    s_shadow_entry.sp_max = max_value.value();
+    s_shadow_entry.sp_warn = warn.value();
+    s_shadow_entry.sp_inact = inact.value();
+    s_shadow_entry.sp_expire = expire.value();
+    s_shadow_entry.sp_flag = flag.value();
+
+    return true;
+}
+
+struct spwd* getspent()
+{
+    if (!s_stream)
+        setspent();
+
+    while (true) {
+        if (!s_stream || feof(s_stream))
+            return nullptr;
+
+        if (ferror(s_stream)) {
+            dbgln("getspent(): Read error: {}", strerror(ferror(s_stream)));
+            return nullptr;
+        }
+
+        char buffer[1024];
+        ++s_line_number;
+        char* s = fgets(buffer, sizeof(buffer), s_stream);
+
+        // Silently tolerate an empty line at the end.
+        if ((!s || !s[0]) && feof(s_stream))
+            return nullptr;
+
+        String line(s, Chomp);
+        if (parse_shadow_entry(line))
+            return &s_shadow_entry;
+        // Otherwise, proceed to the next line.
+    }
+}
+
+static void construct_spwd(struct spwd* sp, char* buf, struct spwd** result)
+{
+    auto* buf_name = &buf[0];
+    auto* buf_pwdp = &buf[s_name.length() + 1];
+
+    bool ok = true;
+    ok = ok && s_name.copy_characters_to_buffer(buf_name, s_name.length() + 1);
+    ok = ok && s_pwdp.copy_characters_to_buffer(buf_pwdp, s_pwdp.length() + 1);
+
+    VERIFY(ok);
+
+    *result = sp;
+    sp->sp_namp = buf_name;
+    sp->sp_pwdp = buf_pwdp;
+}
+
+int getspnam_r(const char* name, struct spwd* sp, char* buf, size_t buflen, struct spwd** result)
+{
+    // FIXME: This is a HACK!
+    TemporaryChange name_change { s_name, {} };
+    TemporaryChange pwdp_change { s_pwdp, {} };
+
+    setspent();
+    bool found = false;
+    while (auto* sp = getspent()) {
+        if (!strcmp(sp->sp_namp, name)) {
+            found = true;
+            break;
+        }
+    }
+
+    if (!found) {
+        *result = nullptr;
+        return 0;
+    }
+
+    const auto total_buffer_length = s_name.length() + s_pwdp.length() + 8;
+    if (buflen < total_buffer_length)
+        return ERANGE;
+
+    construct_spwd(sp, buf, result);
+    return 0;
+}
+
+int putspent(struct spwd* p, FILE* stream)
+{
+    if (!p || !stream || !p->sp_namp || !p->sp_pwdp) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    auto is_valid_field = [](const char* str) {
+        return str && !strpbrk(str, ":\n");
+    };
+
+    if (!is_valid_field(p->sp_namp) || !is_valid_field(p->sp_pwdp)) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    int nwritten = fprintf(stream, "%s:%s:%ld:%ld:%ld:%ld:%ld:%ld:%ld\n", p->sp_namp,
+        p->sp_pwdp, p->sp_lstchg, p->sp_min, p->sp_max, p->sp_warn, p->sp_inact, p->sp_expire, p->sp_flag);
+    if (!nwritten || nwritten < 0) {
+        errno = ferror(stream);
+        return -1;
+    }
+
+    return 0;
+}
+}

+ 39 - 0
Userland/Libraries/LibC/shadow.h

@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2021, Gunnar Beutner <gbeutner@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <bits/FILE.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+struct spwd {
+    char* sp_namp;
+    char* sp_pwdp;
+    long int sp_lstchg;
+    long int sp_min;
+    long int sp_max;
+    long int sp_warn;
+    long int sp_inact;
+    long int sp_expire;
+    unsigned long int sp_flag;
+};
+
+struct spwd* getspent();
+void setspent();
+void endspent();
+struct spwd* getspnam(const char* name);
+int putspent(struct spwd* p, FILE* stream);
+
+int getspent_r(struct spwd* spbuf, char* buf, size_t buflen, struct spwd** spbufp);
+int getspnam_r(const char* name, struct spwd* spbuf, char* buf, size_t buflen, struct spwd** spbufp);
+
+int fgetspent_r(FILE* fp, struct spwd* spbuf, char* buf, size_t buflen, struct spwd** spbufp);
+int sgetspent_r(const char* s, struct spwd* spbuf, char* buf, size_t buflen, struct spwd** spbufp);
+
+__END_DECLS

+ 50 - 47
Userland/Libraries/LibCore/Account.cpp

@@ -13,6 +13,7 @@
 #include <errno.h>
 #include <errno.h>
 #include <grp.h>
 #include <grp.h>
 #include <pwd.h>
 #include <pwd.h>
+#include <shadow.h>
 #include <stdio.h>
 #include <stdio.h>
 #include <string.h>
 #include <string.h>
 #include <sys/stat.h>
 #include <sys/stat.h>
@@ -47,39 +48,52 @@ static Vector<gid_t> get_gids(const StringView& username)
     return extra_gids;
     return extra_gids;
 }
 }
 
 
-Result<Account, String> Account::from_passwd(const passwd& pwd)
+Result<Account, String> Account::from_passwd(const passwd& pwd, const spwd& spwd)
 {
 {
-    Account account(pwd, get_gids(pwd.pw_name));
+    Account account(pwd, spwd, get_gids(pwd.pw_name));
     endpwent();
     endpwent();
+    endspent();
     return account;
     return account;
 }
 }
 
 
 Result<Account, String> Account::from_name(const char* username)
 Result<Account, String> Account::from_name(const char* username)
 {
 {
-    struct passwd* pwd = nullptr;
     errno = 0;
     errno = 0;
-    pwd = getpwnam(username);
+    auto* pwd = getpwnam(username);
     if (!pwd) {
     if (!pwd) {
         if (errno == 0)
         if (errno == 0)
             return String("No such user");
             return String("No such user");
 
 
         return String(strerror(errno));
         return String(strerror(errno));
     }
     }
-    return from_passwd(*pwd);
+    auto* spwd = getspnam(username);
+    if (!spwd) {
+        if (errno == 0)
+            return String("No such user");
+
+        return String(strerror(errno));
+    }
+    return from_passwd(*pwd, *spwd);
 }
 }
 
 
 Result<Account, String> Account::from_uid(uid_t uid)
 Result<Account, String> Account::from_uid(uid_t uid)
 {
 {
-    struct passwd* pwd = nullptr;
     errno = 0;
     errno = 0;
-    pwd = getpwuid(uid);
+    auto* pwd = getpwuid(uid);
     if (!pwd) {
     if (!pwd) {
         if (errno == 0)
         if (errno == 0)
             return String("No such user");
             return String("No such user");
 
 
         return String(strerror(errno));
         return String(strerror(errno));
     }
     }
-    return from_passwd(*pwd);
+    auto* spwd = getspnam(pwd->pw_name);
+    if (!spwd) {
+        if (errno == 0)
+            return String("No such user");
+
+        return String(strerror(errno));
+    }
+    return from_passwd(*pwd, *spwd);
 }
 }
 
 
 bool Account::authenticate(const char* password) const
 bool Account::authenticate(const char* password) const
@@ -133,8 +147,9 @@ void Account::delete_password()
     m_password_hash = "";
     m_password_hash = "";
 }
 }
 
 
-Account::Account(const passwd& pwd, Vector<gid_t> extra_gids)
+Account::Account(const passwd& pwd, const spwd& spwd, Vector<gid_t> extra_gids)
     : m_username(pwd.pw_name)
     : m_username(pwd.pw_name)
+    , m_password_hash(spwd.sp_pwdp)
     , m_uid(pwd.pw_uid)
     , m_uid(pwd.pw_uid)
     , m_gid(pwd.pw_gid)
     , m_gid(pwd.pw_gid)
     , m_gecos(pwd.pw_gecos)
     , m_gecos(pwd.pw_gecos)
@@ -142,7 +157,6 @@ Account::Account(const passwd& pwd, Vector<gid_t> extra_gids)
     , m_shell(pwd.pw_shell)
     , m_shell(pwd.pw_shell)
     , m_extra_gids(extra_gids)
     , m_extra_gids(extra_gids)
 {
 {
-    load_shadow_file();
 }
 }
 
 
 String Account::generate_passwd_file() const
 String Account::generate_passwd_file() const
@@ -179,50 +193,39 @@ String Account::generate_passwd_file() const
     return builder.to_string();
     return builder.to_string();
 }
 }
 
 
-void Account::load_shadow_file()
+String Account::generate_shadow_file() const
 {
 {
-    auto file_or_error = Core::File::open("/etc/shadow", Core::File::ReadOnly);
-    VERIFY(!file_or_error.is_error());
-    auto shadow_file = file_or_error.release_value();
-    VERIFY(shadow_file->is_open());
-
-    Vector<ShadowEntry> entries;
-
-    for (;;) {
-        auto line = shadow_file->read_line();
-        if (line.is_null())
-            break;
-        auto parts = line.split(':', true);
-        if (parts.size() != 2) {
-            dbgln("Malformed shadow entry, ignoring.");
-            continue;
-        }
-        const auto& username = parts[0];
-        const auto& password_hash = parts[1];
-        entries.append({ username, password_hash });
+    StringBuilder builder;
 
 
-        if (username == m_username) {
-            m_password_hash = password_hash;
-        }
-    }
+    setspent();
 
 
-    m_shadow_entries = move(entries);
-}
+    struct spwd* p;
+    errno = 0;
+    while ((p = getspent())) {
+        if (p->sp_namp == m_username) {
+            builder.appendff("{}:{}:{}:{}:{}:{}:{}:{}:{}\n",
+                m_username, m_password_hash,
+                p->sp_lstchg, p->sp_min,
+                p->sp_max, p->sp_warn,
+                p->sp_inact, p->sp_expire,
+                p->sp_flag);
 
 
-String Account::generate_shadow_file() const
-{
-    StringBuilder builder;
-    bool updated_entry_in_place = false;
-    for (auto& entry : m_shadow_entries) {
-        if (entry.username == m_username) {
-            updated_entry_in_place = true;
-            builder.appendff("{}:{}\n", m_username, m_password_hash);
         } else {
         } else {
-            builder.appendff("{}:{}\n", entry.username, entry.password_hash);
+            builder.appendff("{}:{}:{}:{}:{}:{}:{}:{}:{}\n",
+                p->sp_namp, p->sp_pwdp,
+                p->sp_lstchg, p->sp_min,
+                p->sp_max, p->sp_warn,
+                p->sp_inact, p->sp_expire,
+                p->sp_flag);
         }
         }
     }
     }
-    if (!updated_entry_in_place)
-        builder.appendff("{}:{}\n", m_username, m_password_hash);
+    endspent();
+
+    if (errno) {
+        dbgln("errno was non-zero after generating new passwd file.");
+        return {};
+    }
+
     return builder.to_string();
     return builder.to_string();
 }
 }
 
 

+ 3 - 9
Userland/Libraries/LibCore/Account.h

@@ -11,6 +11,7 @@
 #include <AK/Types.h>
 #include <AK/Types.h>
 #include <AK/Vector.h>
 #include <AK/Vector.h>
 #include <pwd.h>
 #include <pwd.h>
+#include <shadow.h>
 #include <sys/types.h>
 #include <sys/types.h>
 
 
 namespace Core {
 namespace Core {
@@ -46,10 +47,9 @@ public:
     bool sync();
     bool sync();
 
 
 private:
 private:
-    static Result<Account, String> from_passwd(const passwd&);
+    static Result<Account, String> from_passwd(const passwd&, const spwd&);
 
 
-    Account(const passwd& pwd, Vector<gid_t> extra_gids);
-    void load_shadow_file();
+    Account(const passwd& pwd, const spwd& spwd, Vector<gid_t> extra_gids);
 
 
     String generate_passwd_file() const;
     String generate_passwd_file() const;
     String generate_shadow_file() const;
     String generate_shadow_file() const;
@@ -63,12 +63,6 @@ private:
     String m_home_directory;
     String m_home_directory;
     String m_shell;
     String m_shell;
     Vector<gid_t> m_extra_gids;
     Vector<gid_t> m_extra_gids;
-
-    struct ShadowEntry {
-        String username;
-        String password_hash;
-    };
-    Vector<ShadowEntry> m_shadow_entries;
 };
 };
 
 
 }
 }

+ 2 - 2
Userland/Utilities/passwd.cpp

@@ -64,7 +64,7 @@ int main(int argc, char** argv)
 
 
     setpwent();
     setpwent();
 
 
-    if (pledge("stdio wpath cpath fattr tty", nullptr) < 0) {
+    if (pledge("stdio wpath rpath cpath fattr tty", nullptr) < 0) {
         perror("pledge");
         perror("pledge");
         return 1;
         return 1;
     }
     }
@@ -93,7 +93,7 @@ int main(int argc, char** argv)
         target_account.set_password(new_password.value().characters());
         target_account.set_password(new_password.value().characters());
     }
     }
 
 
-    if (pledge("stdio wpath cpath fattr", nullptr) < 0) {
+    if (pledge("stdio wpath rpath cpath fattr", nullptr) < 0) {
         perror("pledge");
         perror("pledge");
         return 1;
         return 1;
     }
     }