Login Accounting getlogin() and Finding the Login Name

 

Chapter 40: Login Accounting
Part 5 โ€“ getlogin() and Finding the Login Name
๐Ÿ‘ค Login Identity
๐Ÿ“ Uses utmp File
๐Ÿ” Secure vs LOGNAME

Who Actually Logged In?

Imagine a user logs in as ravi, then uses su to switch to root. Now, your program is running as root (UID 0). If you call getuid() and look it up in /etc/passwd, you’ll get “root” โ€” but the actual person sitting at the keyboard is ravi.

The getlogin() function solves this. It checks the utmp file to find who originally logged in on this terminal โ€” not who the process is running as.

Key Terms in This Section

getlogin() getlogin_r() ttyname() LOGNAME env var controlling terminal ENOTTY getpwuid()

Function Signature
#include <unistd.h>

char *getlogin(void);
/* Returns: pointer to login name string on success,
 *          NULL on error (errno set) */

/* Reentrant version (SUSv3 specified) */
int getlogin_r(char *buf, size_t bufsize);
/* Returns: 0 on success, positive error number on error */

The non-reentrant version returns a pointer to a static internal buffer. The reentrant version writes into your provided buf of bufsize bytes โ€” use this in multi-threaded programs.

How getlogin() Works Internally

The function performs these steps:

Step 1
Call ttyname(STDIN_FILENO) to get the name of the terminal associated with standard input (e.g., /dev/pts/0)
โ†“
Step 2
Strip the /dev/ prefix from the terminal name to get just pts/0 (the ut_line value)
โ†“
Step 3
Scan the utmp file for a record where ut_line matches this terminal name
โ†“
Step 4
Return the ut_user field from the matching record โ€” this is the original login name

When getlogin() Can Fail
Situation errno Why
Process is a daemon (no terminal) ENOTTY ttyname() fails because stdin is not a terminal
Terminal emulator doesn’t update utmp varies Some terminal emulators (xterm with certain options) skip writing utmp records
Running in a container/VM varies Containers may not maintain utmp properly
utmp file is corrupted or deleted ENOENT File not found when trying to open it

getlogin() vs getpwuid() vs LOGNAME โ€” Which to Use?
Method Returns Problem Secure?
getlogin() Original login name from utmp Fails for daemons; may fail if utmp not updated โœ“ Yes
getpwuid(getuid()) First name in /etc/passwd for this UID Multiple login names with same UID โ†’ returns first one only. After su, returns “root”, not the original user. โš  Partial
getenv(“LOGNAME”) Login name from environment variable User can change this variable: export LOGNAME=root. Never trust it for security decisions. โœ— No

Best practice: Use getlogin() when you need to identify who logged in. Fall back to getpwuid(getuid()) if getlogin() fails. Never rely on LOGNAME for security.

Coding Example 1: Basic getlogin() Usage

Shows getlogin() in action, with fallback to getpwuid().

/* show_login.c
 * Demonstrates getlogin() and compares with getpwuid()
 * Compile: gcc show_login.c -o show_login
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>    /* getlogin() */
#include <pwd.h>       /* getpwuid() */
#include <errno.h>
#include <string.h>

int main(void)
{
    char *login_name;
    struct passwd *pw;
    uid_t uid;

    /* --- Method 1: getlogin() --- */
    login_name = getlogin();
    if (login_name != NULL) {
        printf("getlogin()         : %s\n", login_name);
    } else {
        printf("getlogin()         : FAILED (%s)\n", strerror(errno));
        printf("  This can happen if:\n");
        printf("  - Process has no controlling terminal (daemon)\n");
        printf("  - Terminal emulator did not update utmp\n");
    }

    /* --- Method 2: getpwuid(getuid()) --- */
    uid = getuid();
    pw = getpwuid(uid);
    if (pw != NULL) {
        printf("getpwuid(getuid()) : %s  (UID=%d)\n", pw->pw_name, uid);
    } else {
        printf("getpwuid(getuid()) : FAILED\n");
    }

    /* --- Method 3: LOGNAME env var (NOT secure) --- */
    char *logname_env = getenv("LOGNAME");
    printf("LOGNAME env var    : %s  (WARNING: user can fake this!)\n",
           logname_env ? logname_env : "(not set)");

    /* Practical: use getlogin with fallback */
    printf("\n--- Recommended approach ---\n");
    login_name = getlogin();
    if (login_name == NULL) {
        /* Fallback to passwd entry */
        pw = getpwuid(getuid());
        login_name = (pw != NULL) ? pw->pw_name : "unknown";
        printf("Identified user (fallback): %s\n", login_name);
    } else {
        printf("Identified user (getlogin): %s\n", login_name);
    }

    return 0;
}

Coding Example 2: Thread-Safe Version with getlogin_r()

In multi-threaded programs, use getlogin_r() to avoid the shared static buffer problem.

/* getlogin_threadsafe.c
 * Thread-safe login name retrieval using getlogin_r()
 * Compile: gcc getlogin_threadsafe.c -o getlogin_threadsafe -lpthread
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>

#define LOGIN_BUF_SIZE 256

void *worker_thread(void *arg)
{
    int thread_id = *(int *)arg;
    char login_buf[LOGIN_BUF_SIZE];  /* Each thread has its own buffer */
    int ret;

    /* getlogin_r() is the thread-safe version */
    ret = getlogin_r(login_buf, sizeof(login_buf));
    if (ret == 0) {
        printf("Thread %d: login name = %s\n", thread_id, login_buf);
    } else {
        printf("Thread %d: getlogin_r() failed: %s\n",
               thread_id, strerror(ret));
    }

    return NULL;
}

int main(void)
{
    pthread_t tid[3];
    int ids[3] = {1, 2, 3};
    int i;

    printf("Spawning 3 threads to call getlogin_r() safely...\n\n");

    for (i = 0; i < 3; i++)
        pthread_create(&tid[i], NULL, worker_thread, &ids[i]);

    for (i = 0; i < 3; i++)
        pthread_join(tid[i], NULL);

    printf("\nAll threads used separate buffers โ€” no race conditions.\n");
    return 0;
}

Coding Example 3: Implementing getlogin() from Scratch

This exercise from the TLPI book (Exercise 40-1) shows exactly how getlogin() works by implementing it using ttyname() and utmpx functions.

/* my_getlogin.c
 * Manual implementation of getlogin() using utmpx
 * This is Exercise 40-1 from TLPI
 * Compile: gcc my_getlogin.c -o my_getlogin
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <utmpx.h>
#include <errno.h>

static char login_buf[32];  /* Static buffer for return value */

/*
 * my_getlogin() - reimplementation of getlogin()
 *
 * How it works:
 * 1. Find the terminal name for stdin using ttyname()
 * 2. Strip the "/dev/" prefix to get ut_line format
 * 3. Scan utmp for a USER_PROCESS or LOGIN_PROCESS record
 *    with matching ut_line
 * 4. Return ut_user from that record
 */
char *my_getlogin(void)
{
    struct utmpx search_ut, *result;
    char *tty;

    /* Step 1: Get the terminal device name */
    tty = ttyname(STDIN_FILENO);
    if (tty == NULL) {
        errno = ENOTTY;  /* stdin is not a terminal */
        return NULL;
    }

    /* Step 2: Strip "/dev/" prefix (5 characters) */
    if (strncmp(tty, "/dev/", 5) == 0)
        tty += 5;

    /* Step 3: Set up search criteria */
    memset(&search_ut, 0, sizeof(struct utmpx));
    strncpy(search_ut.ut_line, tty, sizeof(search_ut.ut_line) - 1);
    search_ut.ut_type = USER_PROCESS;

    setutxent();
    result = getutxline(&search_ut);
    endutxent();

    if (result == NULL) {
        errno = ENOENT;  /* No matching record in utmp */
        return NULL;
    }

    /* Step 4: Copy username to static buffer */
    strncpy(login_buf, result->ut_user, sizeof(login_buf) - 1);
    login_buf[sizeof(login_buf) - 1] = '\0';

    return login_buf;
}

int main(void)
{
    char *name;

    /* Test our implementation */
    name = my_getlogin();
    if (name != NULL) {
        printf("my_getlogin()  : %s\n", name);
    } else {
        perror("my_getlogin()");
    }

    /* Compare with system getlogin() */
    name = getlogin();
    if (name != NULL) {
        printf("system getlogin: %s\n", name);
    } else {
        perror("system getlogin");
    }

    return 0;
}

Interview Questions
Q1. How does getlogin() find the login name?

Answer: It calls ttyname() to find the terminal associated with stdin, strips the “/dev/” prefix, then searches the utmp file for a USER_PROCESS or LOGIN_PROCESS record whose ut_line matches. If found, it returns the ut_user field.

Q2. Why is getlogin() better than getpwuid(getuid()) for identifying the logged-in user?

Answer: After su or setuid(), getuid() returns the new UID (e.g., 0 for root), so getpwuid(getuid()) returns “root”, not the original user. getlogin() reads from utmp which records who originally logged in on this terminal, regardless of UID changes. Also, if multiple login names share the same UID, getpwuid() always returns the first one in /etc/passwd, while getlogin() returns the actual name used to log in.

Q3. Can you trust the LOGNAME environment variable for security decisions?

Answer: No. Any user can set LOGNAME to anything with export LOGNAME=root. It’s convenient for informational purposes but must never be used for authentication or authorization decisions. Use getlogin() or UID-based methods for secure identity checks.

Q4. When would getlogin() fail for a daemon process?

Answer: Daemon processes detach from their controlling terminal (usually by calling setsid()). When getlogin() internally calls ttyname(STDIN_FILENO), it fails with ENOTTY because stdin is typically redirected to /dev/null in daemons โ€” not a real terminal.

Q5. What is the difference between getlogin() and getlogin_r()?

Answer: getlogin() returns a pointer to an internal static buffer shared across the process. getlogin_r() writes the login name into a caller-provided buffer, making it reentrant and safe for use in multi-threaded programs. getlogin_r() returns 0 on success or an error number on failure (not -1 with errno).

Leave a Reply

Your email address will not be published. Required fields are marked *