1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include "format-util.h"
4 #include "fs-util.h"
5 #include "process-util.h"
6 #include "rlimit-util.h"
7 #include "strv.h"
8 #include "terminal-util.h"
9 #include "user-record-show.h"
10 #include "user-util.h"
11 #include "userdb.h"
12 
user_record_state_color(const char * state)13 const char *user_record_state_color(const char *state) {
14         if (STR_IN_SET(state, "unfixated", "absent"))
15                 return ansi_grey();
16         else if (streq(state, "active"))
17                 return ansi_highlight_green();
18         else if (STR_IN_SET(state, "locked", "dirty"))
19                  return ansi_highlight_yellow();
20 
21         return NULL;
22 }
23 
user_record_show(UserRecord * hr,bool show_full_group_info)24 void user_record_show(UserRecord *hr, bool show_full_group_info) {
25         const char *hd, *ip, *shell;
26         UserStorage storage;
27         usec_t t;
28         size_t k;
29         int r, b;
30 
31         printf("   User name: %s\n",
32                user_record_user_name_and_realm(hr));
33 
34         if (hr->state) {
35                 const char *color;
36 
37                 color = user_record_state_color(hr->state);
38 
39                 printf("       State: %s%s%s\n",
40                        strempty(color), hr->state, color ? ansi_normal() : "");
41         }
42 
43         printf(" Disposition: %s\n", user_disposition_to_string(user_record_disposition(hr)));
44 
45         if (hr->last_change_usec != USEC_INFINITY) {
46                 printf(" Last Change: %s\n", FORMAT_TIMESTAMP(hr->last_change_usec));
47 
48                 if (hr->last_change_usec > now(CLOCK_REALTIME))
49                         printf("              %sModification time lies in the future, system clock wrong?%s\n",
50                                ansi_highlight_yellow(), ansi_normal());
51         }
52 
53         if (hr->last_password_change_usec != USEC_INFINITY &&
54             hr->last_password_change_usec != hr->last_change_usec)
55                 printf(" Last Passw.: %s\n", FORMAT_TIMESTAMP(hr->last_password_change_usec));
56 
57         r = user_record_test_blocked(hr);
58         switch (r) {
59 
60         case -ENOLCK:
61                 printf("    Login OK: %sno%s (record is locked)\n", ansi_highlight_red(), ansi_normal());
62                 break;
63 
64         case -EL2HLT:
65                 printf("    Login OK: %sno%s (record not valid yet))\n", ansi_highlight_red(), ansi_normal());
66                 break;
67 
68         case -EL3HLT:
69                 printf("    Login OK: %sno%s (record not valid anymore))\n", ansi_highlight_red(), ansi_normal());
70                 break;
71 
72         case -ESTALE:
73         default: {
74                 usec_t y;
75 
76                 if (r < 0 && r != -ESTALE) {
77                         errno = -r;
78                         printf("    Login OK: %sno%s (%m)\n", ansi_highlight_red(), ansi_normal());
79                         break;
80                 }
81 
82                 if (is_nologin_shell(user_record_shell(hr))) {
83                         printf("    Login OK: %sno%s (nologin shell)\n", ansi_highlight_red(), ansi_normal());
84                         break;
85                 }
86 
87                 y = user_record_ratelimit_next_try(hr);
88                 if (y != USEC_INFINITY && y > now(CLOCK_REALTIME)) {
89                         printf("    Login OK: %sno%s (ratelimit)\n", ansi_highlight_red(), ansi_normal());
90                         break;
91                 }
92 
93                 printf("    Login OK: %syes%s\n", ansi_highlight_green(), ansi_normal());
94                 break;
95         }}
96 
97         r = user_record_test_password_change_required(hr);
98         switch (r) {
99 
100         case -EKEYREVOKED:
101                 printf(" Password OK: %schange now%s\n", ansi_highlight_yellow(), ansi_normal());
102                 break;
103 
104         case -EOWNERDEAD:
105                 printf(" Password OK: %sexpired%s (change now!)\n", ansi_highlight_yellow(), ansi_normal());
106                 break;
107 
108         case -EKEYREJECTED:
109                 printf(" Password OK: %sexpired%s (for good)\n", ansi_highlight_red(), ansi_normal());
110                 break;
111 
112         case -EKEYEXPIRED:
113                 printf(" Password OK: %sexpires soon%s\n", ansi_highlight_yellow(), ansi_normal());
114                 break;
115 
116         case -ENETDOWN:
117                 printf(" Password OK: %sno timestamp%s\n", ansi_highlight_red(), ansi_normal());
118                 break;
119 
120         case -EROFS:
121                 printf(" Password OK: %schange not permitted%s\n", ansi_highlight_yellow(), ansi_normal());
122                 break;
123 
124         case -ESTALE:
125                 printf(" Password OK: %slast password change in future%s\n", ansi_highlight_yellow(), ansi_normal());
126                 break;
127 
128         default:
129                 if (r < 0) {
130                         errno = -r;
131                         printf(" Password OK: %sno%s (%m)\n", ansi_highlight_yellow(), ansi_normal());
132                         break;
133                 }
134 
135                 if (strv_isempty(hr->hashed_password)) {
136                         if (hr->incomplete) /* Record might be incomplete, due to privs */
137                                 break;
138                         printf(" Password OK: %sno%s (none set)\n", ansi_highlight(), ansi_normal());
139                         break;
140                 }
141                 if (strv_contains(hr->hashed_password, "")) {
142                         printf(" Password OK: %sno%s (empty set)\n", ansi_highlight_red(), ansi_normal());
143                         break;
144                 }
145                 bool has_valid_passwords = false;
146                 STRV_FOREACH(p, hr->hashed_password)
147                         if (!hashed_password_is_locked_or_invalid(*p)) {
148                                 has_valid_passwords = true;
149                                 break;
150                         }
151                 if (has_valid_passwords)
152                         printf(" Password OK: %syes%s\n", ansi_highlight_green(), ansi_normal());
153                 else
154                         printf(" Password OK: %sno%s (locked)\n", ansi_highlight(), ansi_normal());
155         }
156         if (uid_is_valid(hr->uid))
157                 printf("         UID: " UID_FMT "\n", hr->uid);
158         if (gid_is_valid(hr->gid)) {
159                 if (show_full_group_info) {
160                         _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
161 
162                         r = groupdb_by_gid(hr->gid, 0, &gr);
163                         if (r < 0) {
164                                 errno = -r;
165                                 printf("         GID: " GID_FMT " (unresolvable: %m)\n", hr->gid);
166                         } else
167                                 printf("         GID: " GID_FMT " (%s)\n", hr->gid, gr->group_name);
168                 } else
169                         printf("         GID: " GID_FMT "\n", hr->gid);
170         } else if (uid_is_valid(hr->uid)) /* Show UID as GID if not separately configured */
171                 printf("         GID: " GID_FMT "\n", (gid_t) hr->uid);
172 
173         if (show_full_group_info) {
174                 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
175 
176                 r = membershipdb_by_user(hr->user_name, 0, &iterator);
177                 if (r < 0) {
178                         errno = -r;
179                         printf(" Aux. Groups: (can't acquire: %m)\n");
180                 } else {
181                         const char *prefix = " Aux. Groups:";
182 
183                         for (;;) {
184                                 _cleanup_free_ char *group = NULL;
185 
186                                 r = membershipdb_iterator_get(iterator, NULL, &group);
187                                 if (r == -ESRCH)
188                                         break;
189                                 if (r < 0) {
190                                         errno = -r;
191                                         printf("%s (can't iterate: %m)\n", prefix);
192                                         break;
193                                 }
194 
195                                 printf("%s %s\n", prefix, group);
196                                 prefix = "             ";
197                         }
198                 }
199         }
200 
201         if (hr->real_name && !streq(hr->real_name, hr->user_name))
202                 printf("   Real Name: %s\n", hr->real_name);
203 
204         hd = user_record_home_directory(hr);
205         if (hd)
206                 printf("   Directory: %s\n", hd);
207 
208         storage = user_record_storage(hr);
209         if (storage >= 0) /* Let's be political, and clarify which storage we like, and which we don't. About CIFS we don't complain. */
210                 printf("     Storage: %s%s\n", user_storage_to_string(storage),
211                        storage == USER_LUKS ? " (strong encryption)" :
212                        storage == USER_FSCRYPT ? " (weak encryption)" :
213                        IN_SET(storage, USER_DIRECTORY, USER_SUBVOLUME) ? " (no encryption)" : "");
214 
215         ip = user_record_image_path(hr);
216         if (ip && !streq_ptr(ip, hd))
217                 printf("  Image Path: %s\n", ip);
218 
219         b = user_record_removable(hr);
220         if (b >= 0)
221                 printf("   Removable: %s\n", yes_no(b));
222 
223         shell = user_record_shell(hr);
224         if (shell)
225                 printf("       Shell: %s\n", shell);
226 
227         if (hr->email_address)
228                 printf("       Email: %s\n", hr->email_address);
229         if (hr->location)
230                 printf("    Location: %s\n", hr->location);
231         if (hr->password_hint)
232                 printf(" Passw. Hint: %s\n", hr->password_hint);
233         if (hr->icon_name)
234                 printf("   Icon Name: %s\n", hr->icon_name);
235 
236         if (hr->time_zone)
237                 printf("   Time Zone: %s\n", hr->time_zone);
238 
239         if (hr->preferred_language)
240                 printf("    Language: %s\n", hr->preferred_language);
241 
242         if (!strv_isempty(hr->environment))
243                 STRV_FOREACH(i, hr->environment) {
244                         printf(i == hr->environment ?
245                                " Environment: %s\n" :
246                                "              %s\n", *i);
247                 }
248 
249         if (hr->locked >= 0)
250                 printf("      Locked: %s\n", yes_no(hr->locked));
251 
252         if (hr->not_before_usec != UINT64_MAX)
253                 printf("  Not Before: %s\n", FORMAT_TIMESTAMP(hr->not_before_usec));
254 
255         if (hr->not_after_usec != UINT64_MAX)
256                 printf("   Not After: %s\n", FORMAT_TIMESTAMP(hr->not_after_usec));
257 
258         if (hr->umask != MODE_INVALID)
259                 printf("       UMask: 0%03o\n", hr->umask);
260 
261         if (nice_is_valid(hr->nice_level))
262                 printf("        Nice: %i\n", hr->nice_level);
263 
264         for (int j = 0; j < _RLIMIT_MAX; j++) {
265                 if (hr->rlimits[j])
266                         printf("       Limit: RLIMIT_%s=%" PRIu64 ":%" PRIu64 "\n",
267                                rlimit_to_string(j), (uint64_t) hr->rlimits[j]->rlim_cur, (uint64_t) hr->rlimits[j]->rlim_max);
268         }
269 
270         if (hr->tasks_max != UINT64_MAX)
271                 printf("   Tasks Max: %" PRIu64 "\n", hr->tasks_max);
272 
273         if (hr->memory_high != UINT64_MAX)
274                 printf(" Memory High: %s\n", FORMAT_BYTES(hr->memory_high));
275 
276         if (hr->memory_max != UINT64_MAX)
277                 printf("  Memory Max: %s\n", FORMAT_BYTES(hr->memory_max));
278 
279         if (hr->cpu_weight != UINT64_MAX)
280                 printf("  CPU Weight: %" PRIu64 "\n", hr->cpu_weight);
281 
282         if (hr->io_weight != UINT64_MAX)
283                 printf("   IO Weight: %" PRIu64 "\n", hr->io_weight);
284 
285         if (hr->access_mode != MODE_INVALID)
286                 printf(" Access Mode: 0%03o\n", user_record_access_mode(hr));
287 
288         if (storage == USER_LUKS) {
289                 printf("LUKS Discard: online=%s offline=%s\n", yes_no(user_record_luks_discard(hr)), yes_no(user_record_luks_offline_discard(hr)));
290 
291                 if (!sd_id128_is_null(hr->luks_uuid))
292                         printf("   LUKS UUID: " SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(hr->luks_uuid));
293                 if (!sd_id128_is_null(hr->partition_uuid))
294                         printf("   Part UUID: " SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(hr->partition_uuid));
295                 if (!sd_id128_is_null(hr->file_system_uuid))
296                         printf("     FS UUID: " SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(hr->file_system_uuid));
297 
298                 if (hr->file_system_type)
299                         printf(" File System: %s\n", user_record_file_system_type(hr));
300 
301                 if (hr->luks_extra_mount_options)
302                         printf("LUKS MntOpts: %s\n", hr->luks_extra_mount_options);
303 
304                 if (hr->luks_cipher)
305                         printf(" LUKS Cipher: %s\n", hr->luks_cipher);
306                 if (hr->luks_cipher_mode)
307                         printf(" Cipher Mode: %s\n", hr->luks_cipher_mode);
308                 if (hr->luks_volume_key_size != UINT64_MAX)
309                         printf("  Volume Key: %" PRIu64 "bit\n", hr->luks_volume_key_size * 8);
310 
311                 if (hr->luks_pbkdf_type)
312                         printf("  PBKDF Type: %s\n", hr->luks_pbkdf_type);
313                 if (hr->luks_pbkdf_hash_algorithm)
314                         printf("  PBKDF Hash: %s\n", hr->luks_pbkdf_hash_algorithm);
315                 if (hr->luks_pbkdf_time_cost_usec != UINT64_MAX)
316                         printf("  PBKDF Time: %s\n", FORMAT_TIMESPAN(hr->luks_pbkdf_time_cost_usec, 0));
317                 if (hr->luks_pbkdf_memory_cost != UINT64_MAX)
318                         printf(" PBKDF Bytes: %s\n", FORMAT_BYTES(hr->luks_pbkdf_memory_cost));
319 
320                 if (hr->luks_pbkdf_parallel_threads != UINT64_MAX)
321                         printf("PBKDF Thread: %" PRIu64 "\n", hr->luks_pbkdf_parallel_threads);
322 
323         } else if (storage == USER_CIFS) {
324 
325                 if (hr->cifs_service)
326                         printf("CIFS Service: %s\n", hr->cifs_service);
327 
328                 if (hr->cifs_extra_mount_options)
329                         printf("CIFS MntOpts: %s\n", hr->cifs_extra_mount_options);
330         }
331 
332         if (hr->cifs_user_name)
333                 printf("   CIFS User: %s\n", user_record_cifs_user_name(hr));
334         if (hr->cifs_domain)
335                 printf(" CIFS Domain: %s\n", hr->cifs_domain);
336 
337         if (storage != USER_CLASSIC)
338                 printf(" Mount Flags: %s %s %s\n",
339                        hr->nosuid ? "nosuid" : "suid",
340                        hr->nodev ? "nodev" : "dev",
341                        hr->noexec ? "noexec" : "exec");
342 
343         if (hr->skeleton_directory)
344                 printf("  Skel. Dir.: %s\n", user_record_skeleton_directory(hr));
345 
346         if (hr->disk_size != UINT64_MAX)
347                 printf("   Disk Size: %s\n", FORMAT_BYTES(hr->disk_size));
348 
349         if (hr->disk_usage != UINT64_MAX) {
350                 if (hr->disk_size != UINT64_MAX) {
351                         unsigned permille;
352 
353                         permille = (unsigned) DIV_ROUND_UP(hr->disk_usage * 1000U, hr->disk_size); /* Round up! */
354                         printf("  Disk Usage: %s (= %u.%01u%%)\n",
355                                FORMAT_BYTES(hr->disk_usage),
356                                permille / 10, permille % 10);
357                 } else
358                         printf("  Disk Usage: %s\n", FORMAT_BYTES(hr->disk_usage));
359         }
360 
361         if (hr->disk_free != UINT64_MAX) {
362                 if (hr->disk_size != UINT64_MAX) {
363                         const char *color_on, *color_off;
364                         unsigned permille;
365 
366                         permille = (unsigned) ((hr->disk_free * 1000U) / hr->disk_size); /* Round down! */
367 
368                         /* Color the output red or yellow if we are below 10% resp. 25% free. Because 10% and
369                          * 25% can be a lot of space still, let's additionally make some absolute
370                          * restrictions: 1G and 2G */
371                         if (permille <= 100U &&
372                             hr->disk_free < 1024U*1024U*1024U /* 1G */) {
373                                 color_on = ansi_highlight_red();
374                                 color_off = ansi_normal();
375                         } else if (permille <= 250U &&
376                                    hr->disk_free < 2U*1024U*1024U*1024U /* 2G */) {
377                                 color_on = ansi_highlight_yellow();
378                                 color_off = ansi_normal();
379                         } else
380                                 color_on = color_off = "";
381 
382                         printf("   Disk Free: %s%s (= %u.%01u%%)%s\n",
383                                color_on,
384                                FORMAT_BYTES(hr->disk_free),
385                                permille / 10, permille % 10,
386                                color_off);
387                 } else
388                         printf("   Disk Free: %s\n", FORMAT_BYTES(hr->disk_free));
389         }
390 
391         if (hr->disk_floor != UINT64_MAX)
392                 printf("  Disk Floor: %s\n", FORMAT_BYTES(hr->disk_floor));
393 
394         if (hr->disk_ceiling != UINT64_MAX)
395                 printf("Disk Ceiling: %s\n", FORMAT_BYTES(hr->disk_ceiling));
396 
397         if (hr->good_authentication_counter != UINT64_MAX)
398                 printf("  Good Auth.: %" PRIu64 "\n", hr->good_authentication_counter);
399 
400         if (hr->last_good_authentication_usec != UINT64_MAX)
401                 printf("   Last Good: %s\n", FORMAT_TIMESTAMP(hr->last_good_authentication_usec));
402 
403         if (hr->bad_authentication_counter != UINT64_MAX)
404                 printf("   Bad Auth.: %" PRIu64 "\n", hr->bad_authentication_counter);
405 
406         if (hr->last_bad_authentication_usec != UINT64_MAX)
407                 printf("    Last Bad: %s\n", FORMAT_TIMESTAMP(hr->last_bad_authentication_usec));
408 
409         t = user_record_ratelimit_next_try(hr);
410         if (t != USEC_INFINITY) {
411                 usec_t n = now(CLOCK_REALTIME);
412 
413                 if (t <= n)
414                         printf("    Next Try: anytime\n");
415                 else
416                         printf("    Next Try: %sin %s%s\n",
417                                ansi_highlight_red(),
418                                FORMAT_TIMESPAN(t - n, USEC_PER_SEC),
419                                ansi_normal());
420         }
421 
422         if (storage != USER_CLASSIC)
423                 printf(" Auth. Limit: %" PRIu64 " attempts per %s\n", user_record_ratelimit_burst(hr),
424                        FORMAT_TIMESPAN(user_record_ratelimit_interval_usec(hr), 0));
425 
426         if (hr->enforce_password_policy >= 0)
427                 printf(" Passwd Pol.: %s\n", yes_no(hr->enforce_password_policy));
428 
429         if (hr->password_change_min_usec != UINT64_MAX ||
430             hr->password_change_max_usec != UINT64_MAX ||
431             hr->password_change_warn_usec != UINT64_MAX ||
432             hr->password_change_inactive_usec != UINT64_MAX) {
433 
434                 printf(" Passwd Chg.:");
435 
436                 if (hr->password_change_min_usec != UINT64_MAX) {
437                         printf(" min %s", FORMAT_TIMESPAN(hr->password_change_min_usec, 0));
438 
439                         if (hr->password_change_max_usec != UINT64_MAX)
440                                 printf(" …");
441                 }
442 
443                 if (hr->password_change_max_usec != UINT64_MAX)
444                         printf(" max %s", FORMAT_TIMESPAN(hr->password_change_max_usec, 0));
445 
446                 if (hr->password_change_warn_usec != UINT64_MAX)
447                         printf("/warn %s", FORMAT_TIMESPAN(hr->password_change_warn_usec, 0));
448 
449                 if (hr->password_change_inactive_usec != UINT64_MAX)
450                         printf("/inactive %s", FORMAT_TIMESPAN(hr->password_change_inactive_usec, 0));
451 
452                 printf("\n");
453         }
454 
455         if (hr->password_change_now >= 0)
456                 printf("Pas. Ch. Now: %s\n", yes_no(hr->password_change_now));
457 
458         if (hr->drop_caches >= 0 || user_record_drop_caches(hr))
459                 printf(" Drop Caches: %s\n", yes_no(user_record_drop_caches(hr)));
460 
461         if (hr->auto_resize_mode >= 0)
462                 printf(" Auto Resize: %s\n", auto_resize_mode_to_string(user_record_auto_resize_mode(hr)));
463 
464         if (hr->rebalance_weight != REBALANCE_WEIGHT_UNSET) {
465                 uint64_t rb;
466 
467                 rb = user_record_rebalance_weight(hr);
468                 if (rb == REBALANCE_WEIGHT_OFF)
469                         printf("   Rebalance: off\n");
470                 else
471                         printf("   Rebalance: weight %" PRIu64 "\n", rb);
472         }
473 
474         if (!strv_isempty(hr->ssh_authorized_keys))
475                 printf("SSH Pub. Key: %zu\n", strv_length(hr->ssh_authorized_keys));
476 
477         if (!strv_isempty(hr->pkcs11_token_uri))
478                 STRV_FOREACH(i, hr->pkcs11_token_uri)
479                         printf(i == hr->pkcs11_token_uri ?
480                                "PKCS11 Token: %s\n" :
481                                "              %s\n", *i);
482 
483         if (hr->n_fido2_hmac_credential > 0)
484                 printf(" FIDO2 Token: %zu\n", hr->n_fido2_hmac_credential);
485 
486         if (!strv_isempty(hr->recovery_key_type))
487                 printf("Recovery Key: %zu\n", strv_length(hr->recovery_key_type));
488 
489         k = strv_length(hr->hashed_password);
490         if (k == 0)
491                 printf("   Passwords: %snone%s\n",
492                        user_record_disposition(hr) == USER_REGULAR ? ansi_highlight_yellow() : ansi_normal(), ansi_normal());
493         else
494                 printf("   Passwords: %zu\n", k);
495 
496         if (hr->signed_locally >= 0)
497                 printf("  Local Sig.: %s\n", yes_no(hr->signed_locally));
498 
499         if (hr->stop_delay_usec != UINT64_MAX)
500                 printf("  Stop Delay: %s\n", FORMAT_TIMESPAN(hr->stop_delay_usec, 0));
501 
502         if (hr->auto_login >= 0)
503                 printf("Autom. Login: %s\n", yes_no(hr->auto_login));
504 
505         if (hr->kill_processes >= 0)
506                 printf("  Kill Proc.: %s\n", yes_no(hr->kill_processes));
507 
508         if (hr->service)
509                 printf("     Service: %s\n", hr->service);
510 }
511 
group_record_show(GroupRecord * gr,bool show_full_user_info)512 void group_record_show(GroupRecord *gr, bool show_full_user_info) {
513         int r;
514 
515         printf("  Group name: %s\n",
516                group_record_group_name_and_realm(gr));
517 
518         printf(" Disposition: %s\n", user_disposition_to_string(group_record_disposition(gr)));
519 
520         if (gr->last_change_usec != USEC_INFINITY)
521                 printf(" Last Change: %s\n", FORMAT_TIMESTAMP(gr->last_change_usec));
522 
523         if (gid_is_valid(gr->gid))
524                 printf("         GID: " GID_FMT "\n", gr->gid);
525 
526         if (show_full_user_info) {
527                 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
528 
529                 r = membershipdb_by_group(gr->group_name, 0, &iterator);
530                 if (r < 0) {
531                         errno = -r;
532                         printf("     Members: (can't acquire: %m)");
533                 } else {
534                         const char *prefix = "     Members:";
535 
536                         for (;;) {
537                                 _cleanup_free_ char *user = NULL;
538 
539                                 r = membershipdb_iterator_get(iterator, &user, NULL);
540                                 if (r == -ESRCH)
541                                         break;
542                                 if (r < 0) {
543                                         errno = -r;
544                                         printf("%s (can't iterate: %m\n", prefix);
545                                         break;
546                                 }
547 
548                                 printf("%s %s\n", prefix, user);
549                                 prefix = "             ";
550                         }
551                 }
552         } else {
553                 const char *prefix = "     Members:";
554 
555                 STRV_FOREACH(i, gr->members) {
556                         printf("%s %s\n", prefix, *i);
557                         prefix = "             ";
558                 }
559         }
560 
561         if (!strv_isempty(gr->administrators)) {
562                 const char *prefix = "      Admins:";
563 
564                 STRV_FOREACH(i, gr->administrators) {
565                         printf("%s %s\n", prefix, *i);
566                         prefix = "             ";
567                 }
568         }
569 
570         if (gr->description && !streq(gr->description, gr->group_name))
571                 printf(" Description: %s\n", gr->description);
572 
573         if (!strv_isempty(gr->hashed_password))
574                 printf("   Passwords: %zu\n", strv_length(gr->hashed_password));
575 
576         if (gr->service)
577                 printf("     Service: %s\n", gr->service);
578 }
579