client.c revision 35c4bbdf
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
14535c4bbdfSmrg#ifdef __sun                    /* Solaris */
14635c4bbdfSmrg    /* Solaris does not support /proc/pid/cmdline, but makes information
14735c4bbdfSmrg     * similar to what ps shows available in a binary structure in the
14835c4bbdfSmrg     * /proc/pid/psinfo file. */
14935c4bbdfSmrg    if (snprintf(path, sizeof(path), "/proc/%d/psinfo", pid) < 0)
15035c4bbdfSmrg        return;
15135c4bbdfSmrg    fd = open(path, O_RDONLY);
15235c4bbdfSmrg    if (fd < 0) {
15335c4bbdfSmrg        ErrorF("Failed to open %s: %s\n", path, strerror(errno));
15435c4bbdfSmrg        return;
15535c4bbdfSmrg    }
15635c4bbdfSmrg    else {
15735c4bbdfSmrg        psinfo_t psinfo = { 0 };
15835c4bbdfSmrg        char *sp;
15935c4bbdfSmrg
16035c4bbdfSmrg        totsize = read(fd, &psinfo, sizeof(psinfo_t));
16135c4bbdfSmrg        close(fd);
16235c4bbdfSmrg        if (totsize <= 0)
16335c4bbdfSmrg            return;
16435c4bbdfSmrg
16535c4bbdfSmrg        /* pr_psargs is the first PRARGSZ (80) characters of the command
16635c4bbdfSmrg         * line string - assume up to the first space is the command name,
16735c4bbdfSmrg         * since it's not delimited.   While there is also pr_fname, that's
16835c4bbdfSmrg         * more limited, giving only the first 16 chars of the basename of
16935c4bbdfSmrg         * the file that was exec'ed, thus cutting off many long gnome
17035c4bbdfSmrg         * command names, or returning "isapython2.6" for all python scripts.
17135c4bbdfSmrg         */
17235c4bbdfSmrg        psinfo.pr_psargs[PRARGSZ - 1] = '\0';
17335c4bbdfSmrg        sp = strchr(psinfo.pr_psargs, ' ');
17435c4bbdfSmrg        if (sp)
17535c4bbdfSmrg            *sp++ = '\0';
17635c4bbdfSmrg
17735c4bbdfSmrg        if (cmdname)
17835c4bbdfSmrg            *cmdname = strdup(psinfo.pr_psargs);
17935c4bbdfSmrg
18035c4bbdfSmrg        if (cmdargs && sp)
18135c4bbdfSmrg            *cmdargs = strdup(sp);
18235c4bbdfSmrg    }
18335c4bbdfSmrg#elif defined(__OpenBSD__)
18435c4bbdfSmrg    /* on OpenBSD use kvm_getargv() */
18535c4bbdfSmrg    {
18635c4bbdfSmrg        kvm_t *kd;
18735c4bbdfSmrg        char errbuf[_POSIX2_LINE_MAX];
18835c4bbdfSmrg        char **argv;
18935c4bbdfSmrg        struct kinfo_proc *kp;
19035c4bbdfSmrg        size_t len = 0;
19135c4bbdfSmrg        int i, n;
19235c4bbdfSmrg
19335c4bbdfSmrg        kd = kvm_open(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
19435c4bbdfSmrg        if (kd == NULL)
19535c4bbdfSmrg            return;
19635c4bbdfSmrg        kp = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof(struct kinfo_proc),
19735c4bbdfSmrg                          &n);
19835c4bbdfSmrg        if (n != 1)
19935c4bbdfSmrg            return;
20035c4bbdfSmrg        argv = kvm_getargv(kd, kp, 0);
20135c4bbdfSmrg        *cmdname = strdup(argv[0]);
20235c4bbdfSmrg        i = 1;
20335c4bbdfSmrg        while (argv[i] != NULL) {
20435c4bbdfSmrg            len += strlen(argv[i]) + 1;
20535c4bbdfSmrg            i++;
20635c4bbdfSmrg        }
20735c4bbdfSmrg        *cmdargs = calloc(1, len);
20835c4bbdfSmrg        i = 1;
20935c4bbdfSmrg        while (argv[i] != NULL) {
21035c4bbdfSmrg            strlcat(*cmdargs, argv[i], len);
21135c4bbdfSmrg            strlcat(*cmdargs, " ", len);
21235c4bbdfSmrg            i++;
21335c4bbdfSmrg        }
21435c4bbdfSmrg        kvm_close(kd);
21535c4bbdfSmrg    }
21635c4bbdfSmrg#else                           /* Linux using /proc/pid/cmdline */
21735c4bbdfSmrg
21835c4bbdfSmrg    /* Check if /proc/pid/cmdline exists. It's not supported on all
21935c4bbdfSmrg     * operating systems. */
22035c4bbdfSmrg    if (snprintf(path, sizeof(path), "/proc/%d/cmdline", pid) < 0)
22135c4bbdfSmrg        return;
22235c4bbdfSmrg    fd = open(path, O_RDONLY);
22335c4bbdfSmrg    if (fd < 0)
22435c4bbdfSmrg        return;
22535c4bbdfSmrg
22635c4bbdfSmrg    /* Read the contents of /proc/pid/cmdline. It should contain the
22735c4bbdfSmrg     * process name and arguments. */
22835c4bbdfSmrg    totsize = read(fd, path, sizeof(path));
22935c4bbdfSmrg    close(fd);
23035c4bbdfSmrg    if (totsize <= 0)
23135c4bbdfSmrg        return;
23235c4bbdfSmrg    path[totsize - 1] = '\0';
23335c4bbdfSmrg
23435c4bbdfSmrg    /* Contruct the process name without arguments. */
23535c4bbdfSmrg    if (cmdname) {
23635c4bbdfSmrg        *cmdname = strdup(path);
23735c4bbdfSmrg    }
23835c4bbdfSmrg
23935c4bbdfSmrg    /* Construct the arguments for client process. */
24035c4bbdfSmrg    if (cmdargs) {
24135c4bbdfSmrg        int cmdsize = strlen(path) + 1;
24235c4bbdfSmrg        int argsize = totsize - cmdsize;
24335c4bbdfSmrg        char *args = NULL;
24435c4bbdfSmrg
24535c4bbdfSmrg        if (argsize > 0)
24635c4bbdfSmrg            args = malloc(argsize);
24735c4bbdfSmrg        if (args) {
24835c4bbdfSmrg            int i = 0;
24935c4bbdfSmrg
25035c4bbdfSmrg            for (i = 0; i < (argsize - 1); ++i) {
25135c4bbdfSmrg                const char c = path[cmdsize + i];
25235c4bbdfSmrg
25335c4bbdfSmrg                args[i] = (c == '\0') ? ' ' : c;
25435c4bbdfSmrg            }
25535c4bbdfSmrg            args[argsize - 1] = '\0';
25635c4bbdfSmrg            *cmdargs = args;
25735c4bbdfSmrg        }
25835c4bbdfSmrg    }
25935c4bbdfSmrg#endif
26035c4bbdfSmrg}
26135c4bbdfSmrg
26235c4bbdfSmrg/**
26335c4bbdfSmrg * Called when a new client connects. Allocates client ID information.
26435c4bbdfSmrg *
26535c4bbdfSmrg * @param[in] client Recently connected client.
26635c4bbdfSmrg */
26735c4bbdfSmrgvoid
26835c4bbdfSmrgReserveClientIds(struct _Client *client)
26935c4bbdfSmrg{
27035c4bbdfSmrg#ifdef CLIENTIDS
27135c4bbdfSmrg    if (client == NullClient)
27235c4bbdfSmrg        return;
27335c4bbdfSmrg
27435c4bbdfSmrg    assert(!client->clientIds);
27535c4bbdfSmrg    client->clientIds = calloc(1, sizeof(ClientIdRec));
27635c4bbdfSmrg    if (!client->clientIds)
27735c4bbdfSmrg        return;
27835c4bbdfSmrg
27935c4bbdfSmrg    client->clientIds->pid = DetermineClientPid(client);
28035c4bbdfSmrg    if (client->clientIds->pid != -1)
28135c4bbdfSmrg        DetermineClientCmd(client->clientIds->pid, &client->clientIds->cmdname,
28235c4bbdfSmrg                           &client->clientIds->cmdargs);
28335c4bbdfSmrg
28435c4bbdfSmrg    DebugF("client(%lx): Reserved pid(%d).\n",
28535c4bbdfSmrg           (unsigned long) client->clientAsMask, client->clientIds->pid);
28635c4bbdfSmrg    DebugF("client(%lx): Reserved cmdname(%s) and cmdargs(%s).\n",
28735c4bbdfSmrg           (unsigned long) client->clientAsMask,
28835c4bbdfSmrg           client->clientIds->cmdname ? client->clientIds->cmdname : "NULL",
28935c4bbdfSmrg           client->clientIds->cmdargs ? client->clientIds->cmdargs : "NULL");
29035c4bbdfSmrg#endif                          /* CLIENTIDS */
29135c4bbdfSmrg}
29235c4bbdfSmrg
29335c4bbdfSmrg/**
29435c4bbdfSmrg * Called when an existing client disconnects. Frees client ID
29535c4bbdfSmrg * information.
29635c4bbdfSmrg *
29735c4bbdfSmrg * @param[in] client Recently disconnected client.
29835c4bbdfSmrg */
29935c4bbdfSmrgvoid
30035c4bbdfSmrgReleaseClientIds(struct _Client *client)
30135c4bbdfSmrg{
30235c4bbdfSmrg#ifdef CLIENTIDS
30335c4bbdfSmrg    if (client == NullClient)
30435c4bbdfSmrg        return;
30535c4bbdfSmrg
30635c4bbdfSmrg    if (!client->clientIds)
30735c4bbdfSmrg        return;
30835c4bbdfSmrg
30935c4bbdfSmrg    DebugF("client(%lx): Released pid(%d).\n",
31035c4bbdfSmrg           (unsigned long) client->clientAsMask, client->clientIds->pid);
31135c4bbdfSmrg    DebugF("client(%lx): Released cmdline(%s) and cmdargs(%s).\n",
31235c4bbdfSmrg           (unsigned long) client->clientAsMask,
31335c4bbdfSmrg           client->clientIds->cmdname ? client->clientIds->cmdname : "NULL",
31435c4bbdfSmrg           client->clientIds->cmdargs ? client->clientIds->cmdargs : "NULL");
31535c4bbdfSmrg
31635c4bbdfSmrg    free((void *) client->clientIds->cmdname);  /* const char * */
31735c4bbdfSmrg    free((void *) client->clientIds->cmdargs);  /* const char * */
31835c4bbdfSmrg    free(client->clientIds);
31935c4bbdfSmrg    client->clientIds = NULL;
32035c4bbdfSmrg#endif                          /* CLIENTIDS */
32135c4bbdfSmrg}
32235c4bbdfSmrg
32335c4bbdfSmrg/**
32435c4bbdfSmrg * Get cached PID of a client.
32535c4bbdfSmrg *
32635c4bbdfSmrg * param[in] client Client whose PID has been already cached.
32735c4bbdfSmrg *
32835c4bbdfSmrg * @return Cached client PID. Error (-1) if called:
32935c4bbdfSmrg *         - before ClientStateInitial client state notification
33035c4bbdfSmrg *         - after ClientStateGone client state notification
33135c4bbdfSmrg *         - for remote clients
33235c4bbdfSmrg *
33335c4bbdfSmrg * @see DetermineClientPid
33435c4bbdfSmrg */
33535c4bbdfSmrgpid_t
33635c4bbdfSmrgGetClientPid(struct _Client *client)
33735c4bbdfSmrg{
33835c4bbdfSmrg    if (client == NullClient)
33935c4bbdfSmrg        return -1;
34035c4bbdfSmrg
34135c4bbdfSmrg    if (!client->clientIds)
34235c4bbdfSmrg        return -1;
34335c4bbdfSmrg
34435c4bbdfSmrg    return client->clientIds->pid;
34535c4bbdfSmrg}
34635c4bbdfSmrg
34735c4bbdfSmrg/**
34835c4bbdfSmrg * Get cached command name string of a client.
34935c4bbdfSmrg *
35035c4bbdfSmrg * param[in] client Client whose command line string has been already
35135c4bbdfSmrg *                  cached.
35235c4bbdfSmrg *
35335c4bbdfSmrg * @return Cached client command name. Error (NULL) if called:
35435c4bbdfSmrg *         - before ClientStateInitial client state notification
35535c4bbdfSmrg *         - after ClientStateGone client state notification
35635c4bbdfSmrg *         - for remote clients
35735c4bbdfSmrg *         - on OS that doesn't support mapping of PID to command line
35835c4bbdfSmrg *
35935c4bbdfSmrg * @see DetermineClientCmd
36035c4bbdfSmrg */
36135c4bbdfSmrgconst char *
36235c4bbdfSmrgGetClientCmdName(struct _Client *client)
36335c4bbdfSmrg{
36435c4bbdfSmrg    if (client == NullClient)
36535c4bbdfSmrg        return NULL;
36635c4bbdfSmrg
36735c4bbdfSmrg    if (!client->clientIds)
36835c4bbdfSmrg        return NULL;
36935c4bbdfSmrg
37035c4bbdfSmrg    return client->clientIds->cmdname;
37135c4bbdfSmrg}
37235c4bbdfSmrg
37335c4bbdfSmrg/**
37435c4bbdfSmrg * Get cached command arguments string of a client.
37535c4bbdfSmrg *
37635c4bbdfSmrg * param[in] client Client whose command line string has been already
37735c4bbdfSmrg *                  cached.
37835c4bbdfSmrg *
37935c4bbdfSmrg * @return Cached client command arguments. Error (NULL) if called:
38035c4bbdfSmrg *         - before ClientStateInitial client state notification
38135c4bbdfSmrg *         - after ClientStateGone client state notification
38235c4bbdfSmrg *         - for remote clients
38335c4bbdfSmrg *         - on OS that doesn't support mapping of PID to command line
38435c4bbdfSmrg *
38535c4bbdfSmrg * @see DetermineClientCmd
38635c4bbdfSmrg */
38735c4bbdfSmrgconst char *
38835c4bbdfSmrgGetClientCmdArgs(struct _Client *client)
38935c4bbdfSmrg{
39035c4bbdfSmrg    if (client == NullClient)
39135c4bbdfSmrg        return NULL;
39235c4bbdfSmrg
39335c4bbdfSmrg    if (!client->clientIds)
39435c4bbdfSmrg        return NULL;
39535c4bbdfSmrg
39635c4bbdfSmrg    return client->clientIds->cmdargs;
39735c4bbdfSmrg}
398