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