Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2021, Gunnar Beutner <gbeutner@serenityos.org>
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include <AK/DeprecatedString.h>
9#include <AK/TemporaryChange.h>
10#include <AK/Vector.h>
11#include <errno.h>
12#include <shadow.h>
13#include <stdio.h>
14#include <string.h>
15#include <unistd.h>
16
17extern "C" {
18
19static FILE* s_stream = nullptr;
20static unsigned s_line_number = 0;
21static struct spwd s_shadow_entry;
22
23static DeprecatedString s_name;
24static DeprecatedString s_pwdp;
25
26void setspent()
27{
28 s_line_number = 0;
29 if (s_stream) {
30 rewind(s_stream);
31 } else {
32 s_stream = fopen("/etc/shadow", "r");
33 if (!s_stream) {
34 dbgln("open /etc/shadow failed: {}", strerror(errno));
35 }
36 }
37}
38
39void endspent()
40{
41 s_line_number = 0;
42 if (s_stream) {
43 fclose(s_stream);
44 s_stream = nullptr;
45 }
46
47 memset(&s_shadow_entry, 0, sizeof(s_shadow_entry));
48
49 s_name = {};
50 s_pwdp = {};
51}
52
53struct spwd* getspnam(char const* name)
54{
55 setspent();
56 while (auto* sp = getspent()) {
57 if (!strcmp(sp->sp_namp, name)) {
58 return sp;
59 }
60 }
61 return nullptr;
62}
63
64static bool parse_shadow_entry(DeprecatedString const& line)
65{
66 auto parts = line.split_view(':', SplitBehavior::KeepEmpty);
67 if (parts.size() != 9) {
68 dbgln("getspent(): Malformed entry on line {}", s_line_number);
69 return false;
70 }
71
72 s_name = parts[0];
73 s_pwdp = parts[1];
74 auto& lstchg_string = parts[2];
75 auto& min_string = parts[3];
76 auto& max_string = parts[4];
77 auto& warn_string = parts[5];
78 auto& inact_string = parts[6];
79 auto& expire_string = parts[7];
80 auto& flag_string = parts[8];
81
82 auto lstchg = lstchg_string.to_int();
83 if (!lstchg.has_value()) {
84 dbgln("getspent(): Malformed lstchg on line {}", s_line_number);
85 return false;
86 }
87
88 if (min_string.is_empty())
89 min_string = "-1"sv;
90 auto min_value = min_string.to_int();
91 if (!min_value.has_value()) {
92 dbgln("getspent(): Malformed min value on line {}", s_line_number);
93 return false;
94 }
95
96 if (max_string.is_empty())
97 max_string = "-1"sv;
98 auto max_value = max_string.to_int();
99 if (!max_value.has_value()) {
100 dbgln("getspent(): Malformed max value on line {}", s_line_number);
101 return false;
102 }
103
104 if (warn_string.is_empty())
105 warn_string = "-1"sv;
106 auto warn = warn_string.to_int();
107 if (!warn.has_value()) {
108 dbgln("getspent(): Malformed warn on line {}", s_line_number);
109 return false;
110 }
111
112 if (inact_string.is_empty())
113 inact_string = "-1"sv;
114 auto inact = inact_string.to_int();
115 if (!inact.has_value()) {
116 dbgln("getspent(): Malformed inact on line {}", s_line_number);
117 return false;
118 }
119
120 if (expire_string.is_empty())
121 expire_string = "-1"sv;
122 auto expire = expire_string.to_int();
123 if (!expire.has_value()) {
124 dbgln("getspent(): Malformed expire on line {}", s_line_number);
125 return false;
126 }
127
128 if (flag_string.is_empty())
129 flag_string = "0"sv;
130 auto flag = flag_string.to_int();
131 if (!flag.has_value()) {
132 dbgln("getspent(): Malformed flag on line {}", s_line_number);
133 return false;
134 }
135
136 s_shadow_entry.sp_namp = const_cast<char*>(s_name.characters());
137 s_shadow_entry.sp_pwdp = const_cast<char*>(s_pwdp.characters());
138 s_shadow_entry.sp_lstchg = lstchg.value();
139 s_shadow_entry.sp_min = min_value.value();
140 s_shadow_entry.sp_max = max_value.value();
141 s_shadow_entry.sp_warn = warn.value();
142 s_shadow_entry.sp_inact = inact.value();
143 s_shadow_entry.sp_expire = expire.value();
144 s_shadow_entry.sp_flag = flag.value();
145
146 return true;
147}
148
149struct spwd* getspent()
150{
151 if (!s_stream)
152 setspent();
153
154 while (true) {
155 if (!s_stream || feof(s_stream))
156 return nullptr;
157
158 if (ferror(s_stream)) {
159 dbgln("getspent(): Read error: {}", strerror(ferror(s_stream)));
160 return nullptr;
161 }
162
163 char buffer[1024];
164 ++s_line_number;
165 char* s = fgets(buffer, sizeof(buffer), s_stream);
166
167 // Silently tolerate an empty line at the end.
168 if ((!s || !s[0]) && feof(s_stream))
169 return nullptr;
170
171 DeprecatedString line(s, Chomp);
172 if (parse_shadow_entry(line))
173 return &s_shadow_entry;
174 // Otherwise, proceed to the next line.
175 }
176}
177
178static void construct_spwd(struct spwd* sp, char* buf, struct spwd** result)
179{
180 auto* buf_name = &buf[0];
181 auto* buf_pwdp = &buf[s_name.length() + 1];
182
183 bool ok = true;
184 ok = ok && s_name.copy_characters_to_buffer(buf_name, s_name.length() + 1);
185 ok = ok && s_pwdp.copy_characters_to_buffer(buf_pwdp, s_pwdp.length() + 1);
186
187 VERIFY(ok);
188
189 *result = sp;
190 sp->sp_namp = buf_name;
191 sp->sp_pwdp = buf_pwdp;
192}
193
194int getspnam_r(char const* name, struct spwd* sp, char* buf, size_t buflen, struct spwd** result)
195{
196 // FIXME: This is a HACK!
197 TemporaryChange name_change { s_name, {} };
198 TemporaryChange pwdp_change { s_pwdp, {} };
199
200 setspent();
201 bool found = false;
202 while (auto* sp = getspent()) {
203 if (!strcmp(sp->sp_namp, name)) {
204 found = true;
205 break;
206 }
207 }
208
209 if (!found) {
210 *result = nullptr;
211 return 0;
212 }
213
214 auto const total_buffer_length = s_name.length() + s_pwdp.length() + 8;
215 if (buflen < total_buffer_length)
216 return ERANGE;
217
218 construct_spwd(sp, buf, result);
219 return 0;
220}
221
222int putspent(struct spwd* p, FILE* stream)
223{
224 if (!p || !stream || !p->sp_namp || !p->sp_pwdp) {
225 errno = EINVAL;
226 return -1;
227 }
228
229 auto is_valid_field = [](char const* str) {
230 return str && !strpbrk(str, ":\n");
231 };
232
233 if (!is_valid_field(p->sp_namp) || !is_valid_field(p->sp_pwdp)) {
234 errno = EINVAL;
235 return -1;
236 }
237
238 int nwritten;
239
240 nwritten = fprintf(stream, "%s:%s:", p->sp_namp, p->sp_pwdp);
241 if (!nwritten || nwritten < 0) {
242 errno = ferror(stream);
243 return -1;
244 }
245
246 if (p->sp_lstchg != (long int)-1)
247 nwritten = fprintf(stream, "%ld:", p->sp_lstchg);
248 else
249 nwritten = fprintf(stream, "%c", ':');
250 if (!nwritten || nwritten < 0) {
251 errno = ferror(stream);
252 return -1;
253 }
254
255 if (p->sp_min != (long int)-1)
256 nwritten = fprintf(stream, "%ld:", p->sp_min);
257 else
258 nwritten = fprintf(stream, "%c", ':');
259 if (!nwritten || nwritten < 0) {
260 errno = ferror(stream);
261 return -1;
262 }
263
264 if (p->sp_max != (long int)-1)
265 nwritten = fprintf(stream, "%ld:", p->sp_max);
266 else
267 nwritten = fprintf(stream, "%c", ':');
268 if (!nwritten || nwritten < 0) {
269 errno = ferror(stream);
270 return -1;
271 }
272
273 if (p->sp_warn != (long int)-1)
274 nwritten = fprintf(stream, "%ld:", p->sp_warn);
275 else
276 nwritten = fprintf(stream, "%c", ':');
277 if (!nwritten || nwritten < 0) {
278 errno = ferror(stream);
279 return -1;
280 }
281
282 if (p->sp_inact != (long int)-1)
283 nwritten = fprintf(stream, "%ld:", p->sp_inact);
284 else
285 nwritten = fprintf(stream, "%c", ':');
286 if (!nwritten || nwritten < 0) {
287 errno = ferror(stream);
288 return -1;
289 }
290
291 if (p->sp_expire != (long int)-1)
292 nwritten = fprintf(stream, "%ld:", p->sp_expire);
293 else
294 nwritten = fprintf(stream, "%c", ':');
295 if (!nwritten || nwritten < 0) {
296 errno = ferror(stream);
297 return -1;
298 }
299
300 if (p->sp_flag != (unsigned long int)-1)
301 nwritten = fprintf(stream, "%ld\n", p->sp_flag);
302 else
303 nwritten = fprintf(stream, "%c", '\n');
304 if (!nwritten || nwritten < 0) {
305 errno = ferror(stream);
306 return -1;
307 }
308
309 return 0;
310}
311}