client.c revision 1b5d61b8
135c4bbdfSmrg/*
235c4bbdfSmrg * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All
335c4bbdfSmrg * rights reserved.
435c4bbdfSmrg * Copyright (c) 1993, 2010, Oracle and/or its affiliates. All rights reserved.
535c4bbdfSmrg *
635c4bbdfSmrg * Permission is hereby granted, free of charge, to any person obtaining a copy
735c4bbdfSmrg * of this software and associated documentation files (the "Software"), to deal
835c4bbdfSmrg * in the Software without restriction, including without limitation the rights
935c4bbdfSmrg * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1035c4bbdfSmrg * copies of the Software, and to permit persons to whom the Software is
1135c4bbdfSmrg * furnished to do so, subject to the following conditions:
1235c4bbdfSmrg *
1335c4bbdfSmrg * The above copyright notice and this permission notice shall be included in
1435c4bbdfSmrg * all copies or substantial portions of the Software.
1535c4bbdfSmrg *
1635c4bbdfSmrg * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1735c4bbdfSmrg * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1835c4bbdfSmrg * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1935c4bbdfSmrg * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2035c4bbdfSmrg * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2135c4bbdfSmrg * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2235c4bbdfSmrg * THE SOFTWARE.
2335c4bbdfSmrg */
2435c4bbdfSmrg
2535c4bbdfSmrg/**
2635c4bbdfSmrg * @file
2735c4bbdfSmrg *
2835c4bbdfSmrg * This file contains functionality for identifying clients by various
2935c4bbdfSmrg * means. The primary purpose of identification is to simply aid in
3035c4bbdfSmrg * finding out which clients are using X server and how they are using
3135c4bbdfSmrg * it. For example, it's often necessary to monitor what requests
3235c4bbdfSmrg * clients are executing (to spot bad behaviour) and how they are
3335c4bbdfSmrg * allocating resources in X server (to spot excessive resource
3435c4bbdfSmrg * usage).
3535c4bbdfSmrg *
3635c4bbdfSmrg * This framework automatically allocates information, that can be
3735c4bbdfSmrg * used for client identification, when a client connects to the
3835c4bbdfSmrg * server. The information is freed when the client disconnects. The
3935c4bbdfSmrg * allocated information is just a collection of various IDs, such as
4035c4bbdfSmrg * PID and process name for local clients, that are likely to be
4135c4bbdfSmrg * useful in analyzing X server usage.
4235c4bbdfSmrg *
4335c4bbdfSmrg * Users of the framework can query ID information about clients at
4435c4bbdfSmrg * any time. To avoid repeated polling of IDs the users can also
4535c4bbdfSmrg * subscribe for notifications about the availability of ID
4635c4bbdfSmrg * information. IDs have been allocated before ClientStateCallback is
4735c4bbdfSmrg * called with ClientStateInitial state. Similarly the IDs will be
4835c4bbdfSmrg * released after ClientStateCallback is called with ClientStateGone
4935c4bbdfSmrg * state.
5035c4bbdfSmrg *
5135c4bbdfSmrg * Author: Rami Ylimäki <rami.ylimaki@vincit.fi>
5235c4bbdfSmrg */
5335c4bbdfSmrg
5435c4bbdfSmrg#include <sys/stat.h>
5535c4bbdfSmrg#include <fcntl.h>
5635c4bbdfSmrg#include <unistd.h>
5735c4bbdfSmrg
5835c4bbdfSmrg#include "client.h"
5935c4bbdfSmrg#include "os.h"
6035c4bbdfSmrg#include "dixstruct.h"
6135c4bbdfSmrg
6235c4bbdfSmrg#ifdef __sun
6335c4bbdfSmrg#include <errno.h>
6435c4bbdfSmrg#include <procfs.h>
6535c4bbdfSmrg#endif
6635c4bbdfSmrg
6735c4bbdfSmrg#ifdef __OpenBSD__
6835c4bbdfSmrg#include <sys/param.h>
6935c4bbdfSmrg#include <sys/sysctl.h>
7035c4bbdfSmrg#include <sys/types.h>
7135c4bbdfSmrg
7235c4bbdfSmrg#include <kvm.h>
7335c4bbdfSmrg#include <limits.h>
7435c4bbdfSmrg#endif
7535c4bbdfSmrg
7635c4bbdfSmrg/**
7735c4bbdfSmrg * Try to determine a PID for a client from its connection
7835c4bbdfSmrg * information. This should be called only once when new client has
7935c4bbdfSmrg * connected, use GetClientPid to determine the PID at other times.
8035c4bbdfSmrg *
8135c4bbdfSmrg * @param[in] client Connection linked to some process.
8235c4bbdfSmrg *
8335c4bbdfSmrg * @return PID of the client. Error (-1) if PID can't be determined
8435c4bbdfSmrg *         for the client.
8535c4bbdfSmrg *
8635c4bbdfSmrg * @see GetClientPid
8735c4bbdfSmrg */
8835c4bbdfSmrgpid_t
8935c4bbdfSmrgDetermineClientPid(struct _Client * client)
9035c4bbdfSmrg{
9135c4bbdfSmrg    LocalClientCredRec *lcc = NULL;
9235c4bbdfSmrg    pid_t pid = -1;
9335c4bbdfSmrg
9435c4bbdfSmrg    if (client == NullClient)
9535c4bbdfSmrg        return pid;
9635c4bbdfSmrg
9735c4bbdfSmrg    if (client == serverClient)
9835c4bbdfSmrg        return getpid();
9935c4bbdfSmrg
10035c4bbdfSmrg    if (GetLocalClientCreds(client, &lcc) != -1) {
10135c4bbdfSmrg        if (lcc->fieldsSet & LCC_PID_SET)
10235c4bbdfSmrg            pid = lcc->pid;
10335c4bbdfSmrg        FreeLocalClientCreds(lcc);
10435c4bbdfSmrg    }
10535c4bbdfSmrg
10635c4bbdfSmrg    return pid;
10735c4bbdfSmrg}
10835c4bbdfSmrg
10935c4bbdfSmrg/**
11035c4bbdfSmrg * Try to determine a command line string for a client based on its
11135c4bbdfSmrg * PID. Note that mapping PID to a command hasn't been implemented for
11235c4bbdfSmrg * some operating systems. This should be called only once when a new
11335c4bbdfSmrg * client has connected, use GetClientCmdName/Args to determine the
11435c4bbdfSmrg * string at other times.
11535c4bbdfSmrg *
11635c4bbdfSmrg * @param[in]  pid     Process ID of a client.
11735c4bbdfSmrg
11835c4bbdfSmrg * @param[out] cmdname Client process name without arguments. You must
11935c4bbdfSmrg *                     release this by calling free. On error NULL is
12035c4bbdfSmrg *                     returned. Pass NULL if you aren't interested in
12135c4bbdfSmrg *                     this value.
12235c4bbdfSmrg * @param[out] cmdargs Arguments to client process. Useful for
12335c4bbdfSmrg *                     identifying a client that is executed from a
12435c4bbdfSmrg *                     launcher program. You must release this by
12535c4bbdfSmrg *                     calling free. On error NULL is returned. Pass
12635c4bbdfSmrg *                     NULL if you aren't interested in this value.
12735c4bbdfSmrg *
12835c4bbdfSmrg * @see GetClientCmdName/Args
12935c4bbdfSmrg */
13035c4bbdfSmrgvoid
13135c4bbdfSmrgDetermineClientCmd(pid_t pid, const char **cmdname, const char **cmdargs)
13235c4bbdfSmrg{
13335c4bbdfSmrg    char path[PATH_MAX + 1];
13435c4bbdfSmrg    int totsize = 0;
13535c4bbdfSmrg    int fd = 0;
13635c4bbdfSmrg
13735c4bbdfSmrg    if (cmdname)
13835c4bbdfSmrg        *cmdname = NULL;
13935c4bbdfSmrg    if (cmdargs)
14035c4bbdfSmrg        *cmdargs = NULL;
14135c4bbdfSmrg
14235c4bbdfSmrg    if (pid == -1)
14335c4bbdfSmrg        return;
14435c4bbdfSmrg
1451b5d61b8Smrg#if defined(__OpenBSD__)
14635c4bbdfSmrg    /* on OpenBSD use kvm_getargv() */
14735c4bbdfSmrg    {
14835c4bbdfSmrg        kvm_t *kd;
14935c4bbdfSmrg        char errbuf[_POSIX2_LINE_MAX];
15035c4bbdfSmrg        char **argv;
15135c4bbdfSmrg        struct kinfo_proc *kp;
15235c4bbdfSmrg        size_t len = 0;
15335c4bbdfSmrg        int i, n;
15435c4bbdfSmrg
15535c4bbdfSmrg        kd = kvm_open(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
15635c4bbdfSmrg        if (kd == NULL)
15735c4bbdfSmrg            return;
15835c4bbdfSmrg        kp = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof(struct kinfo_proc),
15935c4bbdfSmrg                          &n);
16035c4bbdfSmrg        if (n != 1)
16135c4bbdfSmrg            return;
16235c4bbdfSmrg        argv = kvm_getargv(kd, kp, 0);
16335c4bbdfSmrg        *cmdname = strdup(argv[0]);
16435c4bbdfSmrg        i = 1;
16535c4bbdfSmrg        while (argv[i] != NULL) {
16635c4bbdfSmrg            len += strlen(argv[i]) + 1;
16735c4bbdfSmrg            i++;
16835c4bbdfSmrg        }
16935c4bbdfSmrg        *cmdargs = calloc(1, len);
17035c4bbdfSmrg        i = 1;
17135c4bbdfSmrg        while (argv[i] != NULL) {
17235c4bbdfSmrg            strlcat(*cmdargs, argv[i], len);
17335c4bbdfSmrg            strlcat(*cmdargs, " ", len);
17435c4bbdfSmrg            i++;
17535c4bbdfSmrg        }
17635c4bbdfSmrg        kvm_close(kd);
17735c4bbdfSmrg    }
17835c4bbdfSmrg#else                           /* Linux using /proc/pid/cmdline */
17935c4bbdfSmrg
18035c4bbdfSmrg    /* Check if /proc/pid/cmdline exists. It's not supported on all
18135c4bbdfSmrg     * operating systems. */
18235c4bbdfSmrg    if (snprintf(path, sizeof(path), "/proc/%d/cmdline", pid) < 0)
18335c4bbdfSmrg        return;
18435c4bbdfSmrg    fd = open(path, O_RDONLY);
18535c4bbdfSmrg    if (fd < 0)
1861b5d61b8Smrg#ifdef __sun
1871b5d61b8Smrg        goto fallback;
1881b5d61b8Smrg#else
18935c4bbdfSmrg        return;
1901b5d61b8Smrg#endif
19135c4bbdfSmrg
19235c4bbdfSmrg    /* Read the contents of /proc/pid/cmdline. It should contain the
19335c4bbdfSmrg     * process name and arguments. */
19435c4bbdfSmrg    totsize = read(fd, path, sizeof(path));
19535c4bbdfSmrg    close(fd);
19635c4bbdfSmrg    if (totsize <= 0)
19735c4bbdfSmrg        return;
19835c4bbdfSmrg    path[totsize - 1] = '\0';
19935c4bbdfSmrg
20035c4bbdfSmrg    /* Contruct the process name without arguments. */
20135c4bbdfSmrg    if (cmdname) {
20235c4bbdfSmrg        *cmdname = strdup(path);
20335c4bbdfSmrg    }
20435c4bbdfSmrg
20535c4bbdfSmrg    /* Construct the arguments for client process. */
20635c4bbdfSmrg    if (cmdargs) {
20735c4bbdfSmrg        int cmdsize = strlen(path) + 1;
20835c4bbdfSmrg        int argsize = totsize - cmdsize;
20935c4bbdfSmrg        char *args = NULL;
21035c4bbdfSmrg
21135c4bbdfSmrg        if (argsize > 0)
21235c4bbdfSmrg            args = malloc(argsize);
21335c4bbdfSmrg        if (args) {
21435c4bbdfSmrg            int i = 0;
21535c4bbdfSmrg
21635c4bbdfSmrg            for (i = 0; i < (argsize - 1); ++i) {
21735c4bbdfSmrg                const char c = path[cmdsize + i];
21835c4bbdfSmrg
21935c4bbdfSmrg                args[i] = (c == '\0') ? ' ' : c;
22035c4bbdfSmrg            }
22135c4bbdfSmrg            args[argsize - 1] = '\0';
22235c4bbdfSmrg            *cmdargs = args;
22335c4bbdfSmrg        }
22435c4bbdfSmrg    }
2251b5d61b8Smrg    return;
2261b5d61b8Smrg#endif
2271b5d61b8Smrg
2281b5d61b8Smrg#ifdef __sun                    /* Solaris */
2291b5d61b8Smrg  fallback:
2301b5d61b8Smrg    /* Solaris prior to 11.3.5 does not support /proc/pid/cmdline, but
2311b5d61b8Smrg     * makes information similar to what ps shows available in a binary
2321b5d61b8Smrg     * structure in the /proc/pid/psinfo file. */
2331b5d61b8Smrg    if (snprintf(path, sizeof(path), "/proc/%d/psinfo", pid) < 0)
2341b5d61b8Smrg        return;
2351b5d61b8Smrg    fd = open(path, O_RDONLY);
2361b5d61b8Smrg    if (fd < 0) {
2371b5d61b8Smrg        ErrorF("Failed to open %s: %s\n", path, strerror(errno));
2381b5d61b8Smrg        return;
2391b5d61b8Smrg    }
2401b5d61b8Smrg    else {
2411b5d61b8Smrg        psinfo_t psinfo = { 0 };
2421b5d61b8Smrg        char *sp;
2431b5d61b8Smrg
2441b5d61b8Smrg        totsize = read(fd, &psinfo, sizeof(psinfo_t));
2451b5d61b8Smrg        close(fd);
2461b5d61b8Smrg        if (totsize <= 0)
2471b5d61b8Smrg            return;
2481b5d61b8Smrg
2491b5d61b8Smrg        /* pr_psargs is the first PRARGSZ (80) characters of the command
2501b5d61b8Smrg         * line string - assume up to the first space is the command name,
2511b5d61b8Smrg         * since it's not delimited.   While there is also pr_fname, that's
2521b5d61b8Smrg         * more limited, giving only the first 16 chars of the basename of
2531b5d61b8Smrg         * the file that was exec'ed, thus cutting off many long gnome
2541b5d61b8Smrg         * command names, or returning "isapython2.6" for all python scripts.
2551b5d61b8Smrg         */
2561b5d61b8Smrg        psinfo.pr_psargs[PRARGSZ - 1] = '\0';
2571b5d61b8Smrg        sp = strchr(psinfo.pr_psargs, ' ');
2581b5d61b8Smrg        if (sp)
2591b5d61b8Smrg            *sp++ = '\0';
2601b5d61b8Smrg
2611b5d61b8Smrg        if (cmdname)
2621b5d61b8Smrg            *cmdname = strdup(psinfo.pr_psargs);
2631b5d61b8Smrg
2641b5d61b8Smrg        if (cmdargs && sp)
2651b5d61b8Smrg            *cmdargs = strdup(sp);
2661b5d61b8Smrg    }
26735c4bbdfSmrg#endif
26835c4bbdfSmrg}
26935c4bbdfSmrg
27035c4bbdfSmrg/**
27135c4bbdfSmrg * Called when a new client connects. Allocates client ID information.
27235c4bbdfSmrg *
27335c4bbdfSmrg * @param[in] client Recently connected client.
27435c4bbdfSmrg */
27535c4bbdfSmrgvoid
27635c4bbdfSmrgReserveClientIds(struct _Client *client)
27735c4bbdfSmrg{
27835c4bbdfSmrg#ifdef CLIENTIDS
27935c4bbdfSmrg    if (client == NullClient)
28035c4bbdfSmrg        return;
28135c4bbdfSmrg
28235c4bbdfSmrg    assert(!client->clientIds);
28335c4bbdfSmrg    client->clientIds = calloc(1, sizeof(ClientIdRec));
28435c4bbdfSmrg    if (!client->clientIds)
28535c4bbdfSmrg        return;
28635c4bbdfSmrg
28735c4bbdfSmrg    client->clientIds->pid = DetermineClientPid(client);
28835c4bbdfSmrg    if (client->clientIds->pid != -1)
28935c4bbdfSmrg        DetermineClientCmd(client->clientIds->pid, &client->clientIds->cmdname,
29035c4bbdfSmrg                           &client->clientIds->cmdargs);
29135c4bbdfSmrg
29235c4bbdfSmrg    DebugF("client(%lx): Reserved pid(%d).\n",
29335c4bbdfSmrg           (unsigned long) client->clientAsMask, client->clientIds->pid);
29435c4bbdfSmrg    DebugF("client(%lx): Reserved cmdname(%s) and cmdargs(%s).\n",
29535c4bbdfSmrg           (unsigned long) client->clientAsMask,
29635c4bbdfSmrg           client->clientIds->cmdname ? client->clientIds->cmdname : "NULL",
29735c4bbdfSmrg           client->clientIds->cmdargs ? client->clientIds->cmdargs : "NULL");
29835c4bbdfSmrg#endif                          /* CLIENTIDS */
29935c4bbdfSmrg}
30035c4bbdfSmrg
30135c4bbdfSmrg/**
30235c4bbdfSmrg * Called when an existing client disconnects. Frees client ID
30335c4bbdfSmrg * information.
30435c4bbdfSmrg *
30535c4bbdfSmrg * @param[in] client Recently disconnected client.
30635c4bbdfSmrg */
30735c4bbdfSmrgvoid
30835c4bbdfSmrgReleaseClientIds(struct _Client *client)
30935c4bbdfSmrg{
31035c4bbdfSmrg#ifdef CLIENTIDS
31135c4bbdfSmrg    if (client == NullClient)
31235c4bbdfSmrg        return;
31335c4bbdfSmrg
31435c4bbdfSmrg    if (!client->clientIds)
31535c4bbdfSmrg        return;
31635c4bbdfSmrg
31735c4bbdfSmrg    DebugF("client(%lx): Released pid(%d).\n",
31835c4bbdfSmrg           (unsigned long) client->clientAsMask, client->clientIds->pid);
31935c4bbdfSmrg    DebugF("client(%lx): Released cmdline(%s) and cmdargs(%s).\n",
32035c4bbdfSmrg           (unsigned long) client->clientAsMask,
32135c4bbdfSmrg           client->clientIds->cmdname ? client->clientIds->cmdname : "NULL",
32235c4bbdfSmrg           client->clientIds->cmdargs ? client->clientIds->cmdargs : "NULL");
32335c4bbdfSmrg
32435c4bbdfSmrg    free((void *) client->clientIds->cmdname);  /* const char * */
32535c4bbdfSmrg    free((void *) client->clientIds->cmdargs);  /* const char * */
32635c4bbdfSmrg    free(client->clientIds);
32735c4bbdfSmrg    client->clientIds = NULL;
32835c4bbdfSmrg#endif                          /* CLIENTIDS */
32935c4bbdfSmrg}
33035c4bbdfSmrg
33135c4bbdfSmrg/**
33235c4bbdfSmrg * Get cached PID of a client.
33335c4bbdfSmrg *
33435c4bbdfSmrg * param[in] client Client whose PID has been already cached.
33535c4bbdfSmrg *
33635c4bbdfSmrg * @return Cached client PID. Error (-1) if called:
33735c4bbdfSmrg *         - before ClientStateInitial client state notification
33835c4bbdfSmrg *         - after ClientStateGone client state notification
33935c4bbdfSmrg *         - for remote clients
34035c4bbdfSmrg *
34135c4bbdfSmrg * @see DetermineClientPid
34235c4bbdfSmrg */
34335c4bbdfSmrgpid_t
34435c4bbdfSmrgGetClientPid(struct _Client *client)
34535c4bbdfSmrg{
34635c4bbdfSmrg    if (client == NullClient)
34735c4bbdfSmrg        return -1;
34835c4bbdfSmrg
34935c4bbdfSmrg    if (!client->clientIds)
35035c4bbdfSmrg        return -1;
35135c4bbdfSmrg
35235c4bbdfSmrg    return client->clientIds->pid;
35335c4bbdfSmrg}
35435c4bbdfSmrg
35535c4bbdfSmrg/**
35635c4bbdfSmrg * Get cached command name string of a client.
35735c4bbdfSmrg *
35835c4bbdfSmrg * param[in] client Client whose command line string has been already
35935c4bbdfSmrg *                  cached.
36035c4bbdfSmrg *
36135c4bbdfSmrg * @return Cached client command name. Error (NULL) if called:
36235c4bbdfSmrg *         - before ClientStateInitial client state notification
36335c4bbdfSmrg *         - after ClientStateGone client state notification
36435c4bbdfSmrg *         - for remote clients
36535c4bbdfSmrg *         - on OS that doesn't support mapping of PID to command line
36635c4bbdfSmrg *
36735c4bbdfSmrg * @see DetermineClientCmd
36835c4bbdfSmrg */
36935c4bbdfSmrgconst char *
37035c4bbdfSmrgGetClientCmdName(struct _Client *client)
37135c4bbdfSmrg{
37235c4bbdfSmrg    if (client == NullClient)
37335c4bbdfSmrg        return NULL;
37435c4bbdfSmrg
37535c4bbdfSmrg    if (!client->clientIds)
37635c4bbdfSmrg        return NULL;
37735c4bbdfSmrg
37835c4bbdfSmrg    return client->clientIds->cmdname;
37935c4bbdfSmrg}
38035c4bbdfSmrg
38135c4bbdfSmrg/**
38235c4bbdfSmrg * Get cached command arguments string of a client.
38335c4bbdfSmrg *
38435c4bbdfSmrg * param[in] client Client whose command line string has been already
38535c4bbdfSmrg *                  cached.
38635c4bbdfSmrg *
38735c4bbdfSmrg * @return Cached client command arguments. Error (NULL) if called:
38835c4bbdfSmrg *         - before ClientStateInitial client state notification
38935c4bbdfSmrg *         - after ClientStateGone client state notification
39035c4bbdfSmrg *         - for remote clients
39135c4bbdfSmrg *         - on OS that doesn't support mapping of PID to command line
39235c4bbdfSmrg *
39335c4bbdfSmrg * @see DetermineClientCmd
39435c4bbdfSmrg */
39535c4bbdfSmrgconst char *
39635c4bbdfSmrgGetClientCmdArgs(struct _Client *client)
39735c4bbdfSmrg{
39835c4bbdfSmrg    if (client == NullClient)
39935c4bbdfSmrg        return NULL;
40035c4bbdfSmrg
40135c4bbdfSmrg    if (!client->clientIds)
40235c4bbdfSmrg        return NULL;
40335c4bbdfSmrg
40435c4bbdfSmrg    return client->clientIds->cmdargs;
40535c4bbdfSmrg}
406