]>
Commit | Line | Data |
---|---|---|
9b2d9078 LP |
1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
2 | ||
3 | #include "errno-util.h" | |
4 | #include "format-util.h" | |
5 | #include "libcrypt-util.h" | |
6 | #include "strv.h" | |
7 | #include "user-record-nss.h" | |
8 | ||
ddee3ada ZJS |
9 | #define SET_IF(field, condition, value, fallback) \ |
10 | field = (condition) ? (value) : (fallback) | |
11 | ||
9b2d9078 LP |
12 | int nss_passwd_to_user_record( |
13 | const struct passwd *pwd, | |
14 | const struct spwd *spwd, | |
15 | UserRecord **ret) { | |
16 | ||
17 | _cleanup_(user_record_unrefp) UserRecord *hr = NULL; | |
18 | int r; | |
19 | ||
20 | assert(pwd); | |
21 | assert(ret); | |
22 | ||
23 | if (isempty(pwd->pw_name)) | |
24 | return -EINVAL; | |
25 | ||
26 | if (spwd && !streq_ptr(spwd->sp_namp, pwd->pw_name)) | |
27 | return -EINVAL; | |
28 | ||
29 | hr = user_record_new(); | |
30 | if (!hr) | |
31 | return -ENOMEM; | |
32 | ||
33 | r = free_and_strdup(&hr->user_name, pwd->pw_name); | |
34 | if (r < 0) | |
35 | return r; | |
36 | ||
192aee3c ZJS |
37 | r = free_and_strdup(&hr->real_name, |
38 | streq_ptr(pwd->pw_gecos, hr->user_name) ? NULL : empty_to_null(pwd->pw_gecos)); | |
39 | if (r < 0) | |
40 | return r; | |
9b2d9078 | 41 | |
192aee3c ZJS |
42 | r = free_and_strdup(&hr->home_directory, empty_to_null(pwd->pw_dir)); |
43 | if (r < 0) | |
44 | return r; | |
9b2d9078 | 45 | |
192aee3c ZJS |
46 | r = free_and_strdup(&hr->shell, empty_to_null(pwd->pw_shell)); |
47 | if (r < 0) | |
48 | return r; | |
9b2d9078 LP |
49 | |
50 | hr->uid = pwd->pw_uid; | |
51 | hr->gid = pwd->pw_gid; | |
52 | ||
ddee3ada ZJS |
53 | if (spwd && hashed_password_valid(spwd->sp_pwdp)) { |
54 | strv_free_erase(hr->hashed_password); | |
55 | hr->hashed_password = strv_new(spwd->sp_pwdp); | |
56 | if (!hr->hashed_password) | |
57 | return -ENOMEM; | |
58 | } else | |
9b2d9078 | 59 | hr->hashed_password = strv_free_erase(hr->hashed_password); |
ddee3ada ZJS |
60 | |
61 | /* shadow-utils suggests using "chage -E 0" (or -E 1, depending on which man page you check) | |
62 | * for locking a whole account, hence check for that. Note that it also defines a way to lock | |
63 | * just a password instead of the whole account, but that's mostly pointless in times of | |
64 | * password-less authorization, hence let's not bother. */ | |
65 | ||
66 | SET_IF(hr->locked, | |
67 | spwd && spwd->sp_expire >= 0, | |
68 | spwd->sp_expire <= 1, -1); | |
69 | ||
70 | SET_IF(hr->not_after_usec, | |
71 | spwd && spwd->sp_expire > 1 && (uint64_t) spwd->sp_expire < (UINT64_MAX-1)/USEC_PER_DAY, | |
72 | spwd->sp_expire * USEC_PER_DAY, UINT64_MAX); | |
73 | ||
74 | SET_IF(hr->password_change_now, | |
75 | spwd && spwd->sp_lstchg >= 0, | |
76 | spwd->sp_lstchg == 0, -1); | |
77 | ||
78 | SET_IF(hr->last_password_change_usec, | |
79 | spwd && spwd->sp_lstchg > 0 && (uint64_t) spwd->sp_lstchg <= (UINT64_MAX-1)/USEC_PER_DAY, | |
80 | spwd->sp_lstchg * USEC_PER_DAY, UINT64_MAX); | |
81 | ||
82 | SET_IF(hr->password_change_min_usec, | |
83 | spwd && spwd->sp_min > 0 && (uint64_t) spwd->sp_min <= (UINT64_MAX-1)/USEC_PER_DAY, | |
84 | spwd->sp_min * USEC_PER_DAY, UINT64_MAX); | |
85 | ||
86 | SET_IF(hr->password_change_max_usec, | |
87 | spwd && spwd->sp_max > 0 && (uint64_t) spwd->sp_max <= (UINT64_MAX-1)/USEC_PER_DAY, | |
88 | spwd->sp_max * USEC_PER_DAY, UINT64_MAX); | |
89 | ||
90 | SET_IF(hr->password_change_warn_usec, | |
91 | spwd && spwd->sp_warn > 0 && (uint64_t) spwd->sp_warn <= (UINT64_MAX-1)/USEC_PER_DAY, | |
92 | spwd->sp_warn * USEC_PER_DAY, UINT64_MAX); | |
93 | ||
94 | SET_IF(hr->password_change_inactive_usec, | |
95 | spwd && spwd->sp_inact > 0 && (uint64_t) spwd->sp_inact <= (UINT64_MAX-1)/USEC_PER_DAY, | |
96 | spwd->sp_inact * USEC_PER_DAY, UINT64_MAX); | |
9b2d9078 LP |
97 | |
98 | hr->json = json_variant_unref(hr->json); | |
99 | r = json_build(&hr->json, JSON_BUILD_OBJECT( | |
100 | JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(hr->user_name)), | |
101 | JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(hr->uid)), | |
102 | JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(hr->gid)), | |
103 | JSON_BUILD_PAIR_CONDITION(hr->real_name, "realName", JSON_BUILD_STRING(hr->real_name)), | |
104 | JSON_BUILD_PAIR_CONDITION(hr->home_directory, "homeDirectory", JSON_BUILD_STRING(hr->home_directory)), | |
105 | JSON_BUILD_PAIR_CONDITION(hr->shell, "shell", JSON_BUILD_STRING(hr->shell)), | |
106 | JSON_BUILD_PAIR_CONDITION(!strv_isempty(hr->hashed_password), "privileged", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRV(hr->hashed_password)))), | |
107 | JSON_BUILD_PAIR_CONDITION(hr->locked >= 0, "locked", JSON_BUILD_BOOLEAN(hr->locked)), | |
108 | JSON_BUILD_PAIR_CONDITION(hr->not_after_usec != UINT64_MAX, "notAfterUSec", JSON_BUILD_UNSIGNED(hr->not_after_usec)), | |
109 | JSON_BUILD_PAIR_CONDITION(hr->password_change_now >= 0, "passwordChangeNow", JSON_BUILD_BOOLEAN(hr->password_change_now)), | |
110 | JSON_BUILD_PAIR_CONDITION(hr->last_password_change_usec != UINT64_MAX, "lastPasswordChangeUSec", JSON_BUILD_UNSIGNED(hr->last_password_change_usec)), | |
111 | JSON_BUILD_PAIR_CONDITION(hr->password_change_min_usec != UINT64_MAX, "passwordChangeMinUSec", JSON_BUILD_UNSIGNED(hr->password_change_min_usec)), | |
112 | JSON_BUILD_PAIR_CONDITION(hr->password_change_max_usec != UINT64_MAX, "passwordChangeMaxUSec", JSON_BUILD_UNSIGNED(hr->password_change_max_usec)), | |
113 | JSON_BUILD_PAIR_CONDITION(hr->password_change_warn_usec != UINT64_MAX, "passwordChangeWarnUSec", JSON_BUILD_UNSIGNED(hr->password_change_warn_usec)), | |
114 | JSON_BUILD_PAIR_CONDITION(hr->password_change_inactive_usec != UINT64_MAX, "passwordChangeInactiveUSec", JSON_BUILD_UNSIGNED(hr->password_change_inactive_usec)))); | |
115 | ||
116 | if (r < 0) | |
117 | return r; | |
118 | ||
119 | hr->mask = USER_RECORD_REGULAR | | |
120 | (!strv_isempty(hr->hashed_password) ? USER_RECORD_PRIVILEGED : 0); | |
121 | ||
122 | *ret = TAKE_PTR(hr); | |
123 | return 0; | |
124 | } | |
125 | ||
126 | int nss_spwd_for_passwd(const struct passwd *pwd, struct spwd *ret_spwd, char **ret_buffer) { | |
127 | size_t buflen = 4096; | |
128 | int r; | |
129 | ||
130 | assert(pwd); | |
131 | assert(ret_spwd); | |
132 | assert(ret_buffer); | |
133 | ||
134 | for (;;) { | |
135 | _cleanup_free_ char *buf = NULL; | |
136 | struct spwd spwd, *result; | |
137 | ||
138 | buf = malloc(buflen); | |
139 | if (!buf) | |
140 | return -ENOMEM; | |
141 | ||
142 | r = getspnam_r(pwd->pw_name, &spwd, buf, buflen, &result); | |
143 | if (r == 0) { | |
144 | if (!result) | |
145 | return -ESRCH; | |
146 | ||
147 | *ret_spwd = *result; | |
148 | *ret_buffer = TAKE_PTR(buf); | |
149 | return 0; | |
150 | } | |
151 | if (r < 0) | |
152 | return -EIO; /* Weird, this should not return negative! */ | |
153 | if (r != ERANGE) | |
154 | return -r; | |
155 | ||
156 | if (buflen > SIZE_MAX / 2) | |
157 | return -ERANGE; | |
158 | ||
159 | buflen *= 2; | |
160 | buf = mfree(buf); | |
161 | } | |
162 | } | |
163 | ||
164 | int nss_user_record_by_name(const char *name, UserRecord **ret) { | |
165 | _cleanup_free_ char *buf = NULL, *sbuf = NULL; | |
166 | struct passwd pwd, *result; | |
167 | bool incomplete = false; | |
168 | size_t buflen = 4096; | |
169 | struct spwd spwd; | |
170 | int r; | |
171 | ||
172 | assert(name); | |
173 | assert(ret); | |
174 | ||
175 | for (;;) { | |
176 | buf = malloc(buflen); | |
177 | if (!buf) | |
178 | return -ENOMEM; | |
179 | ||
180 | r = getpwnam_r(name, &pwd, buf, buflen, &result); | |
181 | if (r == 0) { | |
182 | if (!result) | |
183 | return -ESRCH; | |
184 | ||
185 | break; | |
186 | } | |
187 | ||
188 | if (r < 0) | |
189 | return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwnam_r() returned a negative value"); | |
190 | if (r != ERANGE) | |
191 | return -r; | |
192 | ||
193 | if (buflen > SIZE_MAX / 2) | |
194 | return -ERANGE; | |
195 | ||
196 | buflen *= 2; | |
197 | buf = mfree(buf); | |
198 | } | |
199 | ||
200 | r = nss_spwd_for_passwd(result, &spwd, &sbuf); | |
201 | if (r < 0) { | |
202 | log_debug_errno(r, "Failed to do shadow lookup for user %s, ignoring: %m", name); | |
203 | incomplete = ERRNO_IS_PRIVILEGE(r); | |
204 | } | |
205 | ||
206 | r = nss_passwd_to_user_record(result, r >= 0 ? &spwd : NULL, ret); | |
207 | if (r < 0) | |
208 | return r; | |
209 | ||
210 | (*ret)->incomplete = incomplete; | |
211 | return 0; | |
212 | } | |
213 | ||
214 | int nss_user_record_by_uid(uid_t uid, UserRecord **ret) { | |
215 | _cleanup_free_ char *buf = NULL, *sbuf = NULL; | |
216 | struct passwd pwd, *result; | |
217 | bool incomplete = false; | |
218 | size_t buflen = 4096; | |
219 | struct spwd spwd; | |
220 | int r; | |
221 | ||
222 | assert(ret); | |
223 | ||
224 | for (;;) { | |
225 | buf = malloc(buflen); | |
226 | if (!buf) | |
227 | return -ENOMEM; | |
228 | ||
229 | r = getpwuid_r(uid, &pwd, buf, buflen, &result); | |
230 | if (r == 0) { | |
231 | if (!result) | |
232 | return -ESRCH; | |
233 | ||
234 | break; | |
235 | } | |
236 | if (r < 0) | |
237 | return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwuid_r() returned a negative value"); | |
238 | if (r != ERANGE) | |
239 | return -r; | |
240 | ||
241 | if (buflen > SIZE_MAX / 2) | |
242 | return -ERANGE; | |
243 | ||
244 | buflen *= 2; | |
245 | buf = mfree(buf); | |
246 | } | |
247 | ||
248 | r = nss_spwd_for_passwd(result, &spwd, &sbuf); | |
249 | if (r < 0) { | |
250 | log_debug_errno(r, "Failed to do shadow lookup for UID " UID_FMT ", ignoring: %m", uid); | |
251 | incomplete = ERRNO_IS_PRIVILEGE(r); | |
252 | } | |
253 | ||
254 | r = nss_passwd_to_user_record(result, r >= 0 ? &spwd : NULL, ret); | |
255 | if (r < 0) | |
256 | return r; | |
257 | ||
258 | (*ret)->incomplete = incomplete; | |
259 | return 0; | |
260 | } |