1/* GNU's pinky. 2 Copyright (C) 1992-1997, 1999-2006, 2008-2010 Free Software Foundation, Inc. 3 4 This program is free software: you can redistribute it and/or modify 5 it under the terms of the GNU General Public License as published by 6 the Free Software Foundation, either version 3 of the License, or 7 (at your option) any later version. 8 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 GNU General Public License for more details. 13 14 You should have received a copy of the GNU General Public License 15 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 16 17/* Created by hacking who.c by Kaveh Ghazi ghazi@caip.rutgers.edu */ 18 19#include <config.h> 20#include <getopt.h> 21#include <pwd.h> 22#include <stdio.h> 23 24#include <sys/types.h> 25#include "system.h" 26 27#include "canon-host.h" 28#include "error.h" 29#include "hard-locale.h" 30#include "readutmp.h" 31 32/* The official name of this program (e.g., no `g' prefix). */ 33#define PROGRAM_NAME "pinky" 34 35#define AUTHORS \ 36 proper_name ("Joseph Arceneaux"), \ 37 proper_name ("David MacKenzie"), \ 38 proper_name ("Kaveh Ghazi") 39 40#ifndef MAXHOSTNAMELEN 41# define MAXHOSTNAMELEN 64 42#endif 43 44char *ttyname (int); 45 46/* If true, display the hours:minutes since each user has touched 47 the keyboard, or blank if within the last minute, or days followed 48 by a 'd' if not within the last day. */ 49static bool include_idle = true; 50 51/* If true, display a line at the top describing each field. */ 52static bool include_heading = true; 53 54/* if true, display the user's full name from pw_gecos. */ 55static bool include_fullname = true; 56 57/* if true, display the user's ~/.project file when doing long format. */ 58static bool include_project = true; 59 60/* if true, display the user's ~/.plan file when doing long format. */ 61static bool include_plan = true; 62 63/* if true, display the user's home directory and shell 64 when doing long format. */ 65static bool include_home_and_shell = true; 66 67/* if true, use the "short" output format. */ 68static bool do_short_format = true; 69 70/* if true, display the ut_host field. */ 71#ifdef HAVE_UT_HOST 72static bool include_where = true; 73#endif 74 75/* The strftime format to use for login times, and its expected 76 output width. */ 77static char const *time_format; 78static int time_format_width; 79 80static struct option const longopts[] = 81{ 82 {GETOPT_HELP_OPTION_DECL}, 83 {GETOPT_VERSION_OPTION_DECL}, 84 {NULL, 0, NULL, 0} 85}; 86 87/* Count and return the number of ampersands in STR. */ 88 89static size_t 90count_ampersands (const char *str) 91{ 92 size_t count = 0; 93 do 94 { 95 if (*str == '&') 96 count++; 97 } while (*str++); 98 return count; 99} 100 101/* Create a string (via xmalloc) which contains a full name by substituting 102 for each ampersand in GECOS_NAME the USER_NAME string with its first 103 character capitalized. The caller must ensure that GECOS_NAME contains 104 no `,'s. The caller also is responsible for free'ing the return value of 105 this function. */ 106 107static char * 108create_fullname (const char *gecos_name, const char *user_name) 109{ 110 size_t rsize = strlen (gecos_name) + 1; 111 char *result; 112 char *r; 113 size_t ampersands = count_ampersands (gecos_name); 114 115 if (ampersands != 0) 116 { 117 size_t ulen = strlen (user_name); 118 size_t product = ampersands * ulen; 119 rsize += product - ampersands; 120 if (xalloc_oversized (ulen, ampersands) || rsize < product) 121 xalloc_die (); 122 } 123 124 r = result = xmalloc (rsize); 125 126 while (*gecos_name) 127 { 128 if (*gecos_name == '&') 129 { 130 const char *uname = user_name; 131 if (islower (to_uchar (*uname))) 132 *r++ = toupper (to_uchar (*uname++)); 133 while (*uname) 134 *r++ = *uname++; 135 } 136 else 137 { 138 *r++ = *gecos_name; 139 } 140 141 gecos_name++; 142 } 143 *r = 0; 144 145 return result; 146} 147 148/* Return a string representing the time between WHEN and the time 149 that this function is first run. */ 150 151static const char * 152idle_string (time_t when) 153{ 154 static time_t now = 0; 155 static char buf[INT_STRLEN_BOUND (long int) + 2]; 156 time_t seconds_idle; 157 158 if (now == 0) 159 time (&now); 160 161 seconds_idle = now - when; 162 if (seconds_idle < 60) /* One minute. */ 163 return " "; 164 if (seconds_idle < (24 * 60 * 60)) /* One day. */ 165 { 166 int hours = seconds_idle / (60 * 60); 167 int minutes = (seconds_idle % (60 * 60)) / 60; 168 sprintf (buf, "%02d:%02d", hours, minutes); 169 } 170 else 171 { 172 unsigned long int days = seconds_idle / (24 * 60 * 60); 173 sprintf (buf, "%lud", days); 174 } 175 return buf; 176} 177 178/* Return a time string. */ 179static const char * 180time_string (const STRUCT_UTMP *utmp_ent) 181{ 182 static char buf[INT_STRLEN_BOUND (intmax_t) + sizeof "-%m-%d %H:%M"]; 183 184 /* Don't take the address of UT_TIME_MEMBER directly. 185 Ulrich Drepper wrote: 186 ``... GNU libc (and perhaps other libcs as well) have extended 187 utmp file formats which do not use a simple time_t ut_time field. 188 In glibc, ut_time is a macro which selects for backward compatibility 189 the tv_sec member of a struct timeval value.'' */ 190 time_t t = UT_TIME_MEMBER (utmp_ent); 191 struct tm *tmp = localtime (&t); 192 193 if (tmp) 194 { 195 strftime (buf, sizeof buf, time_format, tmp); 196 return buf; 197 } 198 else 199 return timetostr (t, buf); 200} 201 202/* Display a line of information about UTMP_ENT. */ 203 204static void 205print_entry (const STRUCT_UTMP *utmp_ent) 206{ 207 struct stat stats; 208 time_t last_change; 209 char mesg; 210 211#define DEV_DIR_WITH_TRAILING_SLASH "/dev/" 212#define DEV_DIR_LEN (sizeof (DEV_DIR_WITH_TRAILING_SLASH) - 1) 213 214 char line[sizeof (utmp_ent->ut_line) + DEV_DIR_LEN + 1]; 215 216 /* Copy ut_line into LINE, prepending `/dev/' if ut_line is not 217 already an absolute file name. Some system may put the full, 218 absolute file name in ut_line. */ 219 if (utmp_ent->ut_line[0] == '/') 220 { 221 strncpy (line, utmp_ent->ut_line, sizeof (utmp_ent->ut_line)); 222 line[sizeof (utmp_ent->ut_line)] = '\0'; 223 } 224 else 225 { 226 strcpy (line, DEV_DIR_WITH_TRAILING_SLASH); 227 strncpy (line + DEV_DIR_LEN, utmp_ent->ut_line, sizeof (utmp_ent->ut_line)); 228 line[DEV_DIR_LEN + sizeof (utmp_ent->ut_line)] = '\0'; 229 } 230 231 if (stat (line, &stats) == 0) 232 { 233 mesg = (stats.st_mode & S_IWGRP) ? ' ' : '*'; 234 last_change = stats.st_atime; 235 } 236 else 237 { 238 mesg = '?'; 239 last_change = 0; 240 } 241 242 printf ("%-8.*s", UT_USER_SIZE, UT_USER (utmp_ent)); 243 244 if (include_fullname) 245 { 246 struct passwd *pw; 247 char name[UT_USER_SIZE + 1]; 248 249 strncpy (name, UT_USER (utmp_ent), UT_USER_SIZE); 250 name[UT_USER_SIZE] = '\0'; 251 pw = getpwnam (name); 252 if (pw == NULL) 253 /* TRANSLATORS: Real name is unknown; at most 19 characters. */ 254 printf (" %19s", _(" ???")); 255 else 256 { 257 char *const comma = strchr (pw->pw_gecos, ','); 258 char *result; 259 260 if (comma) 261 *comma = '\0'; 262 263 result = create_fullname (pw->pw_gecos, pw->pw_name); 264 printf (" %-19.19s", result); 265 free (result); 266 } 267 } 268 269 printf (" %c%-8.*s", 270 mesg, (int) sizeof (utmp_ent->ut_line), utmp_ent->ut_line); 271 272 if (include_idle) 273 { 274 if (last_change) 275 printf (" %-6s", idle_string (last_change)); 276 else 277 /* TRANSLATORS: Idle time is unknown; at most 5 characters. */ 278 printf (" %-6s", _("?????")); 279 } 280 281 printf (" %s", time_string (utmp_ent)); 282 283#ifdef HAVE_UT_HOST 284 if (include_where && utmp_ent->ut_host[0]) 285 { 286 char ut_host[sizeof (utmp_ent->ut_host) + 1]; 287 char *host = NULL; 288 char *display = NULL; 289 290 /* Copy the host name into UT_HOST, and ensure it's nul terminated. */ 291 strncpy (ut_host, utmp_ent->ut_host, (int) sizeof (utmp_ent->ut_host)); 292 ut_host[sizeof (utmp_ent->ut_host)] = '\0'; 293 294 /* Look for an X display. */ 295 display = strchr (ut_host, ':'); 296 if (display) 297 *display++ = '\0'; 298 299 if (*ut_host) 300 /* See if we can canonicalize it. */ 301 host = canon_host (ut_host); 302 if ( ! host) 303 host = ut_host; 304 305 if (display) 306 printf (" %s:%s", host, display); 307 else 308 printf (" %s", host); 309 310 if (host != ut_host) 311 free (host); 312 } 313#endif 314 315 putchar ('\n'); 316} 317 318/* Display a verbose line of information about UTMP_ENT. */ 319 320static void 321print_long_entry (const char name[]) 322{ 323 struct passwd *pw; 324 325 pw = getpwnam (name); 326 327 printf (_("Login name: ")); 328 printf ("%-28s", name); 329 330 printf (_("In real life: ")); 331 if (pw == NULL) 332 { 333 /* TRANSLATORS: Real name is unknown; no hard limit. */ 334 printf (" %s", _("???\n")); 335 return; 336 } 337 else 338 { 339 char *const comma = strchr (pw->pw_gecos, ','); 340 char *result; 341 342 if (comma) 343 *comma = '\0'; 344 345 result = create_fullname (pw->pw_gecos, pw->pw_name); 346 printf (" %s", result); 347 free (result); 348 } 349 350 putchar ('\n'); 351 352 if (include_home_and_shell) 353 { 354 printf (_("Directory: ")); 355 printf ("%-29s", pw->pw_dir); 356 printf (_("Shell: ")); 357 printf (" %s", pw->pw_shell); 358 putchar ('\n'); 359 } 360 361 if (include_project) 362 { 363 FILE *stream; 364 char buf[1024]; 365 const char *const baseproject = "/.project"; 366 char *const project = 367 xmalloc (strlen (pw->pw_dir) + strlen (baseproject) + 1); 368 stpcpy (stpcpy (project, pw->pw_dir), baseproject); 369 370 stream = fopen (project, "r"); 371 if (stream) 372 { 373 size_t bytes; 374 375 printf (_("Project: ")); 376 377 while ((bytes = fread (buf, 1, sizeof (buf), stream)) > 0) 378 fwrite (buf, 1, bytes, stdout); 379 fclose (stream); 380 } 381 382 free (project); 383 } 384 385 if (include_plan) 386 { 387 FILE *stream; 388 char buf[1024]; 389 const char *const baseplan = "/.plan"; 390 char *const plan = 391 xmalloc (strlen (pw->pw_dir) + strlen (baseplan) + 1); 392 stpcpy (stpcpy (plan, pw->pw_dir), baseplan); 393 394 stream = fopen (plan, "r"); 395 if (stream) 396 { 397 size_t bytes; 398 399 printf (_("Plan:\n")); 400 401 while ((bytes = fread (buf, 1, sizeof (buf), stream)) > 0) 402 fwrite (buf, 1, bytes, stdout); 403 fclose (stream); 404 } 405 406 free (plan); 407 } 408 409 putchar ('\n'); 410} 411 412/* Print the username of each valid entry and the number of valid entries 413 in UTMP_BUF, which should have N elements. */ 414 415static void 416print_heading (void) 417{ 418 printf ("%-8s", _("Login")); 419 if (include_fullname) 420 printf (" %-19s", _("Name")); 421 printf (" %-9s", _(" TTY")); 422 if (include_idle) 423 printf (" %-6s", _("Idle")); 424 printf (" %-*s", time_format_width, _("When")); 425#ifdef HAVE_UT_HOST 426 if (include_where) 427 printf (" %s", _("Where")); 428#endif 429 putchar ('\n'); 430} 431 432/* Display UTMP_BUF, which should have N entries. */ 433 434static void 435scan_entries (size_t n, const STRUCT_UTMP *utmp_buf, 436 const int argc_names, char *const argv_names[]) 437{ 438 if (hard_locale (LC_TIME)) 439 { 440 time_format = "%Y-%m-%d %H:%M"; 441 time_format_width = 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2; 442 } 443 else 444 { 445 time_format = "%b %e %H:%M"; 446 time_format_width = 3 + 1 + 2 + 1 + 2 + 1 + 2; 447 } 448 449 if (include_heading) 450 print_heading (); 451 452 while (n--) 453 { 454 if (IS_USER_PROCESS (utmp_buf)) 455 { 456 if (argc_names) 457 { 458 int i; 459 460 for (i = 0; i < argc_names; i++) 461 if (strncmp (UT_USER (utmp_buf), argv_names[i], UT_USER_SIZE) 462 == 0) 463 { 464 print_entry (utmp_buf); 465 break; 466 } 467 } 468 else 469 print_entry (utmp_buf); 470 } 471 utmp_buf++; 472 } 473} 474 475/* Display a list of who is on the system, according to utmp file FILENAME. */ 476 477static void 478short_pinky (const char *filename, 479 const int argc_names, char *const argv_names[]) 480{ 481 size_t n_users; 482 STRUCT_UTMP *utmp_buf; 483 484 if (read_utmp (filename, &n_users, &utmp_buf, 0) != 0) 485 error (EXIT_FAILURE, errno, "%s", filename); 486 487 scan_entries (n_users, utmp_buf, argc_names, argv_names); 488} 489 490static void 491long_pinky (const int argc_names, char *const argv_names[]) 492{ 493 int i; 494 495 for (i = 0; i < argc_names; i++) 496 print_long_entry (argv_names[i]); 497} 498 499void 500usage (int status) 501{ 502 if (status != EXIT_SUCCESS) 503 fprintf (stderr, _("Try `%s --help' for more information.\n"), 504 program_name); 505 else 506 { 507 printf (_("Usage: %s [OPTION]... [USER]...\n"), program_name); 508 fputs (_("\ 509\n\ 510 -l produce long format output for the specified USERs\n\ 511 -b omit the user's home directory and shell in long format\n\ 512 -h omit the user's project file in long format\n\ 513 -p omit the user's plan file in long format\n\ 514 -s do short format output, this is the default\n\ 515"), stdout); 516 fputs (_("\ 517 -f omit the line of column headings in short format\n\ 518 -w omit the user's full name in short format\n\ 519 -i omit the user's full name and remote host in short format\n\ 520 -q omit the user's full name, remote host and idle time\n\ 521 in short format\n\ 522"), stdout); 523 fputs (HELP_OPTION_DESCRIPTION, stdout); 524 fputs (VERSION_OPTION_DESCRIPTION, stdout); 525 printf (_("\ 526\n\ 527A lightweight `finger' program; print user information.\n\ 528The utmp file will be %s.\n\ 529"), UTMP_FILE); 530 emit_ancillary_info (); 531 } 532 exit (status); 533} 534 535int 536main (int argc, char **argv) 537{ 538 int optc; 539 int n_users; 540 541 initialize_main (&argc, &argv); 542 set_program_name (argv[0]); 543 setlocale (LC_ALL, ""); 544 bindtextdomain (PACKAGE, LOCALEDIR); 545 textdomain (PACKAGE); 546 547 atexit (close_stdout); 548 549 while ((optc = getopt_long (argc, argv, "sfwiqbhlp", longopts, NULL)) != -1) 550 { 551 switch (optc) 552 { 553 case 's': 554 do_short_format = true; 555 break; 556 557 case 'l': 558 do_short_format = false; 559 break; 560 561 case 'f': 562 include_heading = false; 563 break; 564 565 case 'w': 566 include_fullname = false; 567 break; 568 569 case 'i': 570 include_fullname = false; 571#ifdef HAVE_UT_HOST 572 include_where = false; 573#endif 574 break; 575 576 case 'q': 577 include_fullname = false; 578#ifdef HAVE_UT_HOST 579 include_where = false; 580#endif 581 include_idle = false; 582 break; 583 584 case 'h': 585 include_project = false; 586 break; 587 588 case 'p': 589 include_plan = false; 590 break; 591 592 case 'b': 593 include_home_and_shell = false; 594 break; 595 596 case_GETOPT_HELP_CHAR; 597 598 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); 599 600 default: 601 usage (EXIT_FAILURE); 602 } 603 } 604 605 n_users = argc - optind; 606 607 if (!do_short_format && n_users == 0) 608 { 609 error (0, 0, _("no username specified; at least one must be\ 610 specified when using -l")); 611 usage (EXIT_FAILURE); 612 } 613 614 if (do_short_format) 615 short_pinky (UTMP_FILE, n_users, argv + optind); 616 else 617 long_pinky (n_users, argv + optind); 618 619 exit (EXIT_SUCCESS); 620} 621