Serenity Operating System
at master 247 lines 6.7 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2021, Maxime Friess <M4x1me@pm.me> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include <AK/DeprecatedString.h> 9#include <AK/ScopeGuard.h> 10#include <AK/Vector.h> 11#include <errno.h> 12#include <errno_codes.h> 13#include <grp.h> 14#include <stdio.h> 15#include <string.h> 16#include <unistd.h> 17 18extern "C" { 19 20static FILE* s_stream = nullptr; 21static unsigned s_line_number = 0; 22 23void setgrent() 24{ 25 s_line_number = 0; 26 if (s_stream) { 27 rewind(s_stream); 28 } else { 29 s_stream = fopen("/etc/group", "r"); 30 if (!s_stream) { 31 perror("open /etc/group"); 32 } 33 } 34} 35 36void endgrent() 37{ 38 s_line_number = 0; 39 if (s_stream) { 40 fclose(s_stream); 41 s_stream = nullptr; 42 } 43} 44 45struct group* getgrgid(gid_t gid) 46{ 47 setgrent(); 48 ScopeGuard guard = [] { endgrent(); }; 49 while (auto* gr = getgrent()) { 50 if (gr->gr_gid == gid) 51 return gr; 52 } 53 return nullptr; 54} 55 56struct group* getgrnam(char const* name) 57{ 58 setgrent(); 59 ScopeGuard guard = [] { endgrent(); }; 60 while (auto* gr = getgrent()) { 61 if (!strcmp(gr->gr_name, name)) 62 return gr; 63 } 64 return nullptr; 65} 66 67static bool parse_grpdb_entry(char* buffer, size_t buffer_size, struct group& group_entry) 68{ 69 size_t line_length = strlen(buffer); 70 71 for (size_t i = 0; i < line_length; ++i) { 72 auto& ch = buffer[i]; 73 if (ch == '\r' || ch == '\n') 74 line_length = i; 75 if (ch == ':' || ch == '\r' || ch == '\n') 76 ch = '\0'; 77 } 78 79 auto line = StringView { buffer, line_length }; 80 auto parts = line.split_view('\0', SplitBehavior::KeepEmpty); 81 if (parts.size() != 4) { 82 warnln("parse_grpdb_entry(): Malformed entry on line {}: '{}' has {} parts", s_line_number, line, parts.size()); 83 return false; 84 } 85 86 auto name = parts[0]; 87 auto passwd = parts[1]; 88 auto& gid_string = parts[2]; 89 StringView members_string = parts[3]; 90 91 auto gid = gid_string.to_uint(); 92 if (!gid.has_value()) { 93 warnln("parse_grpdb_entry(): Malformed GID on line {}", s_line_number); 94 return false; 95 } 96 97 // Generate table of members pointers. 98 Vector<char const*> members_ptrs; 99 auto members = members_string.split_view(','); 100 members_ptrs.clear_with_capacity(); 101 members_ptrs.ensure_capacity(members.size() + 1); 102 for (auto& member : members) { 103 members_ptrs.append(member.characters_without_null_termination()); 104 } 105 members_ptrs.append(nullptr); 106 107 // Convert remaining commas to null terminators. Last gr_mem entry uses the whole line's null terminator. 108 // 3 for 3 null terminators. 109 size_t members_position = name.length() + passwd.length() + gid_string.length() + 3; 110 for (size_t i = members_position; i < line_length; i++) 111 if (buffer[i] == ',') 112 buffer[i] = '\0'; 113 114 // Must have room at the end of the buffer for the new table. 115 // Remaining space is one byte past null terminator generated by original line. 116 size_t bytes_used = line_length + 1; 117 size_t ptrs_size = sizeof(char const*) * members_ptrs.size(); 118 119 if (bytes_used + ptrs_size < buffer_size) { 120 char* buffer_remaining = buffer + bytes_used; 121 memcpy(buffer_remaining, members_ptrs.data(), ptrs_size); 122 123 group_entry.gr_gid = gid.value(); 124 group_entry.gr_name = const_cast<char*>(name.characters_without_null_termination()); 125 group_entry.gr_passwd = const_cast<char*>(passwd.characters_without_null_termination()); 126 group_entry.gr_mem = reinterpret_cast<char**>(buffer_remaining); 127 128 return true; 129 } else { 130 warnln("parse_grpdb_entry(): Provided buffer too small to fit table for gr_mem"); 131 errno = ERANGE; 132 return false; 133 } 134} 135 136struct group* getgrent() 137{ 138 static struct group group_entry; 139 static char buffer[1024]; 140 struct group* result; 141 if (getgrent_r(&group_entry, buffer, sizeof(buffer), &result) < 0) 142 return nullptr; 143 return result; 144} 145 146int getgrent_r(struct group* group_buf, char* buffer, size_t buffer_size, struct group** group_entry_ptr) 147{ 148 if (!s_stream) 149 setgrent(); 150 151 while (true) { 152 if (!s_stream || feof(s_stream)) { 153 errno = EIO; 154 return -1; 155 } 156 157 if (ferror(s_stream)) { 158 warnln("getgrent_r(): Read error: {}", strerror(ferror(s_stream))); 159 errno = EIO; 160 return -1; 161 } 162 163 ++s_line_number; 164 char* s = fgets(buffer, buffer_size, s_stream); 165 166 // Silently tolerate an empty line at the end. 167 if ((!s || !s[0]) && feof(s_stream)) { 168 *group_entry_ptr = nullptr; 169 return 0; 170 } 171 172 if (strlen(s) == buffer_size - 1) { 173 errno = ERANGE; 174 return -1; 175 } 176 177 if (parse_grpdb_entry(buffer, buffer_size, *group_buf)) { 178 *group_entry_ptr = group_buf; 179 return 0; 180 } 181 // Otherwise, proceed to the next line. 182 } 183} 184 185int initgroups(char const* user, gid_t extra_gid) 186{ 187 size_t count = 0; 188 gid_t gids[32]; 189 bool extra_gid_added = false; 190 setgrent(); 191 while (auto* gr = getgrent()) { 192 for (auto* mem = gr->gr_mem; *mem; ++mem) { 193 if (!strcmp(*mem, user)) { 194 gids[count++] = gr->gr_gid; 195 if (gr->gr_gid == extra_gid) 196 extra_gid_added = true; 197 break; 198 } 199 } 200 } 201 endgrent(); 202 if (!extra_gid_added) 203 gids[count++] = extra_gid; 204 return setgroups(count, gids); 205} 206 207int putgrent(const struct group* group, FILE* stream) 208{ 209 if (!group || !stream || !group->gr_name || !group->gr_passwd) { 210 errno = EINVAL; 211 return -1; 212 } 213 214 auto is_valid_field = [](char const* str) { 215 return str && !strpbrk(str, ":\n"); 216 }; 217 218 if (!is_valid_field(group->gr_name) || !is_valid_field(group->gr_passwd)) { 219 errno = EINVAL; 220 return -1; 221 } 222 223 int nwritten = fprintf(stream, "%s:%s:%u:", group->gr_name, group->gr_passwd, group->gr_gid); 224 if (!nwritten || nwritten < 0) { 225 errno = ferror(stream); 226 return -1; 227 } 228 229 if (group->gr_mem) { 230 for (size_t i = 0; group->gr_mem[i] != nullptr; i++) { 231 nwritten = fprintf(stream, i == 0 ? "%s" : ",%s", group->gr_mem[i]); 232 if (!nwritten || nwritten < 0) { 233 errno = ferror(stream); 234 return -1; 235 } 236 } 237 } 238 239 nwritten = fprintf(stream, "\n"); 240 if (!nwritten || nwritten < 0) { 241 errno = ferror(stream); 242 return -1; 243 } 244 245 return 0; 246} 247}