Serenity Operating System
at portability 139 lines 4.9 kB view raw
1/* 2 * Copyright (c) 2019-2020, Jesse Buhagiar <jooster669@gmail.com> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26#include <AK/String.h> 27#include <LibCore/ArgsParser.h> 28#include <ctype.h> 29#include <pwd.h> 30#include <stdio.h> 31#include <sys/stat.h> 32#include <sys/types.h> 33#include <unistd.h> 34 35constexpr uid_t BASE_UID = 1000; 36constexpr gid_t USERS_GID = 100; 37constexpr const char* DEFAULT_SHELL = "/bin/Shell"; 38 39int main(int argc, char** argv) 40{ 41 const char* home_path = nullptr; 42 int uid = 0; 43 int gid = USERS_GID; 44 bool create_home_dir = false; 45 const char* shell = DEFAULT_SHELL; 46 const char* gecos = ""; 47 const char* username = nullptr; 48 49 Core::ArgsParser args_parser; 50 args_parser.add_option(home_path, "Home directory for the new user", "home-dir", 'd', "path"); 51 args_parser.add_option(uid, "User ID (uid) for the new user", "uid", 'u', "uid"); 52 args_parser.add_option(gid, "Group ID (gid) for the new user", "gid", 'g', "gid"); 53 args_parser.add_option(create_home_dir, "Create home directory if it does not exist", "create-home", 'm'); 54 args_parser.add_option(shell, "Path to the default shell binary for the new user", "shell", 's', "path-to-shell"); 55 args_parser.add_option(gecos, "GECOS name of the new user", "gecos", 'n', "general-info"); 56 args_parser.add_positional_argument(username, "Login user identity (username)", "login"); 57 58 args_parser.parse(argc, argv); 59 60 // Let's run a quick sanity check on username 61 if (strpbrk(username, "\\/!@#$%^&*()~+=`:\n")) { 62 fprintf(stderr, "invalid character in username, %s\n", username); 63 return 1; 64 } 65 66 // Disallow names starting with _ and - 67 if (username[0] == '_' || username[0] == '-' || !isalpha(username[0])) { 68 fprintf(stderr, "invalid username, %s\n", username); 69 return 1; 70 } 71 72 if (uid < 0) { 73 fprintf(stderr, "invalid uid %d!\n", uid); 74 return 3; 75 } 76 77 // First, let's sort out the uid for the user 78 if (uid > 0) { 79 if (getpwuid(static_cast<uid_t>(uid))) { 80 fprintf(stderr, "uid %u already exists!\n", uid); 81 return 4; 82 } 83 84 } else { 85 for (uid = BASE_UID; getpwuid(static_cast<uid_t>(uid)); uid++) { 86 } 87 } 88 89 if (gid < 0) { 90 fprintf(stderr, "invalid gid %d\n", gid); 91 return 3; 92 } 93 94 FILE* pwfile = fopen("/etc/passwd", "a"); 95 if (!pwfile) { 96 perror("failed to open /etc/passwd"); 97 return 1; 98 } 99 100 String home; 101 if (!home_path) 102 home = String::format("/home/%s", username); 103 else 104 home = home_path; 105 106 if (create_home_dir) { 107 if (mkdir(home.characters(), 0700) < 0) { 108 perror(String::format("failed to create directory %s", home.characters()).characters()); 109 return 12; 110 } 111 112 if (chown(home.characters(), static_cast<uid_t>(uid), static_cast<gid_t>(gid)) < 0) { 113 perror(String::format("failed to chown %s to %u:%u", home.characters(), uid, gid).characters()); 114 115 if (rmdir(home.characters()) < 0) { 116 perror(String::format("failed to rmdir %s", home.characters()).characters()); 117 return 12; 118 } 119 return 12; 120 } 121 } 122 123 struct passwd p; 124 p.pw_name = const_cast<char*>(username); 125 p.pw_dir = const_cast<char*>(home.characters()); 126 p.pw_uid = static_cast<uid_t>(uid); 127 p.pw_gid = static_cast<gid_t>(gid); 128 p.pw_shell = const_cast<char*>(shell); 129 p.pw_gecos = const_cast<char*>(gecos); 130 131 if (putpwent(&p, pwfile) < 0) { 132 perror("putpwent"); 133 return 1; 134 } 135 136 fclose(pwfile); 137 138 return 0; 139}