2 * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved.
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
25 /* Implementation of Display Manager (dtlogin/gdm/xdm/etc.) to X server
26 * communication pipe. The Display Manager process will start
27 * the X window server at system boot time before any user
28 * has logged into the system. The X server is by default
29 * started as the root UID "0".
31 * At login time the Xserver local communication pipe is provided
32 * by the Xserver for user specific configuration data supplied
33 * by the display manager. It notifies the Xserver it needs to change
34 * over to the user's credentials (UID, GID, GID_LIST) and
35 * also switch CWD (current working directory) of to match
36 * the user's CWD home.
38 * When shutting down, the Xserver restores it's original uid/gid as
39 * needed by the cleanup/teardown actions in several drivers.
41 * For the original definition, see Sun ASARC case 1995/390
44 #ifdef HAVE_DIX_CONFIG_H
45 #include <dix-config.h>
49 #include <sys/param.h>
50 #include <sys/socket.h>
51 #include <netinet/in.h>
55 #include <sys/types.h>
61 #include <X11/Xpoll.h>
64 #include "dixstruct.h"
71 #include "scrnintstr.h"
74 #define DTLOGIN_PATH "/var/dt/sdtlogin"
78 int xf86ConsoleFd
= -1;
80 /* in Xserver/os/auth.c */
81 extern const char *GetAuthFilename(void);
83 /* Data about the user we need to switch to */
86 gid_t gid
; /* Primary group */
90 gid_t groupids
[NGROUPS_UMAX
]; /* Supplementary groups */
93 /* Data passed to block/wakeup handlers */
96 char * pipename
; /* path to pipe */
97 char * buf
; /* contains characters to be processed */
98 size_t bufsize
; /* size allocated for buf */
99 struct dmuser user
; /* target user, to switch to on login */
103 /* Data stored in screen privates */
104 struct dmScreenPriv
{
105 CloseScreenProcPtr CloseScreen
;
107 static DevPrivateKeyRec dmScreenKeyRec
;
108 #define dmScreenKey (&dmScreenKeyRec)
109 static struct dmdata
*dmHandlerData
;
110 static struct dmuser originalUser
; /* user to switch back to in CloseDown */
112 static Bool
DtloginCloseScreen(ScreenPtr pScreen
);
113 static int dtlogin_create_pipe(int, struct dmdata
*);
114 static void dtlogin_pipe_notify(int, int, void *);
115 static void dtlogin_receive_packet(struct dmdata
*);
116 static int dtlogin_parse_packet(struct dmdata
*, char *);
117 static void dtlogin_process(struct dmuser
*user
, int user_logged_in
);
118 static void dtlogin_close_pipe(struct dmdata
*);
120 #define DtloginError(fmt, ...) \
121 LogMessageVerb(X_ERROR, 1, "dtlogin: " fmt ": %s\n", __VA_ARGS__, \
124 #define DtloginInfo(fmt, ...) \
125 LogMessageVerb(X_INFO, 5, "dtlogin: " fmt, __VA_ARGS__)
128 * initialize DTLOGIN: create pipe; set handlers.
129 * Called from main loop in os/main.c
135 int displayNumber
= 0;
138 if (serverGeneration
!= 1) return;
140 originalUser
.uid
= geteuid();
141 originalUser
.gid
= getegid();
142 getgroups(NGROUPS_UMAX
, originalUser
.groupids
);
143 originalUser
.homedir
= getcwd(NULL
, 0);
144 originalUser
.projid
= getprojid();
146 if (getuid() != 0) return;
148 dmd
= calloc(1, sizeof(struct dmdata
));
150 DtloginError("Failed to allocate %d bytes for display manager pipe",
151 (int) sizeof(struct dmdata
));
155 dmd
->user
.uid
= (uid_t
) -1;
156 dmd
->user
.gid
= (gid_t
) -1;
157 dmd
->user
.projid
= (projid_t
) -1;
159 displayNumber
= atoi(display
); /* Assigned in dix/main.c */
161 dmd
->pipeFD
= dtlogin_create_pipe(displayNumber
, dmd
);
163 if (dmd
->pipeFD
== -1) {
170 SetNotifyFd(dmd
->pipeFD
, dtlogin_pipe_notify
, X_NOTIFY_READ
, dmd
);
174 * cleanup dtlogin pipe at exit if still running, reset privs back to
175 * root as needed for various cleanup tasks.
176 * Called from main loop in os/main.c & CloseScreen wrappers
179 DtloginCloseDown(void)
181 if (geteuid() != originalUser
.uid
) {
182 dtlogin_process(&originalUser
, 0);
185 if (dmHandlerData
!= NULL
) {
186 dtlogin_close_pipe(dmHandlerData
);
191 DtloginCloseScreen (ScreenPtr pScreen
)
193 struct dmScreenPriv
*pScreenPriv
;
197 /* Unwrap CloseScreen and call down to further levels */
198 pScreenPriv
= (struct dmScreenPriv
*)
199 dixGetPrivate(&pScreen
->devPrivates
, dmScreenKey
);
200 dixSetPrivate(&pScreen
->devPrivates
, dmScreenKey
, NULL
);
202 pScreen
->CloseScreen
= pScreenPriv
->CloseScreen
;
205 return (*pScreen
->CloseScreen
) (pScreen
);
209 dtlogin_create_pipe(int port
, struct dmdata
*dmd
)
215 if (stat(DTLOGIN_PATH
, &statbuf
) == -1) {
216 if (mkdir(DTLOGIN_PATH
, 0700) == -1) {
217 DtloginError("Cannot create %s directory for display "
218 "manager pipe", DTLOGIN_PATH
);
221 } else if (!S_ISDIR(statbuf
.st_mode
)) {
222 DtloginError("Cannot create display manager pipe: "
223 "%s is not a directory", DTLOGIN_PATH
);
227 snprintf(pipename
, sizeof(pipename
), "%s/%d", DTLOGIN_PATH
, port
);
229 /* Workaround: cleanup pipe if it has not been removed. */
230 if (stat(pipename
, &statbuf
) == 0) {
231 DtloginInfo("Cleanup leftover display manager pipe: %s\n", pipename
);
235 if (mkfifo(pipename
, S_IRUSR
| S_IWUSR
) < 0) {
236 DtloginError("Cannot create display manager pipe %s", pipename
);
240 if ((pipeFD
= open(pipename
, O_RDWR
| O_NONBLOCK
)) < 0) {
241 DtloginError("Cannot open display manager pipe %s", pipename
);
246 dmd
->pipename
= xstrdup(pipename
);
248 /* To make sure root has rw permissions. */
249 (void) fchmod(pipeFD
, 0600);
251 DtloginInfo("Created display manager pipe: %s\n", dmd
->pipename
);
255 static void dtlogin_close_pipe(struct dmdata
*dmd
)
257 DtloginInfo("Closing display manager pipe: %s\n", dmd
->pipename
);
258 RemoveNotifyFd(dmd
->pipeFD
);
260 remove(dmd
->pipename
);
263 free(dmd
->user
.homedir
);
266 if (dmHandlerData
== dmd
) {
267 dmHandlerData
= NULL
;
271 static void dtlogin_pipe_notify(int fd
, int events
, void *data
)
273 struct dmdata
*dmd
= (struct dmdata
*) data
;
275 assert(fd
== dmd
->pipeFD
);
277 if (events
& X_NOTIFY_READ
) {
278 dtlogin_receive_packet(dmd
);
279 /* dmd may have been freed in dtlogin_receive_packet,
280 do not use after this point */
282 if (events
& X_NOTIFY_ERROR
) {
283 DtloginError("Lost connection on %s (fd %d)",
284 dmd
->pipename
, dmd
->pipeFD
);
286 DtloginError("Unexpected notification event 0x%x", events
);
288 dtlogin_close_pipe(dmd
);
289 /* dmd has been freed in dtlogin_close_pipe,
290 do not use after this point */
295 dtlogin_receive_packet(struct dmdata
*dmd
)
302 if (dmd
->buf
== NULL
) {
303 dmd
->buf
= malloc(BUFLEN
);
304 if (dmd
->buf
!= NULL
) {
306 dmd
->bufsize
= BUFLEN
;
308 DtloginError("Failed to allocate display manager pipe buffer of %d",
310 dtlogin_close_pipe(dmd
);
315 /* Read data from pipe and split into tokens, buffering the rest */
317 bufLen
= strlen(dmd
->buf
);
320 * Realloc only if buf has filled up and we don't have a record
321 * delimiter yet. Keep track of alloced size.
323 if (bufLen
> (dmd
->bufsize
/2)) {
324 char *newbuf
= realloc(dmd
->buf
, dmd
->bufsize
+ BUFLEN
);
325 if (newbuf
!= NULL
) {
327 dmd
->bufsize
+= BUFLEN
;
331 nbRead
= read(dmd
->pipeFD
, dmd
->buf
+ bufLen
,
332 dmd
->bufsize
- bufLen
- 1);
335 if (errno
== EINTR
) {
337 } else if (errno
== EAGAIN
) {
338 return; /* return to WaitFor, wait for select() */
340 DtloginError("Unexpected error on display manager pipe %s",
342 dtlogin_close_pipe(dmd
);
347 if (nbRead
== 0) { /* End of file */
348 DtloginError("Unexpected EOF on display manager pipe %s",
350 dtlogin_close_pipe(dmd
);
355 dmd
->buf
[bufLen
] = '\0';
357 DtloginInfo("Data buffer: %s\n", dmd
->buf
);
360 while ((n
= strchr(p
, ';')) != NULL
) { /* Next complete packet */
362 DtloginInfo("Next packet: %s\n", p
);
363 done
= dtlogin_parse_packet(dmd
, p
);
370 /* save the rest for the next iteration */
371 if (!done
&& (p
< (dmd
->buf
+ bufLen
))) {
372 DtloginInfo("Left over: %s\n", p
);
380 /* Parse data from packet
383 * UID="xxx" GID="yyy";
384 * G_LIST_ID="aaa" G_LIST_ID="bbb" G_LIST_ID="ccc";
385 * HOME="/nn/mmm/ooo" EOF="";
388 dtlogin_parse_packet(struct dmdata
*dmd
, char *s
)
392 while (s
< (dmd
->buf
+ dmd
->bufsize
)) {
393 /* format is key="value" - split into key & value pair */
395 for (k
= s
; (*k
!= '\0') && isspace(*k
); k
++) {
396 /* Skip over whitespace */
406 DtloginInfo("Malformed key \'%s\'\n", k
);
410 *p
= '\0'; /* end of key string */
414 DtloginInfo("Bad delimiter at \'%s\'\n", p
);
418 v
= p
+ 1; /* start of value string */
423 DtloginInfo("Missing quote after value \'%s\'\n", v
);
427 *p
= '\0'; /* end of value string */
429 s
= p
+ 1; /* start of next pair */
431 DtloginInfo("Received key \"%s\" => value \"%s\"\n", k
, v
);
433 /* Found key & value, now process & put into dmd */
434 if (strcmp(k
, "EOF") == 0) {
435 /* End of transmission, process & close */
436 dtlogin_process(&(dmd
->user
), 1);
439 else if (strcmp(k
, "HOME") == 0) {
440 dmd
->user
.homedir
= xstrdup(v
);
442 else if ( (strcmp(k
, "UID") == 0) || (strcmp(k
, "GID") == 0)
443 || (strcmp(k
, "G_LIST_ID") == 0) ) {
444 /* Value is numeric, convert to int */
448 val
= strtol(v
, NULL
, 10);
450 if ((val
== 0) && (strcmp(v
, "0") != 0)) {
451 /* strtol couldn't parse the results */
452 DtloginInfo("Invalid number \"%s\"\n", v
);
456 if ( ((val
== LONG_MAX
) || (val
== LONG_MIN
))
457 && (errno
== ERANGE
) ) {
458 /* Value out of range */
459 DtloginInfo("Out of range number \"%s\"\n", v
);
463 if (strcmp(k
, "UID") == 0) {
464 dmd
->user
.uid
= (uid_t
) val
;
466 else if (strcmp(k
, "GID") == 0) {
467 dmd
->user
.gid
= (gid_t
) val
;
469 else if (strcmp(k
, "G_LIST_ID") == 0) {
470 if (dmd
->user
.groupid_cnt
< NGROUPS_UMAX
) {
471 dmd
->user
.groupids
[dmd
->user
.groupid_cnt
++] = (gid_t
) val
;
476 DtloginInfo("Unrecognized key \"%s\"\n", k
);
485 dtlogin_process(struct dmuser
*user
, int user_logged_in
)
488 char proj_buf
[PROJECT_BUFSZ
];
489 struct passwd
*ppasswd
;
490 const char *auth_file
= NULL
;
492 if (geteuid() != 0) { /* reset privs back to root */
493 if (seteuid(0) < 0) {
494 DtloginError("Error in resetting euid to %d", 0);
498 auth_file
= GetAuthFilename();
501 if (chown(auth_file
, user
->uid
, user
->gid
) < 0)
502 DtloginError("Error in changing owner of %s to %d",
503 auth_file
, user
->uid
);
506 /* This gid dance is necessary in order to make sure
507 our "saved-set-gid" is 0 so that we can regain gid
508 0 when necessary for priocntl & power management.
509 The first step sets rgid to the user's gid and
510 makes the egid & saved-gid be 0. The second then
511 sets the egid to the users gid, but leaves the
514 if (user
->gid
!= (gid_t
) -1) {
515 DtloginInfo("Setting gid to %d\n", user
->gid
);
517 if (setregid(user
->gid
, 0) < 0)
518 DtloginError("Error in setting regid to %d\n", user
->gid
);
520 if (setegid(user
->gid
) < 0)
521 DtloginError("Error in setting egid to %d\n", user
->gid
);
524 if (user
->groupid_cnt
>= 0) {
525 if (setgroups(user
->groupid_cnt
, user
->groupids
) < 0)
526 DtloginError("Error in setting supplemental (%d) groups",
532 * BUG: 4462531: Set project ID for Xserver
533 * Get user name and default project.
534 * Set before the uid value is set.
536 if (user
->projid
!= (uid_t
) -1) {
537 if (settaskid(user
->projid
, TASK_NORMAL
) == (taskid_t
) -1) {
538 DtloginError("Error in setting project id to %d", user
->projid
);
540 } else if (user
->uid
!= (uid_t
) -1) {
541 ppasswd
= getpwuid(user
->uid
);
543 if (ppasswd
== NULL
) {
544 DtloginError("Error in getting user name for %d", user
->uid
);
546 if (strcmp(ppasswd
->pw_name
, "gdm") == 0) {
547 /* Avoid trying to screenlock the login screen with a
548 non-existent password for the gdm user */
551 if (getdefaultproj(ppasswd
->pw_name
, &proj
,
552 (void *)&proj_buf
, PROJECT_BUFSZ
) == NULL
) {
553 DtloginError("Error in getting project id for %s",
556 DtloginInfo("Setting project to %s\n", proj
.pj_name
);
558 if (setproject(proj
.pj_name
, ppasswd
->pw_name
,
559 TASK_NORMAL
) == -1) {
560 DtloginError("Error in setting project to %s",
567 if (user
->uid
!= (uid_t
) -1) {
568 DtloginInfo("Setting uid to %d\n", user
->uid
);
570 if (setreuid(user
->uid
, -1) < 0)
571 DtloginError("Error in setting ruid to %d", user
->uid
);
573 if (setreuid(-1, user
->uid
) < 0)
574 DtloginError("Error in setting euid to %d", user
->uid
);
576 /* Wrap closeScreen to allow resetting uid on closedown */
577 if ((user
->uid
!= 0) && (user
!= &originalUser
)) {
580 if (dixRegisterPrivateKey(dmScreenKey
, PRIVATE_SCREEN
, 0)) {
581 for (i
= 0; i
< screenInfo
.numScreens
; i
++)
583 ScreenPtr pScreen
= screenInfo
.screens
[i
];
584 struct dmScreenPriv
*pScreenPriv
= (struct dmScreenPriv
*)
585 dixLookupPrivate(&pScreen
->devPrivates
, dmScreenKey
);
587 if (pScreenPriv
== NULL
) {
588 pScreenPriv
= calloc(1, sizeof(struct dmScreenPriv
));
590 if (pScreenPriv
!= NULL
) {
591 pScreenPriv
->CloseScreen
= pScreen
->CloseScreen
;
592 pScreen
->CloseScreen
= DtloginCloseScreen
;
594 dixSetPrivate(&pScreen
->devPrivates
, dmScreenKey
,
597 DtloginError("Failed to allocate %d bytes"
598 " for screen privates",
599 (int) sizeof(struct dmScreenPriv
));
604 DtloginError("Failed to register screen private %s",
605 "for uid reset info");
610 if (user
->homedir
!= NULL
) {
613 if (asprintf(&env_str
, "HOME=%s", user
->homedir
) == -1) {
614 DtloginError("Not enough memory to setenv HOME=%s", user
->homedir
);
616 DtloginInfo("Setting %s\n",env_str
);
618 if (putenv(env_str
) < 0)
619 DtloginError("Failed to setenv %s", env_str
);
622 if (chdir(user
->homedir
) < 0)
623 DtloginError("Error in changing working directory to %s",
627 /* Inform the kernel whether a user has logged in on this VT device */
628 if (xf86ConsoleFd
!= -1)
629 ioctl(xf86ConsoleFd
, VT_SETDISPLOGIN
, user_logged_in
);