1/*
2 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). All
3 * rights reserved.
4 * Copyright (c) 1993, 2010, Oracle and/or its affiliates. All rights reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24
25/**
26 * @file
27 *
28 * This file contains functionality for identifying clients by various
29 * means. The primary purpose of identification is to simply aid in
30 * finding out which clients are using X server and how they are using
31 * it. For example, it's often necessary to monitor what requests
32 * clients are executing (to spot bad behaviour) and how they are
33 * allocating resources in X server (to spot excessive resource
34 * usage).
35 *
36 * This framework automatically allocates information, that can be
37 * used for client identification, when a client connects to the
38 * server. The information is freed when the client disconnects. The
39 * allocated information is just a collection of various IDs, such as
40 * PID and process name for local clients, that are likely to be
41 * useful in analyzing X server usage.
42 *
43 * Users of the framework can query ID information about clients at
44 * any time. To avoid repeated polling of IDs the users can also
45 * subscribe for notifications about the availability of ID
46 * information. IDs have been allocated before ClientStateCallback is
47 * called with ClientStateInitial state. Similarly the IDs will be
48 * released after ClientStateCallback is called with ClientStateGone
49 * state.
50 *
51 * Author: Rami Ylimäki <rami.ylimaki@vincit.fi>
52 */
53
54#include <sys/stat.h>
55#include <fcntl.h>
56#include <unistd.h>
57
58#include "client.h"
59#include "os.h"
60#include "dixstruct.h"
61
62#ifdef __sun
63#include <errno.h>
64#include <procfs.h>
65#endif
66
67#ifdef __OpenBSD__
68#include <sys/param.h>
69#include <sys/sysctl.h>
70#include <sys/types.h>
71
72#include <kvm.h>
73#include <limits.h>
74#endif
75
76#ifdef __APPLE__
77#include <dispatch/dispatch.h>
78#include <errno.h>
79#include <sys/sysctl.h>
80#endif
81
82/**
83 * Try to determine a PID for a client from its connection
84 * information. This should be called only once when new client has
85 * connected, use GetClientPid to determine the PID at other times.
86 *
87 * @param[in] client Connection linked to some process.
88 *
89 * @return PID of the client. Error (-1) if PID can't be determined
90 *         for the client.
91 *
92 * @see GetClientPid
93 */
94pid_t
95DetermineClientPid(struct _Client * client)
96{
97    LocalClientCredRec *lcc = NULL;
98    pid_t pid = -1;
99
100    if (client == NullClient)
101        return pid;
102
103    if (client == serverClient)
104        return getpid();
105
106    if (GetLocalClientCreds(client, &lcc) != -1) {
107        if (lcc->fieldsSet & LCC_PID_SET)
108            pid = lcc->pid;
109        FreeLocalClientCreds(lcc);
110    }
111
112    return pid;
113}
114
115/**
116 * Try to determine a command line string for a client based on its
117 * PID. Note that mapping PID to a command hasn't been implemented for
118 * some operating systems. This should be called only once when a new
119 * client has connected, use GetClientCmdName/Args to determine the
120 * string at other times.
121 *
122 * @param[in]  pid     Process ID of a client.
123
124 * @param[out] cmdname Client process name without arguments. You must
125 *                     release this by calling free. On error NULL is
126 *                     returned. Pass NULL if you aren't interested in
127 *                     this value.
128 * @param[out] cmdargs Arguments to client process. Useful for
129 *                     identifying a client that is executed from a
130 *                     launcher program. You must release this by
131 *                     calling free. On error NULL is returned. Pass
132 *                     NULL if you aren't interested in this value.
133 *
134 * @see GetClientCmdName/Args
135 */
136void
137DetermineClientCmd(pid_t pid, const char **cmdname, const char **cmdargs)
138{
139#if !defined(__APPLE__)
140    char path[PATH_MAX + 1];
141    int totsize = 0;
142    int fd = 0;
143#endif
144
145    if (cmdname)
146        *cmdname = NULL;
147    if (cmdargs)
148        *cmdargs = NULL;
149
150    if (pid == -1)
151        return;
152
153#if defined (__APPLE__)
154    {
155        static dispatch_once_t once;
156        static int argmax;
157        dispatch_once(&once, ^{
158            int mib[2];
159            size_t len;
160
161            mib[0] = CTL_KERN;
162            mib[1] = KERN_ARGMAX;
163
164            len = sizeof(argmax);
165            if (sysctl(mib, 2, &argmax, &len, NULL, 0) == -1) {
166                ErrorF("Unable to dynamically determine kern.argmax, using ARG_MAX (%d)\n", ARG_MAX);
167                argmax = ARG_MAX;
168            }
169        });
170
171        int mib[3];
172        size_t len = argmax;
173        int32_t argc = -1;
174
175        char * const procargs = malloc(len);
176        if (!procargs) {
177            ErrorF("Failed to allocate memory (%lu bytes) for KERN_PROCARGS2 result for pid %d: %s\n", len, pid, strerror(errno));
178            return;
179        }
180
181        mib[0] = CTL_KERN;
182        mib[1] = KERN_PROCARGS2;
183        mib[2] = pid;
184
185        if (sysctl(mib, 3, procargs, &len, NULL, 0) == -1) {
186            ErrorF("Failed to determine KERN_PROCARGS2 for pid %d: %s\n", pid, strerror(errno));
187            free(procargs);
188            return;
189        }
190
191        if (len < sizeof(argc) || len > argmax) {
192            ErrorF("Erroneous length returned when querying KERN_PROCARGS2 for pid %d: %zu\n", pid, len);
193            free(procargs);
194            return;
195        }
196
197        /* Ensure we have a failsafe NUL termination just in case the last entry
198         * was not actually NUL terminated.
199         */
200        procargs[len-1] = '\0';
201
202        /* Setup our iterator */
203        char *is = procargs;
204
205        /* The first element in the buffer is argc as a 32bit int. When using
206         * the older KERN_PROCARGS, this is omitted, and one needs to guess
207         * (usually by checking for an `=` character) when we start seeing
208         * envvars instead of arguments.
209         */
210        argc = *(int32_t *)is;
211        is += sizeof(argc);
212
213        /* The very next string is the executable path.  Skip over it since
214         * this function wants to return argv[0] and argv[1...n].
215         */
216        is += strlen(is) + 1;
217
218        /* Skip over extra NUL characters to get to the start of argv[0] */
219        for (; (is < &procargs[len]) && !(*is); is++);
220
221        if (! (is < &procargs[len])) {
222            ErrorF("Arguments were not returned when querying KERN_PROCARGS2 for pid %d: %zu\n", pid, len);
223            free(procargs);
224            return;
225        }
226
227        if (cmdname) {
228            *cmdname = strdup(is);
229        }
230
231        /* Jump over argv[0] and point to argv[1] */
232        is += strlen(is) + 1;
233
234        if (cmdargs && is < &procargs[len]) {
235            char *args = is;
236
237            /* Remove the NUL terminators except the last one */
238            for (int i = 1; i < argc - 1; i++) {
239                /* Advance to the NUL terminator */
240                is += strlen(is);
241
242                /* Change the NUL to a space, ensuring we don't accidentally remove the terminal NUL */
243                if (is < &procargs[len-1]) {
244                    *is = ' ';
245                }
246            }
247
248            *cmdargs = strdup(args);
249        }
250
251        free(procargs);
252    }
253#elif defined(__OpenBSD__)
254    /* on OpenBSD use kvm_getargv() */
255    {
256        kvm_t *kd;
257        char errbuf[_POSIX2_LINE_MAX];
258        char **argv;
259        struct kinfo_proc *kp;
260        size_t len = 0;
261        int i, n;
262
263        kd = kvm_open(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
264        if (kd == NULL)
265            return;
266        kp = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof(struct kinfo_proc),
267                          &n);
268        if (n != 1)
269            return;
270        argv = kvm_getargv(kd, kp, 0);
271        if (cmdname) {
272            if (argv == NULL || argv[0] == NULL)
273                return;
274            else
275                *cmdname = strdup(argv[0]);
276        }
277        if (cmdargs) {
278            i = 1;
279            while (argv[i] != NULL) {
280                len += strlen(argv[i]) + 1;
281                i++;
282            }
283            *cmdargs = calloc(1, len);
284            i = 1;
285            while (argv[i] != NULL) {
286                strlcat(*(char **)cmdargs, argv[i], len);
287                strlcat(*(char **)cmdargs, " ", len);
288                i++;
289            }
290        }
291        kvm_close(kd);
292    }
293#else                           /* Linux using /proc/pid/cmdline */
294
295    /* Check if /proc/pid/cmdline exists. It's not supported on all
296     * operating systems. */
297    if (snprintf(path, sizeof(path), "/proc/%d/cmdline", pid) < 0)
298        return;
299    fd = open(path, O_RDONLY);
300    if (fd < 0)
301#ifdef __sun
302        goto fallback;
303#else
304        return;
305#endif
306
307    /* Read the contents of /proc/pid/cmdline. It should contain the
308     * process name and arguments. */
309    totsize = read(fd, path, sizeof(path));
310    close(fd);
311    if (totsize <= 0)
312        return;
313    path[totsize - 1] = '\0';
314
315    /* Construct the process name without arguments. */
316    if (cmdname) {
317        *cmdname = strdup(path);
318    }
319
320    /* Construct the arguments for client process. */
321    if (cmdargs) {
322        int cmdsize = strlen(path) + 1;
323        int argsize = totsize - cmdsize;
324        char *args = NULL;
325
326        if (argsize > 0)
327            args = malloc(argsize);
328        if (args) {
329            int i = 0;
330
331            for (i = 0; i < (argsize - 1); ++i) {
332                const char c = path[cmdsize + i];
333
334                args[i] = (c == '\0') ? ' ' : c;
335            }
336            args[argsize - 1] = '\0';
337            *cmdargs = args;
338        }
339    }
340    return;
341#endif
342
343#ifdef __sun                    /* Solaris */
344  fallback:
345    /* Solaris prior to 11.3.5 does not support /proc/pid/cmdline, but
346     * makes information similar to what ps shows available in a binary
347     * structure in the /proc/pid/psinfo file. */
348    if (snprintf(path, sizeof(path), "/proc/%d/psinfo", pid) < 0)
349        return;
350    fd = open(path, O_RDONLY);
351    if (fd < 0) {
352        ErrorF("Failed to open %s: %s\n", path, strerror(errno));
353        return;
354    }
355    else {
356        psinfo_t psinfo = { 0 };
357        char *sp;
358
359        totsize = read(fd, &psinfo, sizeof(psinfo_t));
360        close(fd);
361        if (totsize <= 0)
362            return;
363
364        /* pr_psargs is the first PRARGSZ (80) characters of the command
365         * line string - assume up to the first space is the command name,
366         * since it's not delimited.   While there is also pr_fname, that's
367         * more limited, giving only the first 16 chars of the basename of
368         * the file that was exec'ed, thus cutting off many long gnome
369         * command names, or returning "isapython2.6" for all python scripts.
370         */
371        psinfo.pr_psargs[PRARGSZ - 1] = '\0';
372        sp = strchr(psinfo.pr_psargs, ' ');
373        if (sp)
374            *sp++ = '\0';
375
376        if (cmdname)
377            *cmdname = strdup(psinfo.pr_psargs);
378
379        if (cmdargs && sp)
380            *cmdargs = strdup(sp);
381    }
382#endif
383}
384
385/**
386 * Called when a new client connects. Allocates client ID information.
387 *
388 * @param[in] client Recently connected client.
389 */
390void
391ReserveClientIds(struct _Client *client)
392{
393#ifdef CLIENTIDS
394    if (client == NullClient)
395        return;
396
397    assert(!client->clientIds);
398    client->clientIds = calloc(1, sizeof(ClientIdRec));
399    if (!client->clientIds)
400        return;
401
402    client->clientIds->pid = DetermineClientPid(client);
403    if (client->clientIds->pid != -1)
404        DetermineClientCmd(client->clientIds->pid, &client->clientIds->cmdname,
405                           &client->clientIds->cmdargs);
406
407    DebugF("client(%lx): Reserved pid(%d).\n",
408           (unsigned long) client->clientAsMask, client->clientIds->pid);
409    DebugF("client(%lx): Reserved cmdname(%s) and cmdargs(%s).\n",
410           (unsigned long) client->clientAsMask,
411           client->clientIds->cmdname ? client->clientIds->cmdname : "NULL",
412           client->clientIds->cmdargs ? client->clientIds->cmdargs : "NULL");
413#endif                          /* CLIENTIDS */
414}
415
416/**
417 * Called when an existing client disconnects. Frees client ID
418 * information.
419 *
420 * @param[in] client Recently disconnected client.
421 */
422void
423ReleaseClientIds(struct _Client *client)
424{
425#ifdef CLIENTIDS
426    if (client == NullClient)
427        return;
428
429    if (!client->clientIds)
430        return;
431
432    DebugF("client(%lx): Released pid(%d).\n",
433           (unsigned long) client->clientAsMask, client->clientIds->pid);
434    DebugF("client(%lx): Released cmdline(%s) and cmdargs(%s).\n",
435           (unsigned long) client->clientAsMask,
436           client->clientIds->cmdname ? client->clientIds->cmdname : "NULL",
437           client->clientIds->cmdargs ? client->clientIds->cmdargs : "NULL");
438
439    free((void *) client->clientIds->cmdname);  /* const char * */
440    free((void *) client->clientIds->cmdargs);  /* const char * */
441    free(client->clientIds);
442    client->clientIds = NULL;
443#endif                          /* CLIENTIDS */
444}
445
446/**
447 * Get cached PID of a client.
448 *
449 * param[in] client Client whose PID has been already cached.
450 *
451 * @return Cached client PID. Error (-1) if called:
452 *         - before ClientStateInitial client state notification
453 *         - after ClientStateGone client state notification
454 *         - for remote clients
455 *
456 * @see DetermineClientPid
457 */
458pid_t
459GetClientPid(struct _Client *client)
460{
461    if (client == NullClient)
462        return -1;
463
464    if (!client->clientIds)
465        return -1;
466
467    return client->clientIds->pid;
468}
469
470/**
471 * Get cached command name string of a client.
472 *
473 * param[in] client Client whose command line string has been already
474 *                  cached.
475 *
476 * @return Cached client command name. Error (NULL) if called:
477 *         - before ClientStateInitial client state notification
478 *         - after ClientStateGone client state notification
479 *         - for remote clients
480 *         - on OS that doesn't support mapping of PID to command line
481 *
482 * @see DetermineClientCmd
483 */
484const char *
485GetClientCmdName(struct _Client *client)
486{
487    if (client == NullClient)
488        return NULL;
489
490    if (!client->clientIds)
491        return NULL;
492
493    return client->clientIds->cmdname;
494}
495
496/**
497 * Get cached command arguments string of a client.
498 *
499 * param[in] client Client whose command line string has been already
500 *                  cached.
501 *
502 * @return Cached client command arguments. Error (NULL) if called:
503 *         - before ClientStateInitial client state notification
504 *         - after ClientStateGone client state notification
505 *         - for remote clients
506 *         - on OS that doesn't support mapping of PID to command line
507 *
508 * @see DetermineClientCmd
509 */
510const char *
511GetClientCmdArgs(struct _Client *client)
512{
513    if (client == NullClient)
514        return NULL;
515
516    if (!client->clientIds)
517        return NULL;
518
519    return client->clientIds->cmdargs;
520}
521