summaryrefslogtreecommitdiff
path: root/Userland/Utilities/usermod.cpp
blob: 2b4a1214076116c65556690e7ac91dc1f7f1eaeb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/*
 * Copyright (c) 2021, Brandon Pruitt <brapru@pm.me>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <LibCore/Account.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/File.h>
#include <LibCore/System.h>
#include <LibMain/Main.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

ErrorOr<int> serenity_main(Main::Arguments arguments)
{
    if (geteuid() != 0) {
        warnln("Not running as root :^(");
        return 1;
    }

    if (setegid(0) < 0) {
        perror("setegid");
        return 1;
    }

    TRY(Core::System::pledge("stdio wpath rpath cpath fattr tty"));
    TRY(Core::System::unveil("/etc", "rwc"));

    int uid = 0;
    int gid = 0;
    bool lock = false;
    bool unlock = false;
    const char* new_home_directory = nullptr;
    bool move_home = false;
    const char* shell = nullptr;
    const char* gecos = nullptr;
    const char* username = nullptr;

    auto args_parser = Core::ArgsParser();
    args_parser.set_general_help("Modify a user account");
    args_parser.add_option(uid, "The new numerical value of the user's ID", "uid", 'u', "uid");
    args_parser.add_option(gid, "The group number of the user's new initial login group", "gid", 'g', "gid");
    args_parser.add_option(lock, "Lock password", "lock", 'L');
    args_parser.add_option(unlock, "Unlock password", "unlock", 'U');
    args_parser.add_option(new_home_directory, "The user's new login directory", "home", 'd', "new-home");
    args_parser.add_option(move_home, "Move the content of the user's home directory to the new location", "move", 'm');
    args_parser.add_option(shell, "The name of the user's new login shell", "shell", 's', "path-to-shell");
    args_parser.add_option(gecos, "Change the GECOS field of the user", "gecos", 'n', "general-info");
    args_parser.add_positional_argument(username, "Username of the account to modify", "username");

    args_parser.parse(arguments);

    auto account_or_error = Core::Account::from_name(username);

    if (account_or_error.is_error()) {
        warnln("Core::Account::from_name: {}", account_or_error.error());
        return 1;
    }

    // target_account is the account we are modifying.
    auto& target_account = account_or_error.value();

    if (move_home) {
        TRY(Core::System::unveil(target_account.home_directory().characters(), "c"));
        TRY(Core::System::unveil(new_home_directory, "wc"));
    }

    unveil(nullptr, nullptr);

    if (uid) {
        if (uid < 0) {
            warnln("invalid uid {}", uid);
            return 1;
        }

        if (getpwuid(static_cast<uid_t>(uid))) {
            warnln("uid {} already exists", uid);
            return 1;
        }

        target_account.set_uid(uid);
    }

    if (gid) {
        if (gid < 0) {
            warnln("invalid gid {}", gid);
            return 1;
        }

        target_account.set_gid(gid);
    }

    if (lock) {
        target_account.set_password_enabled(false);
    }

    if (unlock) {
        target_account.set_password_enabled(true);
    }

    if (new_home_directory) {
        if (move_home) {
            int rc = rename(target_account.home_directory().characters(), new_home_directory);
            if (rc < 0) {
                if (errno == EXDEV) {
                    auto result = Core::File::copy_file_or_directory(
                        new_home_directory, target_account.home_directory().characters(),
                        Core::File::RecursionMode::Allowed,
                        Core::File::LinkMode::Disallowed,
                        Core::File::AddDuplicateFileMarker::No);

                    if (result.is_error()) {
                        warnln("usermod: could not move directory {} : {}", target_account.home_directory().characters(), static_cast<Error const&>(result.error()));
                        return 1;
                    }
                    rc = unlink(target_account.home_directory().characters());
                    if (rc < 0)
                        warnln("usermod: unlink {} : {}", target_account.home_directory().characters(), strerror(errno));
                } else {
                    warnln("usermod: could not move directory {} : {}", target_account.home_directory().characters(), strerror(errno));
                }
            }
        }

        target_account.set_home_directory(new_home_directory);
    }

    if (shell) {
        target_account.set_shell(shell);
    }

    if (gecos) {
        target_account.set_gecos(gecos);
    }

    TRY(Core::System::pledge("stdio wpath rpath cpath fattr"));
    if (!target_account.sync()) {
        perror("Core::Account::Sync");
        return 1;
    }

    return 0;
}