diff --git a/common/os_calls.c b/common/os_calls.c index 2a997bbb16..c2acae973d 100644 --- a/common/os_calls.c +++ b/common/os_calls.c @@ -2740,7 +2740,10 @@ g_create_path(const char *path) if (!g_directory_exist(copypath)) { - if (!g_create_dir(copypath)) + // Create and check again to avoid a race with another + // process making the same traversal + (void)g_create_dir(copypath); + if (!g_directory_exist(copypath)) { status = 0; break; diff --git a/common/string_calls.c b/common/string_calls.c index 99ec4b402b..e4479db8b7 100644 --- a/common/string_calls.c +++ b/common/string_calls.c @@ -162,8 +162,8 @@ g_text2bool(const char *s) } /*****************************************************************************/ -int -g_get_display_num_from_display(const char *display_text) +static int +get_display_num_from_display(const char *display_text) { int rv = -1; const char *p; @@ -191,6 +191,42 @@ g_get_display_num_from_display(const char *display_text) return rv; } +/*****************************************************************************/ +int +g_get_display_string(char buff[], unsigned int bufflen) +{ + const char *str; + const char *p = NULL; + int rv = 0; + + if ((str = g_getenv("WAYLAND_DISPLAY")) != NULL) + { + // Return the unqualified part of the name + p = strrchr(str, '/'); + p = (p != NULL) ? (p + 1) : str; + strlcpy(buff, p, bufflen); + } + else if ((str = g_getenv("DISPLAY")) != NULL) + { + int n = get_display_num_from_display(str); + + if (n >= 0) + { + g_snprintf(buff, bufflen, ":%d", n); + } + else + { + rv = -1; + } + } + else + { + rv = -1; + } + + return rv; +} + /*****************************************************************************/ /* returns length of text */ int diff --git a/common/string_calls.h b/common/string_calls.h index 77e26f3d4b..92e409f492 100644 --- a/common/string_calls.h +++ b/common/string_calls.h @@ -190,14 +190,17 @@ char * g_bytes_to_hexdump(const char *src, int len); /** - * Extracts the display number from an X11 display string + * Extracts the display string from either DISPLAY or WAYLAND_DISPLAY * - * @param Display string (i.e. g_getenv("DISPLAY")) + * @param buff Buffer for result + * @param bufflen Length for result * - * @result <0 if the string could not be parsed, or >=0 for a display number + * Result will be (e.g.) ":10" for X11 or "wayland-1" for Wayland + * + * @result != 0 if the string could not be found or parsed */ int -g_get_display_num_from_display(const char *display_text); +g_get_display_string(char buff[], unsigned int bufflen); /** * Converts a bitmask into a string for output purposes diff --git a/common/xrdp_sockets.h b/common/xrdp_sockets.h index e98b7657fd..5bc51d5a78 100644 --- a/common/xrdp_sockets.h +++ b/common/xrdp_sockets.h @@ -45,13 +45,13 @@ #define SCP_LISTEN_PORT_BASE_STR "sesman.socket" /* names of socket files within XRDP_SOCKET_PATH, qualified by - * display number */ -#define XRDP_CHANSRV_BASE_STR "xrdp_chansrv_socket_%d" -#define CHANSRV_PORT_OUT_BASE_STR "xrdp_chansrv_audio_out_socket_%d" -#define CHANSRV_PORT_IN_BASE_STR "xrdp_chansrv_audio_in_socket_%d" -#define CHANSRV_API_BASE_STR "xrdpapi_%d" -#define XRDP_X11RDP_BASE_STR "xrdp_display_%d" -#define XRDP_DISCONNECT_BASE_STR "xrdp_disconnect_display_%d" + * display name (with leading colon stripped) */ +#define XRDP_CHANSRV_BASE_STR "xrdp_chansrv_socket_%s" +#define CHANSRV_PORT_OUT_BASE_STR "xrdp_chansrv_audio_out_socket_%s" +#define CHANSRV_PORT_IN_BASE_STR "xrdp_chansrv_audio_in_socket_%s" +#define CHANSRV_API_BASE_STR "xrdpapi_%s" +#define XRDP_X11RDP_BASE_STR "xrdp_display_%s" +#define XRDP_DISCONNECT_BASE_STR "xrdp_disconnect_display_%s" /* fullpath declarations */ #define XRDP_CHANSRV_STR XRDP_SOCKET_PATH "/" XRDP_CHANSRV_BASE_STR @@ -67,4 +67,11 @@ /* fullpath to an X11 display socket */ #define X11_UNIX_SOCKET_STR X11_UNIX_SOCKET_DIRECTORY "/X%d" +/* Use this to strip a colon from an X11 display name. + * + * This is for compatibility with previous versions of xrdp, which + * used X11 display numbers only. Using this macro results in the same + * behaviour for X11 sessions, but no change for Wayland sessions */ +#define STRIP_COLON(dname) (((dname)[0] == ':') ? (dname) + 1 : (dname)) + #endif diff --git a/configure.ac b/configure.ac index f4b3e6c867..acf9f296a0 100644 --- a/configure.ac +++ b/configure.ac @@ -675,6 +675,7 @@ AC_CONFIG_FILES([ sesman/chansrv/Makefile sesman/Makefile sesman/sesexec/Makefile + sesman/sesexec/wayland/Makefile sesman/tools/Makefile tests/Makefile tests/common/Makefile diff --git a/docs/man/sesman.ini.5.in b/docs/man/sesman.ini.5.in index a92e7b2fee..ba62ac403d 100644 --- a/docs/man/sesman.ini.5.in +++ b/docs/man/sesman.ini.5.in @@ -395,6 +395,19 @@ Multiple \fIparam\fR lines are supported. This first line specifies the path to the X11 server executable. Following lines specify command line arguments passed to the X11 server. +.SH "LABWC" +Following parameters can be used in the \fB[Labwc]\fR section. + +.TP +\fBLabwcExe\fR=\fI/path/to/executable\fR +Defaults to \fIlabwc\fR. Specify the path to a labwc executable, if +the executable you wish to use is not on the standard PATH. + +.TP +\fBWayvncExe\fR=\fI/path/to/executable\fR +Defaults to \fIwayvnc\fR. Specify the path to a wayvnc executable, if +the executable you wish to use is not on the standard PATH. + .SH "CHANSRV" Following parameters can be used in the \fB[Chansrv]\fR section. @@ -408,8 +421,7 @@ If first character is not a '/', this is relative to $HOME. The following substitutions are made in this string:- %U - Username %u - Numeric UID - %d - Numeric display number (ex 10) - %D - Display environment variable (ex :10.0) + %d - Display identifier (e.g. "10" for X11, or "wayland-1" for Wayland) %% - Percent character .HP 3 1) The directory path permissions MUST be configured correctly by diff --git a/docs/man/xrdp.ini.5.in b/docs/man/xrdp.ini.5.in index cd6f8c42ee..3bab18d459 100644 --- a/docs/man/xrdp.ini.5.in +++ b/docs/man/xrdp.ini.5.in @@ -400,8 +400,11 @@ values supported for a particular release of \fBxrdp\fR(8) are documented in \fBcode\fR=\fI\fR|\fI0\fR Specifies the session type. \fI0\fR is Xvnc over TCP, \fI1\fR is Xvnc over a UNIX domain socket, -and \fI20\fR is Xorg with xorgxrdp modules. +\fI20\fR is Xorg with xorgxrdp modules, +\fI1000\fR is labwc (Wayland), and +\fI1001\fR is labwc (Wayland) over VNC The default is \fI0\fR on non-FIPS systems, and \fI1\fR on FIPS systems. +Wayland is currently experimental. .TP \fBchansrvport\fR=\fBDISPLAY(\fR\fIn\fR\fB)\fR|\fBDISPLAY(\fR\fIn,u\fR\fB)\fR||\fI/path/to/domain-socket\fR Asks xrdp to connect to a manually started \fBxrdp-chansrv\fR instance. diff --git a/libipm/eicp.c b/libipm/eicp.c index efb021c42f..44715a10e5 100644 --- a/libipm/eicp.c +++ b/libipm/eicp.c @@ -273,7 +273,7 @@ eicp_send_logout_request(struct trans *trans) int eicp_send_create_session_request(struct trans *trans, - unsigned int display, + int x11_display, enum scp_session_type type, unsigned short width, unsigned short height, @@ -284,8 +284,8 @@ eicp_send_create_session_request(struct trans *trans, return libipm_msg_out_simple_send( trans, (int)E_EICP_CREATE_SESSION_REQUEST, - "uyqqyss", - display, + "iyqqyss", + x11_display, type, width, height, @@ -298,7 +298,7 @@ eicp_send_create_session_request(struct trans *trans, int eicp_get_create_session_request(struct trans *trans, - unsigned int *display, + int *x11_display, enum scp_session_type *type, unsigned short *width, unsigned short *height, @@ -307,7 +307,7 @@ eicp_get_create_session_request(struct trans *trans, const char **directory) { /* Intermediate values */ - uint32_t i_display; + int32_t i_x11_display; uint8_t i_type; uint16_t i_width; uint16_t i_height; @@ -315,8 +315,8 @@ eicp_get_create_session_request(struct trans *trans, int rv = libipm_msg_in_parse( trans, - "uyqqyss", - &i_display, + "iyqqyss", + &i_x11_display, &i_type, &i_width, &i_height, @@ -326,7 +326,7 @@ eicp_get_create_session_request(struct trans *trans, if (rv == 0) { - *display = i_display; + *x11_display = i_x11_display; *type = (enum scp_session_type)i_type; *width = i_width; *height = i_height; @@ -341,12 +341,13 @@ eicp_get_create_session_request(struct trans *trans, int eicp_send_create_session_response(struct trans *trans, enum scp_screate_status status, + const char *display, const struct guid *guid) { struct libipm_fsb guid_descriptor = { (void *)guid, sizeof(*guid) }; return libipm_msg_out_simple_send( trans, (int)E_EICP_CREATE_SESSION_RESPONSE, - "iB", status, &guid_descriptor); + "isB", status, display, &guid_descriptor); } /*****************************************************************************/ @@ -354,6 +355,7 @@ eicp_send_create_session_response(struct trans *trans, int eicp_get_create_session_response(struct trans *trans, enum scp_screate_status *status, + const char **display, struct guid *guid) { /* Intermediate values */ @@ -362,14 +364,19 @@ eicp_get_create_session_response(struct trans *trans, const struct libipm_fsb guid_descriptor = { (void *)guid, sizeof(*guid) }; int rv = libipm_msg_in_parse( trans, - "iB", + "isB", &i_status, + display, &guid_descriptor); if (rv == 0) { *status = (enum scp_screate_status)i_status; } + else + { + *display = ""; + } return rv; } diff --git a/libipm/eicp.h b/libipm/eicp.h index 392a69070c..7266a94159 100644 --- a/libipm/eicp.h +++ b/libipm/eicp.h @@ -272,7 +272,7 @@ eicp_send_logout_request(struct trans *trans); * Send an E_EICP_CREATE_SESSION_REQUEST (sesman) * * @param trans EICP transport - * @param display X display number to use + * @param x11_display X11 display number to use, or -1 if not X11 * @param type Session type * @param width Initial session width * @param height Initial session height @@ -292,7 +292,7 @@ eicp_send_logout_request(struct trans *trans); */ int eicp_send_create_session_request(struct trans *trans, - unsigned int display, + int x11_display, enum scp_session_type type, unsigned short width, unsigned short height, @@ -305,7 +305,7 @@ eicp_send_create_session_request(struct trans *trans, * Parse an incoming E_EICP_CREATE_SESSION_REQUEST (sesexec) * * @param trans EICP transport - * @param[out] display X display number to use + * @param[out] x11_display X display number to use * @param[out] type Session type * @param[out] width Initial session width * @param[out] height Initial session height @@ -319,7 +319,7 @@ eicp_send_create_session_request(struct trans *trans, */ int eicp_get_create_session_request(struct trans *trans, - unsigned int *display, + int *x11_display, enum scp_session_type *type, unsigned short *width, unsigned short *height, @@ -336,12 +336,15 @@ eicp_get_create_session_request(struct trans *trans, * * @param trans EICP transport * @param status Status of creation request + * @param display Display name, either (e.g) ":n" (X11) or + * "wayland-n" (Wayland) * @param guid GUID of session * @return != 0 for error */ int eicp_send_create_session_response(struct trans *trans, enum scp_screate_status status, + const char *display, const struct guid *guid); @@ -354,12 +357,15 @@ eicp_send_create_session_response(struct trans *trans, * * @param trans EICP transport * @param[out] status Status of creation request + * @param display Display name, either (e.g) ":n" (X11) or + * "wayland-n" (Wayland) * @param[out] guid GUID of session * @return != 0 for error */ int eicp_get_create_session_response(struct trans *trans, enum scp_screate_status *status, + const char **display, struct guid *guid); #endif /* EICP_H */ diff --git a/libipm/ercp.c b/libipm/ercp.c index a96f517097..62bee9d978 100644 --- a/libipm/ercp.c +++ b/libipm/ercp.c @@ -147,7 +147,7 @@ ercp_msg_in_reset(struct trans *trans) int ercp_send_session_announce_event(struct trans *trans, - unsigned int display, + const char *display, uid_t uid, enum scp_session_type type, unsigned short start_width, @@ -162,7 +162,7 @@ ercp_send_session_announce_event(struct trans *trans, return libipm_msg_out_simple_send( trans, (int)E_ERCP_SESSION_ANNOUNCE_EVENT, - "uiyqqyBsx", + "siyqqyBsx", display, uid, type, @@ -178,7 +178,7 @@ ercp_send_session_announce_event(struct trans *trans, int ercp_get_session_announce_event(struct trans *trans, - unsigned int *display, + const char **display, uid_t *uid, enum scp_session_type *type, unsigned short *start_width, @@ -189,7 +189,6 @@ ercp_get_session_announce_event(struct trans *trans, time_t *start_time) { /* Intermediate values */ - uint32_t i_display; int32_t i_uid; uint8_t i_type; uint16_t i_width; @@ -201,8 +200,8 @@ ercp_get_session_announce_event(struct trans *trans, int rv = libipm_msg_in_parse( trans, - "uiyqqyBsx", - &i_display, + "siyqqyBsx", + display, &i_uid, &i_type, &i_width, @@ -214,7 +213,6 @@ ercp_get_session_announce_event(struct trans *trans, if (rv == 0) { - *display = i_display; *uid = (uid_t)i_uid; *type = (enum scp_session_type)i_type; *start_width = i_width; @@ -222,6 +220,10 @@ ercp_get_session_announce_event(struct trans *trans, *bpp = i_bpp; *start_time = (time_t)i_start_time; } + else + { + *display = ""; + } return rv; } diff --git a/libipm/ercp.h b/libipm/ercp.h index 08f6d53d9a..ac5886a4da 100644 --- a/libipm/ercp.h +++ b/libipm/ercp.h @@ -176,7 +176,7 @@ ercp_msg_in_reset(struct trans *trans); */ int ercp_send_session_announce_event(struct trans *trans, - unsigned int display, + const char *display, uid_t uid, enum scp_session_type type, unsigned short start_width, @@ -206,7 +206,7 @@ ercp_send_session_announce_event(struct trans *trans, */ int ercp_get_session_announce_event(struct trans *trans, - unsigned int *display, + const char **display, uid_t *uid, enum scp_session_type *type, unsigned short *start_width, diff --git a/libipm/scp.c b/libipm/scp.c index df99241731..ac3d337953 100644 --- a/libipm/scp.c +++ b/libipm/scp.c @@ -477,7 +477,7 @@ scp_get_create_session_request(struct trans *trans, int scp_send_create_session_response(struct trans *trans, enum scp_screate_status status, - int display, + const char *display, const struct guid *guid) { struct libipm_fsb guid_descriptor = { (void *)guid, sizeof(*guid) }; @@ -485,7 +485,7 @@ scp_send_create_session_response(struct trans *trans, return libipm_msg_out_simple_send( trans, (int)E_SCP_CREATE_SESSION_RESPONSE, - "iiB", + "isB", status, display, &guid_descriptor); @@ -496,25 +496,27 @@ scp_send_create_session_response(struct trans *trans, int scp_get_create_session_response(struct trans *trans, enum scp_screate_status *status, - int *display, + const char **display, struct guid *guid) { /* Intermediate values */ int32_t i_status; - int32_t i_display; const struct libipm_fsb guid_descriptor = { (void *)guid, sizeof(*guid) }; int rv = libipm_msg_in_parse( trans, - "iiB", + "isB", &i_status, - &i_display, + display, &guid_descriptor); if (rv == 0) { *status = (enum scp_screate_status)i_status; - *display = i_display; + } + else + { + *display = ""; } return rv; @@ -743,7 +745,7 @@ scp_send_list_sessions_response( rv = libipm_msg_out_simple_send( trans, (int)E_SCP_LIST_SESSIONS_RESPONSE, - "iiuyqqyxisssx", + "iisyqqyxisssx", status, info->sid, info->display, @@ -786,7 +788,7 @@ scp_get_list_sessions_response( if (*status == E_SCP_LS_SESSION_INFO) { int32_t i_sid; - uint32_t i_display; + char *i_display; uint8_t i_type; uint16_t i_width; uint16_t i_height; @@ -800,7 +802,7 @@ scp_get_list_sessions_response( rv = libipm_msg_in_parse( trans, - "iuyqqyxisssx", + "isyqqyxisssx", &i_sid, &i_display, &i_type, @@ -819,6 +821,7 @@ scp_get_list_sessions_response( /* Allocate a block of memory large enough for the * structure result, and the strings it contains */ unsigned int len = sizeof(struct scp_session_info) + + g_strlen(i_display) + 1 + g_strlen(i_start_ip_addr) + 1 + g_strlen(i_client_ip) + 1 + g_strlen(i_client_name) + 1; @@ -840,7 +843,7 @@ scp_get_list_sessions_response( } /* Copy the data over */ p->sid = i_sid; - p->display = i_display; + COPY_STRING(p->display, i_display); p->type = (enum scp_session_type)i_type; p->width = i_width; p->height = i_height; diff --git a/libipm/scp.h b/libipm/scp.h index a0179c6485..194014ba18 100644 --- a/libipm/scp.h +++ b/libipm/scp.h @@ -387,15 +387,17 @@ scp_get_create_session_request(struct trans *trans, * * @param trans SCP transport * @param status Status of creation request - * @param display Should be zero if create session failed. + * @param display Will be "" if create session failed * @param guid Guid for session. Should be all zeros if create session failed * * @return != 0 for error + * + * The display will be either ":n" (X11) or (e.g) "wayland-0" (Wayland) */ int scp_send_create_session_response(struct trans *trans, enum scp_screate_status status, - int display, + const char *display, const struct guid *guid); @@ -413,7 +415,7 @@ scp_send_create_session_response(struct trans *trans, int scp_get_create_session_response(struct trans *trans, enum scp_screate_status *status, - int *display, + const char **display, struct guid *guid); /** diff --git a/libipm/scp_application_types.c b/libipm/scp_application_types.c index 5250d811c9..755398d854 100644 --- a/libipm/scp_application_types.c +++ b/libipm/scp_application_types.c @@ -68,6 +68,8 @@ scp_screate_status_to_str(enum scp_screate_status n, (n == E_SCP_SCREATE_MAX_REACHED) ? "Max session limit reached" : (n == E_SCP_SCREATE_NO_DISPLAY) ? "No X displays are available" : (n == E_SCP_SCREATE_X_SERVER_FAIL) ? "X server could not be started" : + (n == E_SCP_SCREATE_LABWC_FAIL) ? "labwc could not be started" : + (n == E_SCP_SCREATE_WAYVNC_FAIL) ? "wayvnc could not be started" : (n == E_SCP_SCREATE_SESSION_FAIL) ? "Session failed immediately" : (n == E_SCP_SCREATE_IN_PROGRESS) ? "Session creation is already in progress" : (n == E_SCP_SCREATE_GENERAL_ERROR) ? "General session creation error" : @@ -96,7 +98,7 @@ scp_sconnect_status_to_str(enum scp_sconnect_status n, (n == E_SCP_SCONNECT_NOT_LOGGED_IN) ? "Connection is not logged in" : (n == E_SCP_SCONNECT_NO_SUCH_GUID) ? "No such session for this user" : (n == E_SCP_SCONNECT_NO_MEMORY) ? "No memory for connection" : - (n == E_SCP_SCONNECT_SERVER_FAIL) ? "Can't connect to X server" : + (n == E_SCP_SCONNECT_SERVER_FAIL) ? "Can't connect to display server" : (n == E_SCP_SCONNECT_GENERAL_ERROR) ? "General session connection error" : /* Default */ NULL; diff --git a/libipm/scp_application_types.h b/libipm/scp_application_types.h index 421aed58b7..a373ab1d25 100644 --- a/libipm/scp_application_types.h +++ b/libipm/scp_application_types.h @@ -35,23 +35,41 @@ enum scp_session_type { SCP_SESSION_TYPE_XVNC = 0, ///< Session used Xvnc SCP_SESSION_TYPE_XVNC_UDS, ///< Session used Xvnc with UDS connection - SCP_SESSION_TYPE_XORG ///< Session used Xorg + xorgxrdp + SCP_SESSION_TYPE_XORG, ///< Session used Xorg + xorgxrdp + SCP_SESSION_TYPE_LABWC, ///< Labwc compositor session + SCP_SESSION_TYPE_LABWC_OVER_VNC ///< Labwc over VNC compositor session }; #define SCP_SESSION_TYPE_TO_STR(t) \ ((t) == SCP_SESSION_TYPE_XVNC ? "Xvnc" : \ (t) == SCP_SESSION_TYPE_XVNC_UDS ? "Xvnc-UDS" : \ (t) == SCP_SESSION_TYPE_XORG ? "Xorg" : \ + (t) == SCP_SESSION_TYPE_LABWC ? "labwc" : \ + (t) == SCP_SESSION_TYPE_LABWC_OVER_VNC ? "labwc(vnc)" : \ "unknown" \ ) +#define SCP_SESSION_TYPE_IS_X11(t) \ + ((t) >= SCP_SESSION_TYPE_XVNC && (t) <= SCP_SESSION_TYPE_XORG) +#define SCP_SESSION_TYPE_IS_LABWC(t) \ + ((t) >= SCP_SESSION_TYPE_LABWC && (t) <= SCP_SESSION_TYPE_LABWC_OVER_VNC) + +// Other constants +enum +{ + /* + * Storage space to allocate for a display name + */ + SCP_DISPLAY_NAME_SIZE = 32 +}; + /** * @brief Information to display about a particular sesman session */ struct scp_session_info { int sid; ///< Session ID - unsigned int display; ///< Display number + char *display; ///< Display name (":n" or "wayland-n") enum scp_session_type type; ///< Session type unsigned short width; ///< Initial session width unsigned short height; ///< Initial session height @@ -101,6 +119,8 @@ enum scp_screate_status E_SCP_SCREATE_MAX_REACHED, ///< Max number of sessions already reached E_SCP_SCREATE_NO_DISPLAY, ///< No X server display number is available E_SCP_SCREATE_X_SERVER_FAIL, ///< X server could not be started + E_SCP_SCREATE_LABWC_FAIL, ///< labwc could not be started + E_SCP_SCREATE_WAYVNC_FAIL, ///< wayvnc could not be started E_SCP_SCREATE_SESSION_FAIL, ///< The session failed quickly E_SCP_SCREATE_IN_PROGRESS, ///< A create session request is in progress E_SCP_SCREATE_GENERAL_ERROR ///< An unspecific error has occurred @@ -135,7 +155,7 @@ enum scp_sconnect_status E_SCP_SCONNECT_NOT_LOGGED_IN, ///< Connection is not logged in E_SCP_SCONNECT_NO_SUCH_GUID, ///< GUID does not exist for this user E_SCP_SCONNECT_NO_MEMORY, ///< Memory allocation failure - E_SCP_SCONNECT_SERVER_FAIL, ///< Can't connect to X server + E_SCP_SCONNECT_SERVER_FAIL, ///< Can't connect to display server E_SCP_SCONNECT_GENERAL_ERROR ///< An unspecific error has occurred }; diff --git a/sesman/chansrv/chansrv.c b/sesman/chansrv/chansrv.c index c75c4f9e8a..0d9b0ba4aa 100644 --- a/sesman/chansrv/chansrv.c +++ b/sesman/chansrv/chansrv.c @@ -69,7 +69,8 @@ static tbus g_thread_done_event = 0; struct config_chansrv *g_cfg = NULL; -int g_display_num = -1; +char g_display_str[32]; + int g_cliprdr_chan_id = -1; /* cliprdr */ int g_rdpsnd_chan_id = -1; /* rdpsnd */ int g_rdpdr_chan_id = -1; /* rdpdr */ @@ -1350,7 +1351,8 @@ setup_listen(void) g_lis_trans = trans_create(TRANS_MODE_UNIX, 8192, 8192); g_lis_trans->is_term = g_is_term; - g_snprintf(port, sizeof(port), XRDP_CHANSRV_STR, g_getuid(), g_display_num); + g_snprintf(port, sizeof(port), XRDP_CHANSRV_STR, + g_getuid(), STRIP_COLON(g_display_str)); g_lis_trans->trans_conn_in = my_trans_conn_in; error = trans_listen(g_lis_trans, port); @@ -1374,7 +1376,8 @@ setup_api_listen(void) g_api_lis_trans = trans_create(TRANS_MODE_UNIX, 8192 * 4, 8192 * 4); g_api_lis_trans->is_term = g_is_term; - g_snprintf(port, sizeof(port), CHANSRV_API_STR, g_getuid(), g_display_num); + g_snprintf(port, sizeof(port), CHANSRV_API_STR, + g_getuid(), STRIP_COLON(g_display_str)); g_api_lis_trans->trans_conn_in = my_api_trans_conn_in; error = trans_listen(g_api_lis_trans, port); @@ -1845,24 +1848,15 @@ main(int argc, char **argv) char text[256]; const char *config_path; char log_path[256]; - const char *display_text; char log_file[256]; enum logReturns error; struct log_config *logconfig; g_init("xrdp-chansrv"); /* os_calls */ g_memset(g_drdynvcs, 0, sizeof(g_drdynvcs)); - display_text = g_getenv("DISPLAY"); - if (display_text == NULL) - { - g_writeln("DISPLAY is not set"); - main_cleanup(); - return 1; - } - g_display_num = g_get_display_num_from_display(display_text); - if (g_display_num < 0) + if (g_get_display_string(g_display_str, sizeof(g_display_str)) < 0) { - g_writeln("Unable to get display from DISPLAY='%s'", display_text); + g_writeln("Unable to get display string"); main_cleanup(); return 1; } @@ -1888,7 +1882,8 @@ main(int argc, char **argv) pid = g_getpid(); /* starting logging subsystem */ - g_snprintf(log_file, 255, "%s/xrdp-chansrv.%d.log", log_path, g_display_num); + g_snprintf(log_file, sizeof(log_file), + "%s/xrdp-chansrv.%s.log", log_path, STRIP_COLON(g_display_str)); g_writeln("chansrv::main: using log file [%s]", log_file); if (g_file_exist(log_file)) { @@ -1950,9 +1945,6 @@ main(int argc, char **argv) /* Cater for the X server exiting unexpectedly */ xcommon_set_x_server_fatal_handler(x_server_fatal_handler); - LOG_DEVEL(LOG_LEVEL_INFO, "main: DISPLAY env var set to %s", display_text); - LOG_DEVEL(LOG_LEVEL_INFO, "main: using DISPLAY %d", g_display_num); - /* Set up RAIL sync objects */ g_snprintf(text, sizeof(text), "xrdp_chansrv_%8.8x_exec", pid); g_exec_event = g_create_wait_obj(text); diff --git a/sesman/chansrv/chansrv_fuse.c b/sesman/chansrv/chansrv_fuse.c index 579a1c8461..1291a337f3 100644 --- a/sesman/chansrv/chansrv_fuse.c +++ b/sesman/chansrv/chansrv_fuse.c @@ -2974,13 +2974,11 @@ static unsigned int format_user_info(char *dest, unsigned int len, char uidstr[64]; char username[64]; char display[64]; - char displaynum[64]; const struct info_string_tag map[] = { {'u', uidstr}, {'U', username}, - {'d', displaynum}, - {'D', display}, + {'d', display}, INFO_STRING_END_OF_LIST }; @@ -2989,20 +2987,22 @@ static unsigned int format_user_info(char *dest, unsigned int len, if (g_getlogin(username, sizeof(username)) != 0) { /* Fall back to UID */ - g_strncpy(username, uidstr, sizeof(username) - 1); + strlcpy(username, uidstr, sizeof(username)); } - if (getenv("DISPLAY") == NULL) + if (g_get_display_string(display, sizeof(display)) < 0) { /* if environment variable is not set, set it to empty string */ display[0] = '\0'; - displaynum[0] = '\0'; } - else + else if (display[0] == ':') { - g_strncpy(display, getenv("DISPLAY"), sizeof(display) - 1); - g_snprintf(displaynum, sizeof(displaynum), "%d", - g_get_display_num_from_display(display)); + // Lose the leading ':' for an X display + char *p = display; + while ((*p = *(p + 1)) != '\0') + { + ++p; + } } return g_format_info_string(dest, len, format, map); diff --git a/sesman/chansrv/pcsc/xrdp_pcsc.c b/sesman/chansrv/pcsc/xrdp_pcsc.c index 46c047c5d8..1e78ffd1f3 100644 --- a/sesman/chansrv/pcsc/xrdp_pcsc.c +++ b/sesman/chansrv/pcsc/xrdp_pcsc.c @@ -164,10 +164,9 @@ static int connect_to_chansrv(void) { int bytes; - int dis; + char disstr[32]; int error; char *xrdp_session; - char *xrdp_display; char *home_str; struct sockaddr_un saddr; struct sockaddr *psaddr; @@ -184,11 +183,10 @@ connect_to_chansrv(void) LLOGLN(0, ("connect_to_chansrv: error, not xrdp session")); return 1; } - xrdp_display = getenv("DISPLAY"); - if (xrdp_display == NULL) + + if (g_get_display_string(disstr, sizeof(disstr)) < 0) { - /* DISPLAY must be set */ - LLOGLN(0, ("connect_to_chansrv: error, display not set")); + LLOGLN(0, ("connect_to_chansrv: error, don't understand DISPLAY")); return 1; } home_str = getenv("HOME"); @@ -198,13 +196,6 @@ connect_to_chansrv(void) LLOGLN(0, ("connect_to_chansrv: error, home not set")); return 1; } - dis = g_get_display_num_from_display(xrdp_display); - if (dis < 0) - { - LLOGLN(0, ("connect_to_chansrv: error, don't understand DISPLAY='%s'", - xrdp_display)); - return 1; - } g_sck = socket(PF_LOCAL, SOCK_STREAM, 0); if (g_sck == -1) { @@ -214,7 +205,8 @@ connect_to_chansrv(void) memset(&saddr, 0, sizeof(struct sockaddr_un)); saddr.sun_family = AF_UNIX; bytes = sizeof(saddr.sun_path); - snprintf(saddr.sun_path, bytes, "%s/.pcsc%d/pcscd.comm", home_str, dis); + snprintf(saddr.sun_path, bytes, "%s/.pcsc%s/pcscd.comm", home_str, + STRIP_COLON(disstr)); saddr.sun_path[bytes - 1] = 0; LLOGLN(10, ("connect_to_chansrv: connecting to %s", saddr.sun_path)); psaddr = (struct sockaddr *) &saddr; diff --git a/sesman/chansrv/rail.c b/sesman/chansrv/rail.c index a9bac1a6ff..74f6de4f64 100644 --- a/sesman/chansrv/rail.c +++ b/sesman/chansrv/rail.c @@ -44,7 +44,7 @@ #include "list.h" extern int g_rail_chan_id; /* in chansrv.c */ -extern int g_display_num; /* in chansrv.c */ +extern char g_display_str[]; /* in chansrv.c */ extern char *g_exec_name; /* in chansrv.c */ extern tbus g_exec_event; /* in chansrv.c */ extern tbus g_exec_mutex; /* in chansrv.c */ diff --git a/sesman/chansrv/smartcard_pcsc.c b/sesman/chansrv/smartcard_pcsc.c index 0584188141..d9b1ca337f 100644 --- a/sesman/chansrv/smartcard_pcsc.c +++ b/sesman/chansrv/smartcard_pcsc.c @@ -52,11 +52,12 @@ #include "chansrv.h" #include "list.h" #include "smartcard_pcsc.h" +#include "xrdp_sockets.h" #if PCSC_STANDIN -extern int g_display_num; /* in chansrv.c */ +extern char g_display_str[]; /* in chansrv.c */ static int g_autoinc = 0; /* general purpose autoinc */ @@ -1983,7 +1984,6 @@ int scard_pcsc_init(void) { char *home; - int disp; int error; LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_pcsc_init:"); @@ -1993,8 +1993,8 @@ scard_pcsc_init(void) // TODO: See #2501. Use needs a way to move PCSCLITE_CSOCK_NAME // to a location not under $HOME. home = g_getenv("HOME"); - disp = g_display_num; - g_snprintf(g_pcsclite_ipc_dir, 255, "%s/.pcsc%d", home, disp); + g_snprintf(g_pcsclite_ipc_dir, 255, "%s/.pcsc%s", + home, STRIP_COLON(g_display_str)); if (g_directory_exist(g_pcsclite_ipc_dir)) { diff --git a/sesman/chansrv/sound.c b/sesman/chansrv/sound.c index 401363ce64..b318ca20d3 100644 --- a/sesman/chansrv/sound.c +++ b/sesman/chansrv/sound.c @@ -60,7 +60,7 @@ static lame_global_flags *g_lame_encoder = 0; #endif extern int g_rdpsnd_chan_id; /* in chansrv.c */ -extern int g_display_num; /* in chansrv.c */ +extern char g_display_str[]; /* in chansrv.c */ extern struct config_chansrv *g_cfg; /* in chansrv.c */ /* audio out: sound_server -> xrdp -> NeutrinoRDP */ @@ -1900,7 +1900,8 @@ sound_start_source_listener(void) g_audio_l_trans_in = trans_create(TRANS_MODE_UNIX, 128 * 1024, 8192); g_audio_l_trans_in->is_term = g_is_term; - g_snprintf(port, sizeof(port), CHANSRV_PORT_IN_STR, g_getuid(), g_display_num); + g_snprintf(port, sizeof(port), CHANSRV_PORT_IN_STR, g_getuid(), + STRIP_COLON(g_display_str)); g_audio_l_trans_in->trans_conn_in = sound_sndsrvr_source_conn_in; if (trans_listen(g_audio_l_trans_in, port) != 0) { @@ -1919,7 +1920,8 @@ sound_start_sink_listener(void) g_audio_l_trans_out = trans_create(TRANS_MODE_UNIX, 128 * 1024, 8192); g_audio_l_trans_out->is_term = g_is_term; - g_snprintf(port, sizeof(port), CHANSRV_PORT_OUT_STR, g_getuid(), g_display_num); + g_snprintf(port, sizeof(port), CHANSRV_PORT_OUT_STR, g_getuid(), + STRIP_COLON(g_display_str)); g_audio_l_trans_out->trans_conn_in = sound_sndsrvr_sink_conn_in; if (trans_listen(g_audio_l_trans_out, port) != 0) { diff --git a/sesman/eicp_process.c b/sesman/eicp_process.c index 708391c755..f659229ae0 100644 --- a/sesman/eicp_process.c +++ b/sesman/eicp_process.c @@ -100,12 +100,12 @@ static int process_create_session_response(struct scp_list_item *sli) { struct session_item *s_item; - int display = -1; + const char *display; struct guid guid; enum scp_screate_status status; int rv = eicp_get_create_session_response(sli->sesexec_trans, - &status, &guid); + &status, &display, &guid); if (rv == 0) { // Create an entry on the session list for the new session @@ -130,8 +130,7 @@ process_create_session_response(struct scp_list_item *sli) s_item->sesexec_pid = sli->sesexec_pid; s_item->guid = guid; s_item->uid = sli->uid; - s_item->display = sli->session_display; - display = s_item->display; + strlcpy(s_item->display, display, sizeof(s_item->display)); // We don't use the sesexec process again sli->sesexec_trans = NULL; @@ -140,12 +139,12 @@ process_create_session_response(struct scp_list_item *sli) else { guid_clear(&guid); - display = -1; + display = ""; } rv = scp_send_create_session_response(sli->client_trans, status, display, &guid); sli->create_session_in_progress = 0; - sli->session_display = -1; + sli->session_x11_display = -1; } return rv; diff --git a/sesman/ercp_process.c b/sesman/ercp_process.c index cc2734376c..39864a5a6e 100644 --- a/sesman/ercp_process.c +++ b/sesman/ercp_process.c @@ -43,7 +43,7 @@ process_session_announce_event(struct session_item *si) { int rv; const char *start_ip_addr; - unsigned int display; + const char *display; rv = ercp_get_session_announce_event(si->sesexec_trans, &display, @@ -55,28 +55,16 @@ process_session_announce_event(struct session_item *si) &si->guid, &start_ip_addr, &si->start_time); - if (rv == 0) - { - // We may already know the display we sent sesexec. If we do, - // check sesexec sent the same value back. - if (si->display >= 0 && display != (unsigned int)si->display) - { - LOG(LOG_LEVEL_ERROR, "Bugcheck: sesman expected display %d, got %u", - si->display, display); - rv = 1; - } - } - if (rv == 0) { snprintf(si->start_ip_addr, sizeof(si->start_ip_addr), "%s", start_ip_addr); - si->display = display; + strlcpy(si->display, display, sizeof(si->display)); si->state = E_SESSION_RUNNING; LOG(LOG_LEVEL_INFO, - "sesman: Session on display :%d is now running", si->display); + "sesman: Session on display %s is now running", si->display); } return rv; @@ -86,7 +74,7 @@ process_session_announce_event(struct session_item *si) static void process_session_finished_event(struct session_item *si) { - LOG(LOG_LEVEL_INFO, "sesman: Session on display :%d has finished.", + LOG(LOG_LEVEL_INFO, "sesman: Session on display %s has finished.", si->display); // Setting the transport down will remove this connection from the list si->sesexec_trans->status = TRANS_STATUS_DOWN; @@ -109,7 +97,7 @@ process_client_connect_event(struct session_item *si) strlcpy(si->client_name, client_name, sizeof(si->client_name)); si->last_connect_disconnect = connect_time; LOG(LOG_LEVEL_INFO, - "sesman: Session on display :%d is connected from client '%s'", + "sesman: Session on display %s is connected from client '%s'", si->display, si->client_name); } @@ -130,7 +118,7 @@ process_client_disconnect_event(struct session_item *si) si->client_name[0] = '\0'; si->last_connect_disconnect = disconnect_time; LOG(LOG_LEVEL_INFO, - "sesman: Session on display :%d has no client connection", + "sesman: Session on display %s has no client connection", si->display); } diff --git a/sesman/libsesman/sesman_auth.h b/sesman/libsesman/sesman_auth.h index 61537ab246..c1178d5a47 100644 --- a/sesman/libsesman/sesman_auth.h +++ b/sesman/libsesman/sesman_auth.h @@ -64,14 +64,14 @@ auth_uds(const char *user, enum scp_login_status *errorcode); * * @brief Starts a session * @param auth_info. Auth handle created by auth_userpass - * @param display_num Display number + * @param x11_display_num Display number (-1 if not used) * @return 0 on success, 1 on failure * * The resources allocated when the session is started are de-allocated * by auth_end() - there is no separate way to do this. */ int -auth_start_session(struct auth_info *auth_info, int display_num); +auth_start_session(struct auth_info *auth_info, int x11_display_num); /** * diff --git a/sesman/libsesman/sesman_config.c b/sesman/libsesman/sesman_config.c index 1621902509..d48a9ad484 100644 --- a/sesman/libsesman/sesman_config.c +++ b/sesman/libsesman/sesman_config.c @@ -405,6 +405,78 @@ config_read_security(int file, struct config_security *sc, return 0; } + +/***************************************************************************//** + * + * @brief Reads sesman [Labwc] configuration section + * @param file configuration file descriptor + * @param sc pointer to a config_labwc struct + * @param param_n parameter name list + * @param param_v parameter value list + * @return 0 on success, 1 on failure + * + */ +static int +config_read_labwc(int file, struct config_labwc *sc, + struct list *param_n, + struct list *param_v) +{ + int i; + const char *buf; + const char *value; + + list_clear(param_v); + list_clear(param_n); + + /* setting defaults */ + sc->labwc_exe = g_strdup(""); + sc->wayvnc_exe = g_strdup(""); + sc->enable_labwc_log = 0; + sc->enable_wayvnc_log = 0; + sc->log_file_path = g_strdup(""); + + file_read_section(file, "Labwc", param_n, param_v); + + for (i = 0; i < param_n->count; i++) + { + buf = (const char *)list_get_item(param_n, i); + value = (const char *)list_get_item(param_v, i); + + if (0 == g_strcasecmp(buf, "LabwcExe")) + { + g_free(sc->labwc_exe); + sc->labwc_exe = g_strdup(value); + } + else if (0 == g_strcasecmp(buf, "EnableLabwcLog")) + { + sc->enable_labwc_log = g_text2bool(value); + } + else if (0 == g_strcasecmp(buf, "WayvncExe")) + { + g_free(sc->wayvnc_exe); + sc->wayvnc_exe = g_strdup(value); + } + else if (0 == g_strcasecmp(buf, "EnableWayvncLog")) + { + sc->enable_wayvnc_log = g_text2bool(value); + } + else if (0 == g_strcasecmp(buf, "LogFilePath")) + { + g_free(sc->log_file_path); + sc->log_file_path = g_strdup(value); + } + } + + /* Check all memory allocations worked */ + if (sc->labwc_exe == NULL || sc->wayvnc_exe == NULL || + sc->log_file_path == NULL) + { + LOG(LOG_LEVEL_ERROR, "Memory allocation failure reading config"); + return 1; + } + return 0; +} + /***************************************************************************//** * * @brief Reads sesman [Sessions] configuration section @@ -636,6 +708,9 @@ config_read(const char *sesman_ini) config_read_vnc_params(fd, cfg, param_n, param_v); config_read_xorg_params(fd, cfg, param_n, param_v); + /* labwc parameters */ + config_read_labwc(fd, &(cfg->labwc), param_n, param_v); + /* read security config */ config_read_security(fd, &(cfg->sec), param_n, param_v); @@ -744,6 +819,19 @@ config_dump(struct config_sesman *config) i, (char *)list_get_item(config->vnc_params, i)); } + /* labwc */ + g_writeln("labwc parameters:"); + g_writeln(" LabwcExe: %s", + config->labwc.labwc_exe); + g_writeln(" WayvncExe: %s", + config->labwc.wayvnc_exe); + g_writeln(" EnableLabwcLog: %d", + config->labwc.enable_labwc_log); + g_writeln(" EnableWayvncLog: %d", + config->labwc.enable_wayvnc_log); + g_writeln(" LogFilePath: %s", + config->labwc.log_file_path); + /* SessionVariables */ if (config->env_names->count) { @@ -776,6 +864,9 @@ config_free(struct config_sesman *cs) g_free(cs->sec.ts_users); g_free(cs->sec.ts_admins); g_free(cs->sec.session_sockdir_group); + g_free(cs->labwc.labwc_exe); + g_free(cs->labwc.wayvnc_exe); + g_free(cs->labwc.log_file_path); g_free(cs); } } diff --git a/sesman/libsesman/sesman_config.h b/sesman/libsesman/sesman_config.h index 61aa6818c4..99994ccd43 100644 --- a/sesman/libsesman/sesman_config.h +++ b/sesman/libsesman/sesman_config.h @@ -173,6 +173,15 @@ struct config_sessions unsigned int startup_wait_time; }; +struct config_labwc +{ + char *labwc_exe; + char *wayvnc_exe; + int enable_labwc_log; + int enable_wayvnc_log; + char *log_file_path; +}; + /** * * @struct config_sesman @@ -239,11 +248,13 @@ struct config_sesman * @brief Xorg additional parameter list */ struct list *xorg_params; + /** - * @var log - * @brief Log configuration struct + * @var labwc + * @brief labwc configuration options struct */ - //struct log_config log; + struct config_labwc labwc; + /** * @var sec * @brief Security configuration options struct diff --git a/sesman/libsesman/verify_user.c b/sesman/libsesman/verify_user.c index fad83b422a..db0516e6c5 100644 --- a/sesman/libsesman/verify_user.c +++ b/sesman/libsesman/verify_user.c @@ -175,7 +175,7 @@ auth_uds(const char *user, enum scp_login_status *errorcode) /******************************************************************************/ /* returns error */ int -auth_start_session(struct auth_info *auth_info, int display_num) +auth_start_session(struct auth_info *auth_info, int x11_display_num) { return 0; } diff --git a/sesman/libsesman/verify_user_bsd.c b/sesman/libsesman/verify_user_bsd.c index 06621e6655..3fa799e9b0 100644 --- a/sesman/libsesman/verify_user_bsd.c +++ b/sesman/libsesman/verify_user_bsd.c @@ -120,7 +120,7 @@ auth_uds(const char *user, enum scp_login_status *errorcode) /******************************************************************************/ /* returns error */ int -auth_start_session(struct auth_info *auth_info, int display_num) +auth_start_session(struct auth_info *auth_info, int x11_display_num) { return 0; } diff --git a/sesman/libsesman/verify_user_kerberos.c b/sesman/libsesman/verify_user_kerberos.c index c2bde41440..e5af0d67ee 100644 --- a/sesman/libsesman/verify_user_kerberos.c +++ b/sesman/libsesman/verify_user_kerberos.c @@ -224,7 +224,7 @@ auth_uds(const char *user, enum scp_login_status *errorcode) /******************************************************************************/ /* returns error */ int -auth_start_session(struct auth_info *auth_info, int display_num) +auth_start_session(struct auth_info *auth_info, int x11_display_num) { return 0; } diff --git a/sesman/libsesman/verify_user_pam.c b/sesman/libsesman/verify_user_pam.c index d94dc0d260..68a6d937c1 100644 --- a/sesman/libsesman/verify_user_pam.c +++ b/sesman/libsesman/verify_user_pam.c @@ -398,19 +398,22 @@ auth_uds(const char *user, enum scp_login_status *errorcode) /* returns error */ static int -auth_start_session_private(struct auth_info *auth_info, int display_num) +auth_start_session_private(struct auth_info *auth_info, int x11_display_num) { int error; - char display[256]; - g_sprintf(display, ":%d", display_num); - error = pam_set_item(auth_info->ph, PAM_TTY, display); - - if (error != PAM_SUCCESS) + if (x11_display_num >= 0) { - LOG(LOG_LEVEL_ERROR, "pam_set_item failed: %s", - pam_strerror(auth_info->ph, error)); - return 1; + char display[32]; + g_snprintf(display, sizeof(display), ":%d", x11_display_num); + error = pam_set_item(auth_info->ph, PAM_TTY, display); + + if (error != PAM_SUCCESS) + { + LOG(LOG_LEVEL_ERROR, "pam_set_item failed: %s", + pam_strerror(auth_info->ph, error)); + return 1; + } } error = pam_setcred(auth_info->ph, PAM_ESTABLISH_CRED); @@ -444,9 +447,9 @@ auth_start_session_private(struct auth_info *auth_info, int display_num) * routine fails */ int -auth_start_session(struct auth_info *auth_info, int display_num) +auth_start_session(struct auth_info *auth_info, int x11_display_num) { - int result = auth_start_session_private(auth_info, display_num); + int result = auth_start_session_private(auth_info, x11_display_num); if (result != 0) { LOG(LOG_LEVEL_ERROR, diff --git a/sesman/libsesman/verify_user_pam_userpass.c b/sesman/libsesman/verify_user_pam_userpass.c index 5ba8e3d73c..5d55a72106 100644 --- a/sesman/libsesman/verify_user_pam_userpass.c +++ b/sesman/libsesman/verify_user_pam_userpass.c @@ -208,19 +208,22 @@ auth_uds(const char *user, enum scp_login_status *errorcode) /* returns error */ static int -auth_start_session_private(struct auth_info *auth_info, int display_num) +auth_start_session_private(struct auth_info *auth_info, int x11_display_num) { int error; - char display[256]; - g_sprintf(display, ":%d", display_num); - error = pam_set_item(auth_info->ph, PAM_TTY, display); - - if (error != PAM_SUCCESS) + if (x11_display_num >= 0) { - LOG(LOG_LEVEL_ERROR, "pam_set_item failed: %s", - pam_strerror(auth_info->ph, error)); - return 1; + char display[32]; + g_snprintf(display, sizeof(display), ":%d", x11_display_num); + error = pam_set_item(auth_info->ph, PAM_TTY, display); + + if (error != PAM_SUCCESS) + { + LOG(LOG_LEVEL_ERROR, "pam_set_item failed: %s", + pam_strerror(auth_info->ph, error)); + return 1; + } } error = pam_setcred(auth_info->ph, PAM_ESTABLISH_CRED); diff --git a/sesman/scp_list.c b/sesman/scp_list.c index ab278d1bda..acf9b62143 100644 --- a/sesman/scp_list.c +++ b/sesman/scp_list.c @@ -126,7 +126,7 @@ scp_list_item_new(void) { g_snprintf(result->peername, sizeof(result->peername), "unknown"); result->uid = (uid_t) -1; - result->session_display = -1; + result->session_x11_display = -1; if (!list_add_item(g_scp_list, (tintptr)result)) { g_free(result); @@ -263,7 +263,7 @@ scp_list_check_wait_objs(void) /******************************************************************************/ void -scp_list_get_create_session_displays(struct set_int *alloc_displays) +scp_list_get_create_session_x11_displays(struct set_int *alloc_displays) { int i = 0; for (i = 0; i < g_scp_list->count; ++i) @@ -272,10 +272,12 @@ scp_list_get_create_session_displays(struct set_int *alloc_displays) sli = (struct scp_list_item *)list_get_item(g_scp_list, i); + // Only add X11 displays if (SCP_LIST_ITEM_IN_USE(sli) && - sli->create_session_in_progress) + sli->create_session_in_progress && + sli->session_x11_display >= 0) { - set_int_add(alloc_displays, sli->session_display); + set_int_add(alloc_displays, sli->session_x11_display); } } } diff --git a/sesman/scp_list.h b/sesman/scp_list.h index 92230a16d7..cae524d25d 100644 --- a/sesman/scp_list.h +++ b/sesman/scp_list.h @@ -86,8 +86,8 @@ struct scp_list_item char start_ip_addr[MAX_PEER_ADDRSTRLEN]; int is_admin; int create_session_in_progress; ///< Already handling a create_session - /// Display allocated for session. This is always valid (>= 0) - unsigned int session_display; + /// Display allocated for session (-1 if N/A) + int session_x11_display; }; @@ -157,11 +157,11 @@ int scp_list_check_wait_objs(void); /** - * @brief Get all create-session displays + * @brief Get all create-session X11 displays * - * Adds displays allocated to create-session operations to a set + * Adds X11 displays allocated to create-session operations to a set */ void -scp_list_get_create_session_displays(struct set_int *alloc_displays); +scp_list_get_create_session_x11_displays(struct set_int *alloc_displays); #endif // SCP_LIST_H diff --git a/sesman/scp_process.c b/sesman/scp_process.c index 40b0e820e0..fa3fd40722 100644 --- a/sesman/scp_process.c +++ b/sesman/scp_process.c @@ -414,8 +414,8 @@ get_free_display(void) { // Get all the displays either allocated to sessions, or // potentially assigned to sessions on the SCP list - session_list_get_session_displays(alloc_displays); - scp_list_get_create_session_displays(alloc_displays); + session_list_get_session_x11_displays(alloc_displays); + scp_list_get_create_session_x11_displays(alloc_displays); // Find a free display, taking the allocated ones into account result = display_utils_get_free_display(alloc_displays); @@ -441,7 +441,8 @@ process_create_session_request(struct scp_list_item *sli) const char *directory; struct guid guid; - int display = -1; + const char *display; + int x11_display = -1; struct session_item *s_item = NULL; int start_sesexec = (sli->sesexec_trans == NULL); int send_client_reply = 1; @@ -474,7 +475,7 @@ process_create_session_request(struct scp_list_item *sli) { // Found an existing session LOG(LOG_LEVEL_INFO, - "A suitable session on display :%d is already active", + "A suitable session on display %s is already active", s_item->display); display = s_item->display; guid = s_item->guid; @@ -487,10 +488,11 @@ process_create_session_request(struct scp_list_item *sli) "The maximum number of sessions has been reached"); status = E_SCP_SCREATE_MAX_REACHED; } - else if ((display = get_free_display()) < 0) + else if (SCP_SESSION_TYPE_IS_X11(type) && + (x11_display = get_free_display()) < 0) { LOG(LOG_LEVEL_ERROR, - "No free display can be found for a new session"); + "No free X11 display can be found for a new session"); status = E_SCP_SCREATE_NO_DISPLAY; } // Create a socket dir for this user @@ -530,7 +532,7 @@ process_create_session_request(struct scp_list_item *sli) int eicp_stat; eicp_stat = eicp_send_create_session_request( sli->sesexec_trans, - display, + x11_display, type, width, height, bpp, shell, directory); @@ -548,7 +550,7 @@ process_create_session_request(struct scp_list_item *sli) // We're not sending a reply yet send_client_reply = 0; sli->create_session_in_progress = 1; - sli->session_display = display; // Reserve display + sli->session_x11_display = x11_display; // Reserve display } } } @@ -557,7 +559,7 @@ process_create_session_request(struct scp_list_item *sli) { if (status != E_SCP_SCREATE_OK) { - display = -1; + display = ""; guid_clear(&guid); } rv = scp_send_create_session_response(sli->client_trans, diff --git a/sesman/sesexec/Makefile.am b/sesman/sesexec/Makefile.am index d444cedfe2..9e83ae66f0 100644 --- a/sesman/sesexec/Makefile.am +++ b/sesman/sesexec/Makefile.am @@ -19,6 +19,14 @@ pkglibexec_PROGRAMS = \ xrdp_sesexec_SOURCES = \ sesexec.c \ sesexec.h \ + session_base.c \ + session_base.h \ + session_labwc.c \ + session_labwc.h \ + session_x11.c \ + session_x11.h \ + session_parameters.c \ + session_parameters.h \ session.c \ session.h \ ccp_server.c \ @@ -37,8 +45,8 @@ xrdp_sesexec_SOURCES = \ sessionrecord.h \ xauth.c \ xauth.h \ - xwait.c \ - xwait.h + display_server_util.c \ + display_server_util.h xrdp_sesexec_LDFLAGS = @@ -47,3 +55,6 @@ xrdp_sesexec_LDADD = \ $(top_builddir)/libipm/libipm.la \ $(top_builddir)/common/libcommon.la \ $(SESEXEC_EXTRA_LIBS) + +SUBDIRS = \ + wayland diff --git a/sesman/sesexec/display_server_util.c b/sesman/sesexec/display_server_util.c new file mode 100644 index 0000000000..ec2322d646 --- /dev/null +++ b/sesman/sesexec/display_server_util.c @@ -0,0 +1,206 @@ +#if defined(HAVE_CONFIG_H) +#include "config_ac.h" +#endif + +#include "env.h" +#include "log.h" +#include "os_calls.h" +#include "string_calls.h" +#include "login_info.h" +#include "sesman_auth.h" + +#include "display_server_util.h" + +#include +#include + +/******************************************************************************/ +static void +process_helper_messages(FILE *dp, + const char *prog, + void (*setenvvar)(const char *name, + const char *value, + void *closure), + void *closure) +{ + char buffer[256]; + while (fgets(buffer, sizeof(buffer), dp)) + { + enum logLevels level = LOG_LEVEL_ERROR; + + g_strtrim(buffer, 2); + + // Has the message got a class at the start? + if (strlen(buffer) > 3 && buffer[0] == '<' && buffer[2] == '>') + { + if (buffer[1] == 'V') + { + // Environment variable + const char *var = buffer + 3; + char *val = g_strchr(var, '='); + if (val == NULL) + { + LOG(LOG_LEVEL_ERROR, "%s sent over bad message '%s'", + prog, buffer); + } + else + { + *val++ = '\0'; // Terminate 'var', and move to the value + if (setenvvar == NULL) + { + LOG(LOG_LEVEL_WARNING, "%s can't set '%s'", + prog, buffer); + } + else + { + (*setenvvar)(var, val, closure); + } + } + } + else + { + // It's a log message + switch (buffer[1]) + { + case 'D': + level = LOG_LEVEL_DEBUG; + break; + case 'I': + level = LOG_LEVEL_INFO; + break; + case 'W': + level = LOG_LEVEL_WARNING; + break; + default: + level = LOG_LEVEL_ERROR; + break; + } + const char *msg = buffer + 3; + + if (strlen(msg) > 0) + { + LOG(level, "%s: %s", prog, msg); + } + } + } + else + { + LOG(LOG_LEVEL_ERROR, "%s sent over bad message '%s'", + prog, buffer); + } + } +} + +/******************************************************************************/ +enum display_server_status +wait_for_display_server(struct login_info *login_info, + const struct list *env_names, + const struct list *env_values, + struct list *cmd, + void (*setenvvar)(const char *name, + const char *value, + void *closure), + void *closure) +{ + enum display_server_status rv = DS_STATUS_MISC_ERROR; + int fd[2] = {-1, -1}; + const char *helper = (const char *)cmd->items[0]; + + // Get the unqualified name of the helper + const char *prog = g_strrchr(helper, '/'); + if (prog != NULL) + { + ++prog; + } + else + { + prog = helper; + } + + if (g_pipe(fd) != 0) + { + LOG(LOG_LEVEL_ERROR, "Can't create pipe : %s", g_get_strerror()); + } + else + { + pid_t pid = g_fork(); + if (pid < 0) + { + // Error already logged + } + else if (pid == 0) + { + /* Child process */ + + /* Send stdout and stderr up the pipe */ + g_file_close(fd[0]); + g_file_duplicate_on(fd[1], 1); + g_file_duplicate_on(fd[1], 2); + + /* Move to the user context... */ + env_set_user(login_info->uid, + 0, + env_names, + env_values); + auth_set_env(login_info->auth_info); + + /* ...and run the program */ + g_execvp_list(helper, cmd); + LOG(LOG_LEVEL_ERROR, "Can't run %s - %s", + helper, g_get_strerror()); + g_exit(rv); + } + else + { + LOG(LOG_LEVEL_DEBUG, + "Waiting for display server to start"); + + g_file_close(fd[1]); + fd[1] = -1; + FILE *dp = fdopen(fd[0], "r"); + if (dp == NULL) + { + LOG(LOG_LEVEL_ERROR, "Unable to launch %s", prog); + } + else + { + struct proc_exit_status e; + + fd[0] = -1; // File descriptor closed by fclose() + process_helper_messages(dp, prog, setenvvar, closure); + fclose(dp); + e = g_waitpid_status(pid); + switch (e.reason) + { + case E_PXR_STATUS_CODE: + rv = (enum display_server_status)e.val; + break; + + case E_PXR_SIGNAL: + { + char sigstr[MAXSTRSIGLEN]; + LOG(LOG_LEVEL_ERROR, + "%s failed with unexpected signal %s", + prog, g_sig2text(e.val, sigstr)); + } + break; + + default: + LOG(LOG_LEVEL_ERROR, + "%s failed with unknown reason", prog); + } + } + } + if (fd[0] >= 0) + { + g_file_close(fd[0]); + } + + if (fd[1] >= 0) + { + g_file_close(fd[1]); + } + } + + return rv; +} diff --git a/sesman/sesexec/display_server_util.h b/sesman/sesexec/display_server_util.h new file mode 100644 index 0000000000..13023fba25 --- /dev/null +++ b/sesman/sesexec/display_server_util.h @@ -0,0 +1,69 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg and contributors 2004-2025 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * + * @file display_server_util.h + * @brief Display server utilities + * @author Matt Burt + * + */ + +#ifndef XWAIT_H +#define XWAIT_H + +#include + +struct login_info; + +enum display_server_status +{ + DS_STATUS_OK = 0, + DS_STATUS_MISC_ERROR, + DS_STATUS_TIMED_OUT, + DS_STATUS_FAILED_TO_START +}; + +/** + * + * @brief waits for the display server to start + * + * A standalone program is used for this, so the status of the display + * server can be determined. + * + * @param login_info Login info for user + * @param env_names Environment to set for user (names) + * @param env_values Environment to set for user (values) + * @param command (and parameters) to run to wait for the display server + * @param setenvvar Callback for handling any environment + * variables sent by the wait command + * @param closure Extra parameter for setenvvar + * @return status + * + */ +enum display_server_status +wait_for_display_server(struct login_info *login_info, + const struct list *env_names, + const struct list *env_values, + struct list *cmd, + void (*setenvvar)(const char *name, + const char *value, + void *closure), + void *closure); + +#endif diff --git a/sesman/sesexec/eicp_server.c b/sesman/sesexec/eicp_server.c index 9f75ca364d..02da996c53 100644 --- a/sesman/sesexec/eicp_server.c +++ b/sesman/sesexec/eicp_server.c @@ -39,6 +39,7 @@ #include "sesexec.h" #include "sesexec_discover.h" #include "session.h" +#include "session_parameters.h" /******************************************************************************/ static int @@ -148,12 +149,13 @@ handle_create_session_request(struct trans *self) int status; status = eicp_get_create_session_request( - self, &sp.display, + self, &sp.x11_display, &sp.type, &sp.width, &sp.height, &sp.bpp, &sp.shell, &sp.directory); if (status == 0) { enum scp_screate_status scp_status = E_SCP_SCREATE_OK; + const char *display = ""; // Must be logged in to start a session if (g_login_info == NULL) @@ -165,10 +167,15 @@ handle_create_session_request(struct trans *self) // Try to create the session sp.guid = guid_new(); scp_status = session_start(g_login_info, &sp, &g_session_data); + if (scp_status == E_SCP_SCREATE_OK) + { + display = session_get_display(g_session_data); + } } // Return the creation status to sesman. - status = eicp_send_create_session_response(self, scp_status, &sp.guid); + status = eicp_send_create_session_response(self, scp_status, display, + &sp.guid); if (status == 0 && scp_status == E_SCP_SCREATE_OK) { // Further comms to sesman is sent over the ERCP protocol @@ -177,7 +184,7 @@ handle_create_session_request(struct trans *self) // Announce the session to sesman if ((status = ercp_send_session_announce_event( self, - sp.display, + display, g_login_info->uid, sp.type, sp.width, diff --git a/sesman/sesexec/env.c b/sesman/sesexec/env.c index a981c018c2..a8dd6d6e6f 100644 --- a/sesman/sesexec/env.c +++ b/sesman/sesexec/env.c @@ -101,7 +101,7 @@ env_check_password_file(const char *filename, const char *passwd) /******************************************************************************/ /* its the responsibility of the caller to free passwd_file */ int -env_set_user(int uid, char **passwd_file, int display, +env_set_user(int uid, char **passwd_file, const struct list *env_names, const struct list *env_values) { int error; @@ -110,16 +110,13 @@ env_set_user(int uid, char **passwd_file, int display, int len; char *name; char *value; - char *pw_username; - char *pw_shell; - char *pw_dir; + char *pw_username = NULL; + char *pw_shell = NULL; + char *pw_dir = NULL; + char *display = NULL; char text[256]; char hostname[256]; - pw_username = 0; - pw_shell = 0; - pw_dir = 0; - error = g_getuser_info_by_uid(uid, &pw_username, &pw_gid, &pw_shell, &pw_dir, 0); @@ -156,27 +153,15 @@ env_set_user(int uid, char **passwd_file, int display, g_setenv_log("UID", text, 1); g_setenv_log("HOME", pw_dir, 1); g_set_current_dir(pw_dir); - g_snprintf(text, sizeof(text), ":%d.0", display); - g_setenv_log("DISPLAY", text, 1); // Use our PID as the XRDP_SESSION value g_snprintf(text, sizeof(text), "%d", g_pid); g_setenv_log("XRDP_SESSION", text, 1); - /* XRDP_SOCKET_PATH should be set here. It's used by + /* XRDP_SOCKET_PATH is used by * xorgxrdp and the pulseaudio plugin */ g_snprintf(text, sizeof(text), XRDP_SOCKET_PATH, uid); g_setenv_log("XRDP_SOCKET_PATH", text, 1); - /* pulse sink socket */ - g_snprintf(text, sizeof(text), CHANSRV_PORT_OUT_BASE_STR, display); - g_setenv_log("XRDP_PULSE_SINK_SOCKET", text, 1); - /* pulse source socket */ - g_snprintf(text, sizeof(text), CHANSRV_PORT_IN_BASE_STR, display); - g_setenv_log("XRDP_PULSE_SOURCE_SOCKET", text, 1); - if (g_cfg->sec.xauth_in_sysdir) - { - g_snprintf(text, sizeof(text), XRDP_SOCKET_PATH "/Xauthority", - uid); - g_setenv_log("XAUTHORITY", text, 1); - } + + // Set the passed-in variables. This may include a DISPLAY if ((env_names != 0) && (env_values != 0) && (env_names->count == env_values->count)) { @@ -185,6 +170,34 @@ env_set_user(int uid, char **passwd_file, int display, name = (char *) list_get_item(env_names, index), value = (char *) list_get_item(env_values, index), g_setenv_log(name, value, 1); + + // Look for a DISPLAY. WAYLAND_DISPLAY overrides + // DISPLAY + if (strcmp(name, "WAYLAND_DISPLAY") == 0 || + (strcmp(name, "DISPLAY") == 0 && display == NULL)) + { + display = value; + } + } + } + + // Set things dependent on the DISPLAY + if (display != NULL) + { + /* pulse sink socket */ + g_snprintf(text, sizeof(text), CHANSRV_PORT_OUT_BASE_STR, + STRIP_COLON(display)); + g_setenv_log("XRDP_PULSE_SINK_SOCKET", text, 1); + /* pulse source socket */ + g_snprintf(text, sizeof(text), CHANSRV_PORT_IN_BASE_STR, + STRIP_COLON(display)); + g_setenv_log("XRDP_PULSE_SOURCE_SOCKET", text, 1); + + if (display[0] == ':' && g_cfg->sec.xauth_in_sysdir) + { + g_snprintf(text, sizeof(text), + XRDP_SOCKET_PATH "/Xauthority", uid); + g_setenv_log("XAUTHORITY", text, 1); } } g_gethostname(hostname, 255); @@ -205,8 +218,9 @@ env_set_user(int uid, char **passwd_file, int display, } } - len = g_snprintf(NULL, 0, "%s/.vnc/sesman_passwd-%s@%s:%d", - pw_dir, pw_username, hostname, display); + len = g_snprintf(NULL, 0, "%s/.vnc/sesman_passwd-%s@%s:%s", + pw_dir, pw_username, hostname, + STRIP_COLON(display)); ++len; // Allow for terminator *passwd_file = (char *) g_malloc(len, 1); @@ -214,8 +228,8 @@ env_set_user(int uid, char **passwd_file, int display, { /* Try legacy names first, remove if found */ g_snprintf(*passwd_file, len, - "%s/.vnc/sesman_%s_passwd:%d", - pw_dir, pw_username, display); + "%s/.vnc/sesman_%s_passwd:%s", + pw_dir, pw_username, STRIP_COLON(display)); if (g_file_exist(*passwd_file)) { LOG(LOG_LEVEL_WARNING, "Removing old " @@ -232,8 +246,9 @@ env_set_user(int uid, char **passwd_file, int display, g_file_delete(*passwd_file); } g_snprintf(*passwd_file, len, - "%s/.vnc/sesman_passwd-%s@%s:%d", - pw_dir, pw_username, hostname, display); + "%s/.vnc/sesman_passwd-%s@%s:%s", + pw_dir, pw_username, hostname, + STRIP_COLON(display)); } } else diff --git a/sesman/sesexec/env.h b/sesman/sesexec/env.h index 615285bb07..0a33c02b3d 100644 --- a/sesman/sesexec/env.h +++ b/sesman/sesexec/env.h @@ -45,12 +45,13 @@ env_check_password_file(const char *filename, const char *password); * @brief Sets user environment ($PATH, $HOME, $UID, and others) * @param uid user ID * @param passwd_file VNC password file - * @param display The session display + * @param env_names List of environment variables to set + * @param env_values Values for the above list * @return 0 on success, g_getuser_info() error codes on error * */ int -env_set_user(int uid, char **passwd_file, int display, +env_set_user(int uid, char **passwd_file, const struct list *env_names, const struct list *env_values); #endif diff --git a/sesman/sesexec/ercp_server.c b/sesman/sesexec/ercp_server.c index 3026173e5e..ad81ff6f7f 100644 --- a/sesman/sesexec/ercp_server.c +++ b/sesman/sesexec/ercp_server.c @@ -49,7 +49,7 @@ get_session_fds(struct session_data *sd, unsigned int scp_flags, { enum scp_sconnect_status result = E_SCP_SCONNECT_OK; - if ((*display_fd = session_get_display_server_fd(g_login_info, sd)) < 0) + if ((*display_fd = session_get_display_server_fd(sd, g_login_info)) < 0) { result = E_SCP_SCONNECT_SERVER_FAIL; } @@ -61,7 +61,7 @@ get_session_fds(struct session_data *sd, unsigned int scp_flags, else { // If this fails, it's inconvenient, but not a show-stopper - *chan_fd = session_get_chansrv_fd(g_login_info, sd); + *chan_fd = session_get_chansrv_fd(sd, g_login_info); } return result; @@ -147,16 +147,16 @@ handle_connect_session_request(struct trans *self) g_login_info->username); if (g_cfg->always_run_reconnect) { - session_run_reconnect_script(g_login_info, - g_session_data, vars); + session_run_reconnect_script(g_session_data, + g_login_info, vars); } } else { LOG(LOG_LEVEL_INFO, "User %s has reconnected to a session", g_login_info->username); - session_run_reconnect_script(g_login_info, - g_session_data, vars); + session_run_reconnect_script(g_session_data, + g_login_info, vars); } // Convert the SCP transport to a CCP transport, and diff --git a/sesman/sesexec/sesexec_discover.c b/sesman/sesexec/sesexec_discover.c index 955d6167fb..47f619e2a2 100644 --- a/sesman/sesexec/sesexec_discover.c +++ b/sesman/sesexec/sesexec_discover.c @@ -34,6 +34,7 @@ #include "trans.h" #include "sesexec.h" #include "session.h" +#include "session_parameters.h" #include "sesman_config.h" #include "os_calls.h" #include "ercp.h" @@ -92,7 +93,7 @@ discover_trans_conn_in(struct trans *trans, struct trans *new_trans) { (void)ercp_send_session_announce_event( new_trans, - sp->display, + session_get_display(g_session_data), g_login_info->uid, sp->type, sp->width, @@ -142,9 +143,9 @@ sesexec_discover_enable(void) { char discover_port[XRDP_SOCKETS_MAXPATH]; - snprintf(discover_port, sizeof(discover_port), "%s.r/%u", + snprintf(discover_port, sizeof(discover_port), "%s.r/%d", g_cfg->listen_port, - session_get_parameters(g_session_data)->display); + session_get_parameters(g_session_data)->x11_display); g_discover_trans->is_term = sesexec_is_term; g_discover_trans->trans_conn_in = discover_trans_conn_in; if ((rv = trans_listen(g_discover_trans, discover_port)) != 0) diff --git a/sesman/sesexec/session.c b/sesman/sesexec/session.c index c4249b7ab1..99a5dfbf17 100644 --- a/sesman/sesexec/session.c +++ b/sesman/sesexec/session.c @@ -1,7 +1,7 @@ /** * xrdp: A Remote Desktop Protocol server. * - * Copyright (C) Jay Sorg 2004-2015 + * Copyright (C) Jay Sorg 2004-2025 * * BSD process grouping by: * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland. @@ -24,8 +24,10 @@ /** * * @file session.c - * @brief Session management code - * @author Jay Sorg, Simone Fedele + * @brief Session management definitions + * + * This module wraps the session management classes + * @author Matt Burt * */ @@ -34,652 +36,32 @@ #endif #include -#include -#include "arch.h" #include "session.h" +#include "session_base.h" +#include "session_parameters.h" #include "sesman_auth.h" #include "sesman_config.h" #include "env.h" -#include "guid.h" -#include "list.h" #include "log.h" #include "login_info.h" #include "os_calls.h" #include "sesexec.h" -#include "sessionrecord.h" -#include "string_calls.h" #include "trans.h" -#include "xauth.h" -#include "xwait.h" #include "xrdp_sockets.h" -struct session_data -{ - pid_t x_server; ///< PID of X server - pid_t win_mgr; ///< PID of window manager - pid_t chansrv; //< PID of chansrv - time_t start_time; - unsigned int connect_count; - struct session_parameters params; - // Flexible array member used to store strings in params and ip_addr; -#ifdef __cplusplus - char strings[1]; -#else - char strings[]; -#endif -}; - /******************************************************************************/ /** - * Create a new session_data structure from a session_parameters object - * - * @param sp Session parameters passed to session_start() - * @return semi-initialised session_data struct + * Starts a session from an operating system perspective + * @param login_info login info for user + * @param s session_parameters + * @return Status */ -static struct session_data * -session_data_new(const struct session_parameters *sp) -{ - unsigned int string_length = 0; - // What string length do we need? - string_length += g_strlen(sp->shell) + 1; - string_length += g_strlen(sp->directory) + 1; - - struct session_data *sd = (struct session_data *)g_malloc(sizeof(*sd) + string_length, 0); - - if (sd == NULL) - { - LOG(LOG_LEVEL_ERROR, "Out of memory allocating session data struct"); - } - else - { - sd->win_mgr = -1; - sd->x_server = -1; - sd->chansrv = -1; - sd->start_time = 0; - sd->connect_count = 0; - - /* Copy all the non-string session parameters... */ - sd->params = *sp; - - /* ...and then the strings */ - char *memptr = sd->strings; - -#define COPY_STRING(dest,src) \ - (dest) = memptr; \ - strcpy(memptr, src); \ - memptr += strlen(memptr) + 1 - - COPY_STRING(sd->params.shell, sp->shell); - COPY_STRING(sd->params.directory, sp->directory); - -#undef COPY_STRING - } - - return sd; -} - -/******************************************************************************/ -void -session_data_free(struct session_data *session_data) -{ - if (session_data != NULL) - { -#ifdef USE_DEVEL_LOGGING - if (session_data->win_mgr > 0) - { - LOG_DEVEL(LOG_LEVEL_WARNING, - "Freeing session data with valid window manager PID %d", - session_data->win_mgr); - } - if (session_data->x_server > 0) - { - LOG_DEVEL(LOG_LEVEL_WARNING, - "Freeing session data with valid X server PID %d", - session_data->x_server); - } - if (session_data->chansrv > 0) - { - LOG_DEVEL(LOG_LEVEL_WARNING, - "Freeing session data with valid chansrv PID %d", - session_data->chansrv); - } -#endif - - free(session_data); - } -} - -/******************************************************************************/ -/** - * Creates a string consisting of all parameters that is hosted in the param list - * @param self - * @param outstr, allocate this buffer before you use this function - * @param len the allocated len for outstr - * @return - */ -static char * -dumpItemsToString(struct list *self, char *outstr, int len) -{ - int index; - int totalLen = 0; - - g_memset(outstr, 0, len); - if (self->count == 0) - { - LOG_DEVEL(LOG_LEVEL_TRACE, "List is empty"); - } - - for (index = 0; index < self->count; index++) - { - /* +1 = one space*/ - totalLen = totalLen + g_strlen((char *)list_get_item(self, index)) + 1; - - if (len > totalLen) - { - g_strcat(outstr, (char *)list_get_item(self, index)); - g_strcat(outstr, " "); - } - } - - return outstr ; -} - -/******************************************************************************/ -static void -start_chansrv(const struct login_info *login_info, - const struct session_parameters *s, - void *closure /* unused */) -{ - struct list *chansrv_params = list_create(); - const char *exe_path = XRDP_SBIN_PATH "/xrdp-chansrv"; - - if (chansrv_params != NULL) - { - chansrv_params->auto_free = 1; - if (!list_add_strdup(chansrv_params, exe_path)) - { - list_delete(chansrv_params); - chansrv_params = NULL; - } - } - - if (chansrv_params == NULL) - { - LOG(LOG_LEVEL_ERROR, "Out of memory starting chansrv"); - } - else - { - env_set_user(login_info->uid, 0, s->display, - g_cfg->env_names, - g_cfg->env_values); - - LOG_DEVEL_LEAKING_FDS("chansrv", 3, -1); - - /* executing chansrv */ - g_execvp_list(exe_path, chansrv_params); - - /* should not get here */ - list_delete(chansrv_params); - } -} - -/******************************************************************************/ -static void -start_window_manager(const struct login_info *login_info, - const struct session_parameters *s, - void *closure /* unused */) -{ - char text[256]; - - env_set_user(login_info->uid, - 0, - s->display, - g_cfg->env_names, - g_cfg->env_values); - - auth_set_env(login_info->auth_info); - LOG_DEVEL_LEAKING_FDS("window manager", 3, -1); - - if (s->directory[0] != '\0') - { - if (g_cfg->sec.allow_alternate_shell) - { - g_set_current_dir(s->directory); - } - else - { - LOG(LOG_LEVEL_WARNING, - "Directory change to %s requested, but not " - "allowed by AllowAlternateShell config value.", - s->directory); - } - } - - if (s->shell[0] != '\0') - { - if (g_cfg->sec.allow_alternate_shell) - { - if (g_strchr(s->shell, ' ') != 0 || g_strchr(s->shell, '\t') != 0) - { - LOG(LOG_LEVEL_INFO, - "Using user requested window manager on " - "display %u with embedded arguments using a shell: %s", - s->display, s->shell); - const char *argv[] = {"sh", "-c", s->shell, NULL}; - g_execvp("/bin/sh", (char **)argv); - } - else - { - LOG(LOG_LEVEL_INFO, - "Using user requested window manager on " - "display %d: %s", s->display, s->shell); - g_execlp3(s->shell, s->shell, 0); - } - } - else - { - LOG(LOG_LEVEL_WARNING, - "Shell %s requested by user, but not allowed by " - "AllowAlternateShell config value.", - s->shell); - } - } - else - { - LOG(LOG_LEVEL_DEBUG, "The user session on display %u did " - "not request a specific window manager", s->display); - } - - /* try to execute user window manager if enabled */ - if (g_cfg->enable_user_wm) - { - g_snprintf(text, sizeof(text), "%s/%s", - g_getenv("HOME"), g_cfg->user_wm); - if (g_file_exist(text)) - { - LOG(LOG_LEVEL_INFO, - "Using window manager on display %u" - " from user home directory: %s", s->display, text); - g_execlp3(text, g_cfg->user_wm, 0); - } - else - { - LOG(LOG_LEVEL_DEBUG, - "The user home directory window manager configuration " - "is enabled but window manager program does not exist: %s", - text); - } - } - - LOG(LOG_LEVEL_INFO, - "Using the default window manager on display %u: %s", - s->display, g_cfg->default_wm); - g_execlp3(g_cfg->default_wm, g_cfg->default_wm, 0); - - /* still a problem starting window manager just start xterm */ - LOG(LOG_LEVEL_WARNING, - "No window manager on display %u started, " - "so falling back to starting xterm for user debugging", - s->display); - g_execlp3("xterm", "xterm", 0); - - /* should not get here */ - LOG(LOG_LEVEL_ERROR, "A fatal error has occurred attempting to start " - "the window manager on display %u, aborting connection", - s->display); -} - -/******************************************************************************/ -static struct list * -prepare_xorg_xserver_params(const struct session_parameters *s, - const char *authfile) -{ - - char screen[32]; /* display number */ - char text[128]; - const char *xserver; - - struct list *params = list_create(); - if (params != NULL) - { - params->auto_free = 1; - - /* - * Make sure Xorg doesn't run setuid root. Root access is not - * needed. Xorg can fail when run as root and the user has no - * console permissions. - */ - if (g_cfg->sec.xorg_no_new_privileges && g_no_new_privs() != 0) - { - LOG(LOG_LEVEL_WARNING, - "[session start] (display %u): Failed to disable " - "setuid on X server: %s", - s->display, g_get_strerror()); - } - - g_snprintf(screen, sizeof(screen), ":%u", s->display); - - /* some args are passed via env vars */ - g_snprintf(text, sizeof(text), "%d", s->width); - g_setenv_log("XRDP_START_WIDTH", text, 1); - - g_snprintf(text, sizeof(text), "%d", s->height); - g_setenv_log("XRDP_START_HEIGHT", text, 1); - - g_snprintf(text, sizeof(text), "%d", g_cfg->sess.max_idle_time); - g_setenv_log("XRDP_SESMAN_MAX_IDLE_TIME", text, 1); - - g_snprintf(text, sizeof(text), "%d", g_cfg->sess.max_disc_time); - g_setenv_log("XRDP_SESMAN_MAX_DISC_TIME", text, 1); - - g_snprintf(text, sizeof(text), "%d", g_cfg->sess.kill_disconnected); - g_setenv_log("XRDP_SESMAN_KILL_DISCONNECTED", text, 1); - - /* get path of Xorg from config */ - xserver = (const char *)list_get_item(g_cfg->xorg_params, 0); - - /* these are the must have parameters */ - list_add_strdup_multi(params, - xserver, screen, - "-auth", authfile, - NULL); - - /* additional parameters from sesman.ini file */ - list_append_list_strdup(g_cfg->xorg_params, params, 1); - } - - return params; -} - -/******************************************************************************/ -/** - * Prepare a list of parameters for the Xvnc X server - * @param s Session parameters - * @params authfile XAUTHORITY file - * @params passwd_file VNC password file, or NULL - * @params port UDS port to connect to, or NULL - * @return parameters list - * - * One of passwd_file and port must be set - */ -static struct list * -prepare_xvnc_xserver_params(const struct session_parameters *s, - const char *authfile, - const char *passwd_file, - const char *port) -{ - char screen[32] = {0}; /* display number */ - char geometry[32] = {0}; - char depth[32] = {0}; - char guid_str[GUID_STR_SIZE]; - const char *xserver; - - struct list *params = list_create(); - if (params != NULL) - { - params->auto_free = 1; - - g_snprintf(screen, sizeof(screen), ":%u", s->display); - g_snprintf(geometry, sizeof(geometry), "%dx%d", s->width, s->height); - g_snprintf(depth, sizeof(depth), "%d", s->bpp); - - guid_to_str(&s->guid, guid_str); - env_check_password_file(passwd_file, guid_str); - - /* get path of Xvnc from config */ - xserver = (const char *)list_get_item(g_cfg->vnc_params, 0); - - /* these are the must have parameters */ - list_add_strdup_multi(params, - xserver, screen, - "-auth", authfile, - "-geometry", geometry, - "-depth", depth, - NULL); - - if (passwd_file != NULL) - { - /* RFB authorization */ - list_add_strdup_multi(params, - "-rfbauth", passwd_file, - NULL); - } - else if (port != NULL) - { - /* UDS connection. Authorization is handled by standard socket - * permissions, so we do not need to authorize within the - * VNC protocol exchange as well */ - char sock_mode[16]; - - /* Convert a standard permissions mask into decimal - * for the -rfbunixmode switch argument - */ - g_snprintf(sock_mode, sizeof(sock_mode), - "%d", 0660); /* rw-rw---- */ - - list_add_strdup_multi(params, - "-rfbunixpath", port, - "-rfbunixmode", sock_mode, - "-SecurityTypes", "None", - NULL); - } - - /* additional parameters from sesman.ini file */ - //config_read_xserver_params(SCP_SESSION_TYPE_XVNC, - // xserver_params); - list_append_list_strdup(g_cfg->vnc_params, params, 1); - } - return params; -} - -/******************************************************************************/ -/* Either execs the X server, or returns */ -static void -start_x_server(const struct login_info *login_info, - const struct session_parameters *s, - void *closure /* unused */) -{ - char authfile[256]; /* The filename for storing xauth information */ - char execvpparams[2048]; - char *passwd_file = NULL; - struct list *xserver_params = NULL; - int unknown_session_type = 0; - - if (s->type == SCP_SESSION_TYPE_XVNC) - { - env_set_user(login_info->uid, - &passwd_file, - s->display, - g_cfg->env_names, - g_cfg->env_values); - } - else - { - env_set_user(login_info->uid, - 0, - s->display, - g_cfg->env_names, - g_cfg->env_values); - } - - /* prepare the Xauthority stuff */ - if (g_getenv("XAUTHORITY") != NULL) - { - g_snprintf(authfile, sizeof(authfile), "%s", - g_getenv("XAUTHORITY")); - } - else - { - g_snprintf(authfile, sizeof(authfile), "%s", ".Xauthority"); - } - - /* Add the entry in XAUTHORITY file or exit if error */ - if (add_xauth_cookie(s->display, authfile) != 0) - { - LOG(LOG_LEVEL_ERROR, - "Error setting the xauth cookie for display %u in file %s", - s->display, authfile); - } - else - { - switch (s->type) - { - char port[256]; - - case SCP_SESSION_TYPE_XORG: - xserver_params = prepare_xorg_xserver_params(s, authfile); - break; - - case SCP_SESSION_TYPE_XVNC: - xserver_params = prepare_xvnc_xserver_params(s, authfile, - passwd_file, NULL); - break; - - case SCP_SESSION_TYPE_XVNC_UDS: - g_snprintf(port, sizeof(port), XRDP_X11RDP_STR, - login_info->uid, s->display); - xserver_params = prepare_xvnc_xserver_params(s, authfile, - NULL, port); - break; - - default: - unknown_session_type = 1; - } - - if (xserver_params == NULL) - { - LOG(LOG_LEVEL_ERROR, "Out of memory allocating X server params"); - } - else if (unknown_session_type) - { - LOG(LOG_LEVEL_ERROR, "Unknown session type: %d", - s->type); - } - else - { - /* fire up X server */ - LOG(LOG_LEVEL_INFO, "Starting X server on display %u: %s", - s->display, - dumpItemsToString(xserver_params, execvpparams, 2048)); - LOG_DEVEL_LEAKING_FDS("X server", 3, -1); - g_execvp_list((const char *)xserver_params->items[0], - xserver_params); - } - } - - /* should not get here */ - g_free(passwd_file); - list_delete(xserver_params); - LOG(LOG_LEVEL_ERROR, "A fatal error has occurred attempting " - "to start the X server on display %u, aborting connection", - s->display); -} - -/******************************************************************************/ -/* - * Simple helper process to fork a child and log errors */ -static int -fork_child( - void (*runproc)(const struct login_info *, - const struct session_parameters *, - void *closure), - const struct login_info *login_info, - const struct session_parameters *s, - pid_t group_pid, - void *closure) -{ - int pid = g_fork(); - if (pid == 0) - { - /* Child process */ - if (group_pid >= 0) - { - (void)g_setpgid(0, group_pid); - } - runproc(login_info, s, closure); - g_exit(0); - } - - if (pid < 0) - { - LOG(LOG_LEVEL_ERROR, "Fork failed [%s]", g_get_strerror()); - } - - return pid; -} - -/******************************************************************************/ -static int -process_startup_wait_time(struct session_data *sd) -{ - int rv = 0; - int robjs_count; - intptr_t robjs[10]; - unsigned int start = g_get_elapsed_ms(); - - LOG(LOG_LEVEL_INFO, "Waiting for %u ms for session to start", - g_cfg->sess.startup_wait_time); - while (1) - { - unsigned int elapsed = g_get_elapsed_ms() - start; - if (elapsed >= g_cfg->sess.startup_wait_time) - { - break; - } - - robjs_count = 0; - robjs[robjs_count++] = g_term_event; - robjs[robjs_count++] = g_sigchld_event; - - if (g_obj_wait(robjs, robjs_count, NULL, 0, - g_cfg->sess.startup_wait_time - elapsed) != 0) - { - /* should not get here */ - LOG(LOG_LEVEL_WARNING, "process_startup_wait_time: " - "Unexpected error from g_obj_wait()"); - g_sleep(100); - continue; - } - - if (g_is_wait_obj_set(g_term_event)) /* term */ - { - // Simulate success for now, but leave g_term_event set. The - // main loop will also pick up the terminate event and the - // session will be closed normally - break; - } - - if (g_is_wait_obj_set(g_sigchld_event)) /* SIGCHLD */ - { - g_reset_wait_obj(g_sigchld_event); - session_process_sigchld_event(sd); - if (sd->win_mgr < 0) - { - // Session has failed in the StartupWaitTime - // Wait for the rest of the session to finish - rv = 1; - session_send_term(sd, 1); - break; - } - } - } - - return rv; -} - -/******************************************************************************/ static enum scp_screate_status -session_start_wrapped(struct login_info *login_info, - const struct session_parameters *s, - struct session_data *sd) +session_start_preamble(struct login_info *login_info, + const struct session_parameters *s) { - int chansrv_pid; - int display_pid; - int window_manager_pid; - enum scp_screate_status status = E_SCP_SCREATE_GENERAL_ERROR; - /* Set the secondary groups before starting the session to prevent * problems on PAM-based systems (see Linux pam_setcred(3)). * If we have *BSD setusercontext() this is not done here */ @@ -693,7 +75,7 @@ session_start_wrapped(struct login_info *login_info, } #endif - if (auth_start_session(login_info->auth_info, s->display) != 0) + if (auth_start_session(login_info->auth_info, s->x11_display) != 0) { // Errors are logged by the auth module, as they are // specific to that module @@ -708,105 +90,20 @@ session_start_wrapped(struct login_info *login_info, { LOG(LOG_LEVEL_WARNING, "[session start] (display %d): setsid failed - pid %d", - s->display, g_getpid()); + s->x11_display, g_getpid()); } if (g_setlogin(login_info->username) < 0) { LOG(LOG_LEVEL_WARNING, "[session start] (display %d): setlogin failed for user %s - pid %d", - s->display, login_info->username, g_getpid()); + s->x11_display, login_info->username, g_getpid()); } #endif - /* start the X server in a new process group. - * - * We group the X server, window manager and chansrv in a single - * process group, as it allows signals to be sent to the user session - * without affecting sesexec (and vice-versa). This is particularly - * important when debugging sesexec as we don't want a SIGINT in - * the debugger to be passed to the children */ - display_pid = fork_child(start_x_server, login_info, s, 0, NULL); - if (display_pid > 0) - { - enum xwait_status xws; - xws = wait_for_xserver(login_info->uid, - g_cfg->env_names, - g_cfg->env_values, - s->display); - - if (xws != XW_STATUS_OK) - { - switch (xws) - { - case XW_STATUS_TIMED_OUT: - LOG(LOG_LEVEL_ERROR, "Timed out waiting for X server"); - break; - case XW_STATUS_FAILED_TO_START: - LOG(LOG_LEVEL_ERROR, "X server failed to start"); - break; - default: - LOG(LOG_LEVEL_ERROR, - "An error occurred waiting for the X server"); - } - status = E_SCP_SCREATE_X_SERVER_FAIL; - /* Kill it anyway in case it did start and we just failed to - * pick up on it */ - g_sigterm(display_pid); - g_waitpid(display_pid); - } - else - { - LOG(LOG_LEVEL_INFO, "X server :%d is working", s->display); - LOG(LOG_LEVEL_INFO, "Starting window manager for display :%d", - s->display); - - window_manager_pid = fork_child(start_window_manager, - login_info, s, display_pid, NULL); - if (window_manager_pid < 0) - { - g_sigterm(display_pid); - g_waitpid(display_pid); - } - else - { - utmp_login(window_manager_pid, s->display, login_info); - LOG(LOG_LEVEL_INFO, - "Starting the xrdp channel server for display :%d", - s->display); - - chansrv_pid = fork_child(start_chansrv, login_info, - s, display_pid, NULL); - - sd->win_mgr = window_manager_pid; - sd->x_server = display_pid; - sd->chansrv = chansrv_pid; - sd->start_time = time(NULL); - - if (process_startup_wait_time(sd) == 0) - { - // Tell the caller we've started - LOG(LOG_LEVEL_INFO, - "Session in progress on display :%d. Waiting until the " - "window manager (pid %d) exits to end the session", - s->display, window_manager_pid); - - status = E_SCP_SCREATE_OK; - } - else - { - LOG(LOG_LEVEL_ERROR, - "Session failed during startup wait time"); - status = E_SCP_SCREATE_SESSION_FAIL; - } - } - } - } - - return status; + return E_SCP_SCREATE_OK; } - /******************************************************************************/ enum scp_screate_status session_start(struct login_info *login_info, @@ -815,330 +112,83 @@ session_start(struct login_info *login_info, { enum scp_screate_status status = E_SCP_SCREATE_GENERAL_ERROR; /* Create the session_data struct first */ - struct session_data *sd = session_data_new(sp); - if (sd == NULL) + struct session_data *self = session_base_new(sp); + if (self == NULL) { status = E_SCP_SCREATE_NO_MEMORY; } else { - status = session_start_wrapped(login_info, sp, sd); + status = session_start_preamble(login_info, sp); if (status == E_SCP_SCREATE_OK) { - *session_data = sd; - } - else - { - *session_data = NULL; - session_data_free(sd); - } - } - - return status; -} - -/******************************************************************************/ -static int -cleanup_sockets(int uid, int display) -{ - LOG_DEVEL(LOG_LEVEL_INFO, "cleanup_sockets:"); - - char file[XRDP_SOCKETS_MAXPATH]; - int error = 0; - - g_snprintf(file, sizeof(file), CHANSRV_PORT_OUT_STR, uid, display); - if (g_file_exist(file)) - { - LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); - if (g_file_delete(file) == 0) - { - LOG(LOG_LEVEL_WARNING, - "cleanup_sockets: failed to delete %s (%s)", - file, g_get_strerror()); - error++; - } - } - - g_snprintf(file, sizeof(file), CHANSRV_PORT_IN_STR, uid, display); - if (g_file_exist(file)) - { - LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); - if (g_file_delete(file) == 0) - { - LOG(LOG_LEVEL_WARNING, - "cleanup_sockets: failed to delete %s (%s)", - file, g_get_strerror()); - error++; - } - } - - g_snprintf(file, sizeof(file), XRDP_CHANSRV_STR, uid, display); - if (g_file_exist(file)) - { - LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); - if (g_file_delete(file) == 0) - { - LOG(LOG_LEVEL_WARNING, - "cleanup_sockets: failed to delete %s (%s)", - file, g_get_strerror()); - error++; - } - } - - g_snprintf(file, sizeof(file), CHANSRV_API_STR, uid, display); - if (g_file_exist(file)) - { - LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); - if (g_file_delete(file) == 0) - { - LOG(LOG_LEVEL_WARNING, - "cleanup_sockets: failed to delete %s (%s)", - file, g_get_strerror()); - error++; - } - } - - /* the following files should be deleted by xorgxrdp - * but just in case the deletion failed */ - - g_snprintf(file, sizeof(file), XRDP_X11RDP_STR, uid, display); - if (g_file_exist(file)) - { - LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); - if (g_file_delete(file) == 0) - { - LOG(LOG_LEVEL_WARNING, - "cleanup_sockets: failed to delete %s (%s)", - file, g_get_strerror()); - error++; - } - } - - g_snprintf(file, sizeof(file), XRDP_DISCONNECT_STR, uid, display); - if (g_file_exist(file)) - { - LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); - if (g_file_delete(file) == 0) - { - LOG(LOG_LEVEL_WARNING, - "cleanup_sockets: failed to delete %s (%s)", - file, g_get_strerror()); - error++; - } - } - - return error; -} - -/******************************************************************************/ -static void -exit_status_to_str(const struct proc_exit_status *e, char buff[], int bufflen) -{ - switch (e->reason) - { - case E_PXR_STATUS_CODE: - if (e->val == 0) + status = self->vtable->start(self, login_info, sp); + if (status == E_SCP_SCREATE_OK) { - g_snprintf(buff, bufflen, "exit code zero"); + *session_data = self; } else { - g_snprintf(buff, bufflen, "non-zero exit code %d", e->val); + *session_data = NULL; + session_data_free(self); } - break; - - case E_PXR_SIGNAL: - { - char sigstr[MAXSTRSIGLEN]; - g_snprintf(buff, bufflen, "signal %s", - g_sig2text(e->val, sigstr)); } - break; - - default: - g_snprintf(buff, bufflen, "an unexpected error"); - break; } + + return status; } /******************************************************************************/ -/** - * Processes an exited child - * - * The PID of the child process is removed from the session_data. - * - * @param sd session_data for this session - * @param pid PID of exited process - * @param e Exit status of the exited process - */ -static void -process_child_exit(struct session_data *sd, - int pid, - const struct proc_exit_status *e) +void +session_data_free(struct session_data *self) { - if (pid == sd->x_server) - { - LOG(LOG_LEVEL_INFO, "X server pid %d on display :%d finished", - sd->x_server, sd->params.display); - sd->x_server = -1; - // No other action - window manager should be going soon - } - else if (pid == sd->chansrv) - { - LOG(LOG_LEVEL_INFO, - "xrdp channel server pid %d on display :%d finished", - sd->chansrv, sd->params.display); - sd->chansrv = -1; - } - else if (pid == sd->win_mgr) - { - int wm_wait_time = time(NULL) - sd->start_time; - - if (e->reason == E_PXR_STATUS_CODE && e->val == 0) - { - LOG(LOG_LEVEL_INFO, - "Window manager (pid %d, display %d) " - "finished normally in %d secs", - sd->win_mgr, sd->params.display, wm_wait_time); - } - else - { - char reason[128]; - exit_status_to_str(e, reason, sizeof(reason)); - - LOG(LOG_LEVEL_WARNING, "Window manager (pid %d, display %d) " - "exited with %s. This " - "could indicate a window manager config problem", - sd->win_mgr, sd->params.display, reason); - } - if (wm_wait_time < 10) - { - /* This could be a config issue. Log a significant error */ - LOG(LOG_LEVEL_WARNING, "Window manager (pid %d, display %d) " - "exited quickly (%d secs). This could indicate a window " - "manager config problem", - sd->win_mgr, sd->params.display, wm_wait_time); - } - - utmp_logout(sd->win_mgr, sd->params.display, e); - sd->win_mgr = -1; - - if (sd->x_server > 0) - { - LOG(LOG_LEVEL_INFO, "Terminating X server (pid %d) on display :%d", - sd->x_server, sd->params.display); - g_sigterm(sd->x_server); - } - - if (sd->chansrv > 0) - { - LOG(LOG_LEVEL_INFO, "Terminating the xrdp channel server (pid %d) " - "on display :%d", sd->chansrv, sd->params.display); - g_sigterm(sd->chansrv); - } - } - - if (!session_active(sd)) - { - cleanup_sockets(g_login_info->uid, sd->params.display); - } + session_base_destroy(self); } /******************************************************************************/ void -session_process_sigchld_event(struct session_data *sd) +session_process_sigchld_event(struct session_data *self) { - struct proc_exit_status e; - int pid; - - // Check for any finished children - while ((pid = g_waitchild(&e)) > 0) - { - process_child_exit(sd, pid, &e); - } + // The base class does this, as the base class method needs to be called + // from other base class methods. + session_base_process_sigchld_event(self); } /******************************************************************************/ unsigned int -session_active(const struct session_data *sd) +session_active(const struct session_data *self) { - return - (sd == NULL) - ? 0 - : (sd->win_mgr > 0) + (sd->x_server > 0) + (sd->chansrv > 0); + return self->vtable->active_processes(self); } /******************************************************************************/ time_t -session_get_start_time(const struct session_data *sd) +session_get_start_time(const struct session_data *self) { - return (sd == NULL) ? 0 : sd->start_time; + return self->start_time; } /******************************************************************************/ -unsigned int -session_get_connect_count(const struct session_data *sd) +const char * +session_get_display(const struct session_data *self) { - return (sd == NULL) ? 0 : sd->connect_count; + return self->display; } /******************************************************************************/ unsigned int -session_increment_connect_count(struct session_data *sd) +session_increment_connect_count(struct session_data *self) { - return (sd == NULL) ? 0 : sd->connect_count++; -} - -/******************************************************************************/ -const struct session_parameters * -session_get_parameters(const struct session_data *sd) -{ - return (sd == NULL) ? NULL : &sd->params; -} - -/******************************************************************************/ -void -session_send_term(struct session_data *sd, int wait_for_all) -{ - if (sd != NULL) - { - if (sd->win_mgr > 0) - { - // Killing the window manager only is appropriate here. - // When we process SIGCHLD for the window manager, we - // will kill other processes as appropriate - g_sigterm(sd->win_mgr); - } - - if (wait_for_all) - { - while (session_active(sd)) - { - /* Don't check SIGTERM - we shouldn't be here long */ - if (g_obj_wait(&g_sigchld_event, 1, NULL, 0, -1) != 0) - { - /* should not get here */ - LOG(LOG_LEVEL_WARNING, "session_send_term: " - "Unexpected error from g_obj_wait()"); - g_sleep(100); - } - else - { - g_reset_wait_obj(g_sigchld_event); - session_process_sigchld_event(sd); - } - } - } - } + return self->connect_count++; } /******************************************************************************/ static void -start_reconnect_script(const struct login_info *login_info, - const struct session_parameters *s, +start_reconnect_script(struct session_data *self, + const struct login_info *login_info, void *closure) { - env_set_user(login_info->uid, 0, s->display, + env_set_user(login_info->uid, 0, g_cfg->env_names, g_cfg->env_values); @@ -1160,14 +210,14 @@ start_reconnect_script(const struct login_info *login_info, LOG_DEVEL_LEAKING_FDS("reconnect script", 3, -1); LOG(LOG_LEVEL_INFO, - "Starting session reconnection script on display %d: %s", - s->display, g_cfg->reconnect_sh); + "Starting session reconnection script on display %s: %s", + self->display, g_cfg->reconnect_sh); g_execlp3(g_cfg->reconnect_sh, g_cfg->reconnect_sh, 0); /* should not get here */ LOG(LOG_LEVEL_ERROR, - "Error starting session reconnection script on display %d: %s", - s->display, g_cfg->reconnect_sh); + "Error starting session reconnection script on display %s: %s", + self->display, g_cfg->reconnect_sh); } else { @@ -1179,103 +229,63 @@ start_reconnect_script(const struct login_info *login_info, /******************************************************************************/ void -session_run_reconnect_script(const struct login_info *login_info, - const struct session_data *sd, +session_run_reconnect_script(struct session_data *self, + const struct login_info *login_info, const char *vars[]) { - if (fork_child(start_reconnect_script, - login_info, &sd->params, sd->x_server, (void *)vars) < 0) + if (session_base_fork_child(self, + login_info, + self->vtable->getpgid(self), + start_reconnect_script, + (void *)vars) < 0) { LOG(LOG_LEVEL_ERROR, "Failed to fork for session reconnection script"); } } /******************************************************************************/ -int -session_get_display_server_fd(const struct login_info *login_info, - const struct session_data *sd) +const struct session_parameters * +session_get_parameters(const struct session_data *self) { - char portname[XRDP_SOCKETS_MAXPATH]; - const char *localhost = "localhost"; // Ignored for TRANS_MODE_UNIX - int socket_mode; - - int rv = -1; - - if (sd->x_server <= 0) - { - LOG(LOG_LEVEL_ERROR, - "Request to connect to display server :%u" - " which has exited", sd->params.display); - } - else - { - switch (sd->params.type) - { - case SCP_SESSION_TYPE_XVNC: - socket_mode = TRANS_MODE_TCP; - snprintf(portname, sizeof(portname), "%u", - 5900 + sd->params.display); - break; - - case SCP_SESSION_TYPE_XVNC_UDS: - case SCP_SESSION_TYPE_XORG: - socket_mode = TRANS_MODE_UNIX; - snprintf(portname, sizeof(portname), XRDP_X11RDP_STR, - login_info->uid, (int)sd->params.display); - - break; - - default: - LOG(LOG_LEVEL_ERROR, "Unsupported session type %d for connect", - sd->params.type); - portname[0] = '\0'; - } + return self->params; +} - if (portname[0] != '\0') - { - // Use the transport library to get the fd - struct trans *t = trans_create(socket_mode, 8 * 8192, 8192); - if (t == NULL) - { - LOG(LOG_LEVEL_ERROR, "Out of memory creating transport"); - } - else if (trans_connect(t, localhost, portname, 3000) != 0) - { - LOG(LOG_LEVEL_ERROR, "Can't connect to display server :%u [%s]", - sd->params.display, - g_get_strerror()); - } - else - { - rv = t->sck; - t->sck = -1; - } - trans_delete(t); - } - } +/******************************************************************************/ +void +session_send_term(struct session_data *self, int wait_for_all) +{ + // The base class does this, as the base class method needs to be called + // from other base class methods. + session_base_send_term(self, wait_for_all); +} - return rv; +/******************************************************************************/ +int +session_get_display_server_fd(const struct session_data *self, + const struct login_info *login_info) +{ + return self->vtable->get_display_server_fd(self, login_info); } /******************************************************************************/ int -session_get_chansrv_fd(const struct login_info *login_info, - const struct session_data *sd) +session_get_chansrv_fd(const struct session_data *self, + const struct login_info *login_info) { char portname[XRDP_SOCKETS_MAXPATH]; int rv = -1; - if (sd->chansrv <= 0) + if (self->chansrv_pid <= 0) { LOG(LOG_LEVEL_ERROR, - "Request to connect to chansrv :%u" - " which has exited", sd->params.display); + "Request to connect to chansrv on display %s" + " which has exited", self->display); } else { - snprintf(portname, sizeof(portname), - XRDP_CHANSRV_STR, login_info->uid, (int)sd->params.display); + g_snprintf(portname, sizeof(portname), XRDP_CHANSRV_STR, + login_info->uid, STRIP_COLON(self->display)); // Use the transport library to get the fd struct trans *t = trans_create(TRANS_MODE_UNIX, 8192, 8192); @@ -1285,8 +295,8 @@ session_get_chansrv_fd(const struct login_info *login_info, } else if (trans_connect(t, NULL, portname, 10 * 1000) != 0) { - LOG(LOG_LEVEL_ERROR, "Can't connect to chansrv :%u [%s]", - sd->params.display, + LOG(LOG_LEVEL_ERROR, "Can't connect to chansrv on %s [%s]", + self->display, g_get_strerror()); } else diff --git a/sesman/sesexec/session.h b/sesman/sesexec/session.h index 2280500f0c..f9cafd48b1 100644 --- a/sesman/sesexec/session.h +++ b/sesman/sesexec/session.h @@ -19,7 +19,7 @@ /** * * @file session.h - * @brief Session management definitions + * @brief Session management function declarations * @author Jay Sorg, Simone Fedele * */ @@ -30,27 +30,11 @@ #include -#include "guid.h" #include "scp_application_types.h" struct login_info; struct proc_exit_status; - -/** - * Information used to start a session - */ -struct session_parameters -{ - unsigned int display; - enum scp_session_type type; - unsigned short width; - unsigned short height; - unsigned char bpp; - struct guid guid; - const char *shell; // Must not be NULL - const char *directory; // Must not be NULL -}; - +struct session_parameters; /** * Data involved in running a session (opaque type) @@ -77,112 +61,118 @@ session_start(struct login_info *login_info, struct session_data **session_data); /** - * Processes a SIGCHLD event + * Frees a session_data object * - * Any pending SIGCHLD events are processed. + * @param self session_data object * - * The PID of a failed child process is removed from the session_data. + * Do not call this until session_active() returns zero, or you + * lose the ability to track the session PIDs + */ +void +session_data_free(struct session_data *self); + +/** + * Process a SIGCHLD event for a session * - * @param sd session_data for this session - * @param pid PID of exited process - * @param e Exit status of the exited process + * Any pending SIGCHLD events are processed. + * + * @param self session_data object */ void -session_process_sigchld_event(struct session_data *sd); +session_process_sigchld_event(struct session_data *self); /** * Returns a count of active processes in the session * - * @param sd session_data for this session + * @param self session_data object */ unsigned int -session_active(const struct session_data *sd); +session_active(const struct session_data *self); /** - * Returns the start time for an active session + * Get the session start time * - * @param sd session_data for this session + * @param self session_data object * @return session start time */ time_t -session_get_start_time(const struct session_data *sd); +session_get_start_time(const struct session_data *self); /** - * Returns the connect count for an active session - * @param sd session_data for this session - * @return connect count + * Get a pointer to the session display name + * + * @param self session_data object + * @return session name (":n" or "wayland-n") + * + * A session name is set by a successful call to session_start() */ -unsigned int -session_get_connect_count(const struct session_data *sd); +const char * +session_get_display(const struct session_data *self); /** * Increment the connect count for an active session - * @param sd session_data for this session + * @param self session_data object * @return Pre-increment value of the connect count */ unsigned int -session_increment_connect_count(struct session_data *sd); +session_increment_connect_count(struct session_data *self); + +/** + * Runs the reconnect script for the session + * @param self session_data object + * @param login_info Login info for the session + * @param vars environment variables for the reconnect script + * + * The vars parameter points to an array of strings in pairs. The + * first string in the pair is the name of an environment variable to + * set, and the second string is the value + */ +void +session_run_reconnect_script(struct session_data *self, + const struct login_info *login_info, + const char *vars[]); /** * Returns the parameters used to start the session * - * @param sd session_data for this session + * @param self session_data object * @return Pointer to parameters * * The pointed-to data returned must not be modified in * any way. */ const struct session_parameters * -session_get_parameters(const struct session_data *sd); +session_get_parameters(const struct session_data *self); /*** * Ask a session to terminate by signalling the window manager * - * @param sd session_data for this session + * @param self session_data object * @param wait_for_all != 0 to wait for all processes in the session * to terminate */ void -session_send_term(struct session_data *sd, int wait_for_all); +session_send_term(struct session_data *self, int wait_for_all); /** - * Frees a session_data object - * - * @param sd session_data for this session + * Get the session display server file descriptor * - * Do not call this until session_active() returns zero, or you - * lose the ability to track the session PIDs - */ -void -session_data_free(struct session_data *session_data); - -/** - * Runs the reconnect script for the session + * @param self session_data_object * @param login_info Login info for the session - * @param sd Session data for the session - * @param vars environment variables for the reconnect script - * - * The vars parameter points to an array of strings in pairs. The - * first string in the pair is the name of an environment variable to - * set, and the second string is the value - */ -void -session_run_reconnect_script(const struct login_info *login_info, - const struct session_data *sd, - const char *vars[]); - -/** - * Connects a file descriptor to the display server + * @return display server fd, or -1 */ int -session_get_display_server_fd(const struct login_info *login_info, - const struct session_data *sd); +session_get_display_server_fd(const struct session_data *self, + const struct login_info *login_info); /** * Connects a file descriptor to chansrv + * @param self session_data object + * @param login_info Login info for the session + * @return file descriptor connected to chansrv, or -1 */ int -session_get_chansrv_fd(const struct login_info *login_info, - const struct session_data *sd); +session_get_chansrv_fd(const struct session_data *self, + const struct login_info *login_info); #endif // SESSION_H diff --git a/sesman/sesexec/session_base.c b/sesman/sesexec/session_base.c new file mode 100644 index 0000000000..9cbf7c93fa --- /dev/null +++ b/sesman/sesexec/session_base.c @@ -0,0 +1,379 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2025 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * + * @file session_base.c + * @brief Session object base class and utilities + * @author Jay Sorg, Simone Fedele + * + */ + +//#include + +#if defined(HAVE_CONFIG_H) +#include "config_ac.h" +#endif + +#include "session_base.h" +#include "session_labwc.h" +#include "session_x11.h" +#include "session_parameters.h" + +#include "sesman_config.h" +#include "env.h" +#include "log.h" +#include "login_info.h" +#include "os_calls.h" +#include "sesexec.h" +#include "string_calls.h" +#include "xrdp_sockets.h" + +/******************************************************************************/ +struct session_data * +session_base_new(const struct session_parameters *sp) +{ + struct session_data *self = NULL; + + // Make a copy of the session parameters for the new object + struct session_parameters *sp_copy = copy_session_parameters(sp); + if (sp_copy == NULL) + { + LOG(LOG_LEVEL_ERROR, + "Out of memory allocating session parameter block"); + } + else if (SCP_SESSION_TYPE_IS_X11(sp->type)) + { + struct session_data_x11 *self_x11 = session_x11_new(); + if (self_x11 == NULL) + { + LOG(LOG_LEVEL_ERROR, "Out of memory allocating X11 session object"); + } + else + { + self = &self_x11->base; + } + } + else if (SCP_SESSION_TYPE_IS_LABWC(sp->type)) + { + struct session_data_labwc *self_labwc = session_labwc_new(); + if (self_labwc == NULL) + { + LOG(LOG_LEVEL_ERROR, + "Out of memory allocating labwc session object"); + } + else + { + self = &self_labwc->base; + } + } + else + { + LOG(LOG_LEVEL_ERROR, + "Unsupported session type %d", (int)sp->type); + } + + if (self != NULL) + { + self->params = sp_copy; + memset(self->display, '\0', sizeof(self->display)); + self->chansrv_pid = -1; + self->start_time = 0; + self->connect_count = 0; + } + else + { + free(sp_copy); + } + + return self; +} + +/******************************************************************************/ +void +session_base_destroy(struct session_data *self) +{ + if (self != NULL) + { + // Call the (optional) derived class destructor + if (self->vtable->free != NULL) + { + self->vtable->free(self); + } +#ifdef USE_DEVEL_LOGGING + if (self->chansrv_pid > 0) + { + LOG_DEVEL(LOG_LEVEL_WARNING, + "Freeing session data with valid chansrv PID %d", + self->chansrv_pid); + } +#endif + + free(self->params); + free(self); + } +} + +/******************************************************************************/ +void +session_base_start_chansrv(struct session_data *self, + const struct login_info *login_info, + void *closure /* unused */) +{ + struct list *chansrv_params = list_create(); + const char *exe_path = XRDP_SBIN_PATH "/xrdp-chansrv"; + + if (chansrv_params != NULL) + { + chansrv_params->auto_free = 1; + if (!list_add_strdup(chansrv_params, exe_path)) + { + list_delete(chansrv_params); + chansrv_params = NULL; + } + } + + if (chansrv_params == NULL) + { + LOG(LOG_LEVEL_ERROR, "Out of memory starting chansrv"); + } + else + { + env_set_user(login_info->uid, 0, + g_cfg->env_names, + g_cfg->env_values); + + LOG_DEVEL_LEAKING_FDS("chansrv", 3, -1); + + /* executing chansrv */ + g_execvp_list(exe_path, chansrv_params); + + /* should not get here */ + list_delete(chansrv_params); + } +} + +/******************************************************************************/ +/* + * Simple helper process to fork a child and log errors */ +int +session_base_fork_child( + struct session_data *self, + const struct login_info *login_info, + pid_t group_pid, + void (*runproc)(struct session_data *self, + const struct login_info *, + void *closure), + void *closure) +{ + int pid = g_fork(); + if (pid == 0) + { + /* Child process */ + if (group_pid >= 0) + { + (void)g_setpgid(0, group_pid); + } + runproc(self, login_info, closure); + g_exit(0); + } + + if (pid < 0) + { + LOG(LOG_LEVEL_ERROR, "Fork failed [%s]", g_get_strerror()); + } + + return pid; +} + +/******************************************************************************/ +int +session_base_process_startup_wait_time(struct session_data *self) +{ + int rv = 0; + int robjs_count; + intptr_t robjs[10]; + unsigned int start = g_get_elapsed_ms(); + + LOG(LOG_LEVEL_INFO, "Waiting for %u ms for session to start", + g_cfg->sess.startup_wait_time); + while (1) + { + unsigned int elapsed = g_get_elapsed_ms() - start; + if (elapsed >= g_cfg->sess.startup_wait_time) + { + break; + } + + robjs_count = 0; + robjs[robjs_count++] = g_term_event; + robjs[robjs_count++] = g_sigchld_event; + + if (g_obj_wait(robjs, robjs_count, NULL, 0, + g_cfg->sess.startup_wait_time - elapsed) != 0) + { + /* should not get here */ + LOG(LOG_LEVEL_WARNING, "process_startup_wait_time: " + "Unexpected error from g_obj_wait()"); + g_sleep(100); + continue; + } + + if (g_is_wait_obj_set(g_term_event)) /* term */ + { + // Simulate success for now, but leave g_term_event set. The + // main loop will also pick up the terminate event and the + // session will be closed normally + break; + } + + if (g_is_wait_obj_set(g_sigchld_event)) /* SIGCHLD */ + { + g_reset_wait_obj(g_sigchld_event); + session_base_process_sigchld_event(self); + if (!self->vtable->main_sess_proc_active(self)) + { + // Session has started to fail in the StartupWaitTime + // Wait for the rest of the session to finish + rv = 1; + session_base_send_term(self, 1); + break; + } + } + } + + return rv; +} + +/******************************************************************************/ +int +session_base_cleanup_sockets(struct session_data *self) +{ + LOG_DEVEL(LOG_LEVEL_INFO, "session_base_cleanup_sockets:"); + + char file[XRDP_SOCKETS_MAXPATH]; + int error = 0; + + int uid = g_login_info->uid; + const char *display = STRIP_COLON(self->display); + + g_snprintf(file, sizeof(file), CHANSRV_PORT_OUT_STR, uid, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "session_base_cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "session_base_cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + + g_snprintf(file, sizeof(file), CHANSRV_PORT_IN_STR, uid, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "session_base_cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "session_base_cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + + g_snprintf(file, sizeof(file), XRDP_CHANSRV_STR, uid, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "session_base_cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "session_base_cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + + g_snprintf(file, sizeof(file), CHANSRV_API_STR, uid, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "session_base_cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "session_base_cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + + return error; +} + +/******************************************************************************/ +void +session_base_process_sigchld_event(struct session_data *self) +{ + struct proc_exit_status e; + int pid; + + // Check for any finished children + while ((pid = g_waitchild(&e)) > 0) + { + if (pid == self->chansrv_pid) + { + LOG(LOG_LEVEL_INFO, + "xrdp channel server pid %d on display %s finished", + self->chansrv_pid, self->display); + self->chansrv_pid = -1; + } + else + { + self->vtable->process_child_exit(self, pid, &e); + } + } +} + +/******************************************************************************/ +void +session_base_send_term(struct session_data *self, int wait_for_all) +{ + self->vtable->send_term(self); + + if (wait_for_all) + { + while (self->vtable->active_processes(self) > 0) + { + /* Don't check SIGTERM - we shouldn't be here long */ + if (g_obj_wait(&g_sigchld_event, 1, NULL, 0, -1) != 0) + { + /* should not get here */ + LOG(LOG_LEVEL_WARNING, "session_send_term: " + "Unexpected error from g_obj_wait()"); + g_sleep(100); + } + else + { + g_reset_wait_obj(g_sigchld_event); + session_base_process_sigchld_event(self); + } + } + } +} diff --git a/sesman/sesexec/session_base.h b/sesman/sesexec/session_base.h new file mode 100644 index 0000000000..845cca9a2b --- /dev/null +++ b/sesman/sesexec/session_base.h @@ -0,0 +1,239 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2013 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * + * @file session_base.h + * @brief Base class for session objects + * @author Jay Sorg, Simone Fedele + * + */ + + +#ifndef SESSION_BASE_H +#define SESSION_BASE_H + +#include +#include + +#include "arch.h" +#include "scp_application_types.h" + +struct session_data; +struct session_parameters; +struct login_info; +struct proc_exit_status; + +/** + * Methods which can be used with a session_data item. + */ +struct session_data_vtable +{ + /** + * Start a session + * @param baseobj session_data object + * @param login_info info for logged in user + * @param s Session parameters + * @return creation status + */ + enum scp_screate_status + (*start)(struct session_data *baseobj, + struct login_info *login_info, + const struct session_parameters *s); + + /** + * Frees session object derived class members + * + * @param baseobj session_data object + */ + void + (*free)(struct session_data *baseobj); + + /** + * Processes a child exit event reaped from a SIGCHLD + * + * @param baseobj session_data object + * @param pid PID of exited process + * @param e Exit status of the exited process + */ + void + (*process_child_exit)(struct session_data *baseobj, + int pid, + const struct proc_exit_status *e); + + /** + * Returns a count of active processes in the session + * + * @param baseobj session_data object + */ + unsigned int + (*active_processes)(const struct session_data *baseobj); + + /** + * Is the main session process (Window manager, whatever) still active? + * + * This can be true while the active_processes is > 0, if the session + * has started to fail. + * @param baseobj session_data object + */ + int + (*main_sess_proc_active)(const struct session_data *baseobj); + + /** + * Gets the process group ID for the session, or -1 + * + * This is the process group PID (if any) the session is started under + * @param baseobj session_data object + * @return process group PID, or -1 + */ + pid_t + (*getpgid)(const struct session_data *baseobj); + + /*** + * Ask a session to terminate by signalling the window manager + * + * @param baseobj session_data object + */ + void + (*send_term)(struct session_data *baseobj); + + /** + * Connects a file descriptor to the display server + * @param baseobj session_data object + * @param login_info Login info for the session + * @return file descriptor connected to chansrv, or -1 + */ + int + (*get_display_server_fd)(const struct session_data *baseobj, + const struct login_info *login_info); + +}; + +/** + * Data involved in running a session (base class) + */ +struct session_data +{ + const struct session_data_vtable *vtable; + struct session_parameters *params; + char display[SCP_DISPLAY_NAME_SIZE]; // Set by derived class start() + pid_t chansrv_pid; // Set by derived class start() + time_t start_time; // Set by derived class start() + unsigned int connect_count; +}; + +/** + * Creates a new session_data object (factory method) + * @param sp Session parameters + * @return session_data object, or NULL + */ +struct session_data * +session_base_new(const struct session_parameters *sp); + +/** + * Destroys a session_data object + * @param self Object to destroy + */ +void +session_base_destroy(struct session_data *self); + +/** + * Starts chansrv (base class method) + * + * @param self session data object + * @param login_info Login info for user + * @param closure (unused) Value passed to session_base_fork_child. + * + * Use this function with session_base_fork_child to start chansrv + * + * This function must be called (if appropriate) from the start + * method of the derived class + */ +void +session_base_start_chansrv(struct session_data *self, + const struct login_info *login_info, + void *closure); + +/** + * Starts a child process (base class method) + * + * @param self Session object + * @param login_info Login info for user + * @param group_pid If >= 0, group pid to run proc under + * @param runproc Function to run in child (see e.g. + * session_base_start_chansrv) + * @param closure Value passed to runproc + * + * @return PID of child. + */ +int +session_base_fork_child( + struct session_data *self, + const struct login_info *login_info, + pid_t group_pid, + void (*runproc)(struct session_data *self, + const struct login_info *, + void *closure), + void *closure); + +/** + * Processes the startup wait time (base class method) + * + * @param self Session object + * + * @return != 0 if the session has failed in the startup wait time + * + * The startup wait time is obtained from the global configuration. + */ +int +session_base_process_startup_wait_time(struct session_data *self); + +/** + * Cleanup sockets allocated to the base class (base class method) + * + * Delete sockets, possibly left from a previous run + * @param self Base object of session object + * @return Error count + */ +int +session_base_cleanup_sockets(struct session_data *self); + +/** + * Process a SIGCHLD event for a session (base class method) + * + * Any pending SIGCHLD events are processed. + * + * @param self session_data object + */ +void +session_base_process_sigchld_event(struct session_data *self); + + +/*** + * Ask a session to terminate by signalling the window manager + * + * This is a base class method + * + * @param self session_data object + * @param wait_for_all != 0 to wait for all processes in the session + * to terminate + */ +void +session_base_send_term(struct session_data *self, int wait_for_all); + +#endif // SESSION_BASE_H diff --git a/sesman/sesexec/session_labwc.c b/sesman/sesexec/session_labwc.c new file mode 100644 index 0000000000..b0b17d4b52 --- /dev/null +++ b/sesman/sesexec/session_labwc.c @@ -0,0 +1,982 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2025 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * + * @file session_labwc.h + * @brief Derived class for labwc session objects + * @author Matt Burt + * + */ + +#if defined(HAVE_CONFIG_H) +#include "config_ac.h" +#endif + +#include +#include + +#include "arch.h" +#include "session_labwc.h" +#include "session_parameters.h" + +#include "sesman_auth.h" +#include "sesman_config.h" +#include "display_server_util.h" +#include "env.h" +#include "guid.h" +#include "list.h" +#include "log.h" +#include "login_info.h" +#include "os_calls.h" +#include "sesexec.h" +#include "sessionrecord.h" +#include "string_calls.h" +#include "trans.h" +#include "xauth.h" +#include "xrdp_sockets.h" + +/******************************************************************************/ +/** + * Creates a string consisting of all parameters that is hosted in the param list + * @param self + * @param outstr, allocate this buffer before you use this function + * @param len the allocated len for outstr + * @return + */ +static char * +dumpItemsToString(struct list *self, char *outstr, int len) +{ + int index; + int totalLen = 0; + + g_memset(outstr, 0, len); + if (self->count == 0) + { + LOG_DEVEL(LOG_LEVEL_TRACE, "List is empty"); + } + + for (index = 0; index < self->count; index++) + { + /* +1 = one space*/ + totalLen = totalLen + g_strlen((char *)list_get_item(self, index)) + 1; + + if (len > totalLen) + { + g_strcat(outstr, (char *)list_get_item(self, index)); + g_strcat(outstr, " "); + } + } + + return outstr ; +} + +/******************************************************************************/ +static void +start_window_manager(struct session_data *baseobj, + const struct login_info *login_info, + void *closure /* unused */) +{ + char text[256]; + const struct session_parameters *s = baseobj->params; + + env_set_user(login_info->uid, + 0, + g_cfg->env_names, + g_cfg->env_values); + + auth_set_env(login_info->auth_info); + LOG_DEVEL_LEAKING_FDS("window manager", 3, -1); + + if (s->directory[0] != '\0') + { + if (g_cfg->sec.allow_alternate_shell) + { + g_set_current_dir(s->directory); + } + else + { + LOG(LOG_LEVEL_WARNING, + "Directory change to %s requested, but not " + "allowed by AllowAlternateShell config value.", + s->directory); + } + } + + if (s->shell[0] != '\0') + { + if (g_cfg->sec.allow_alternate_shell) + { + if (g_strchr(s->shell, ' ') != 0 || g_strchr(s->shell, '\t') != 0) + { + LOG(LOG_LEVEL_INFO, + "Using user requested window manager on " + "display :%d with embedded arguments using a shell: %s", + s->x11_display, s->shell); + const char *argv[] = {"sh", "-c", s->shell, NULL}; + g_execvp("/bin/sh", (char **)argv); + } + else + { + LOG(LOG_LEVEL_INFO, + "Using user requested window manager on " + "display :%d : %s", s->x11_display, s->shell); + g_execlp3(s->shell, s->shell, 0); + } + } + else + { + LOG(LOG_LEVEL_WARNING, + "Shell %s requested by user, but not allowed by " + "AllowAlternateShell config value.", + s->shell); + } + } + else + { + LOG(LOG_LEVEL_DEBUG, "The user session on display :%d did not" + " request a specific window manager", s->x11_display); + } + + /* try to execute user window manager if enabled */ + if (g_cfg->enable_user_wm) + { + g_snprintf(text, sizeof(text), "%s/%s", + g_getenv("HOME"), g_cfg->user_wm); + if (g_file_exist(text)) + { + LOG(LOG_LEVEL_INFO, + "Using window manager on display :%d from user" + " home directory: %s", s->x11_display, text); + g_execlp3(text, g_cfg->user_wm, 0); + } + else + { + LOG(LOG_LEVEL_DEBUG, + "The user home directory window manager configuration " + "is enabled but window manager program does not exist: %s", + text); + } + } + + LOG(LOG_LEVEL_INFO, + "Using the default window manager on display :%d : %s", + s->x11_display, g_cfg->default_wm); + g_execlp3(g_cfg->default_wm, g_cfg->default_wm, 0); + + /* still a problem starting window manager just start xterm */ + LOG(LOG_LEVEL_WARNING, + "No window manager on display :%d started, " + "so falling back to starting xterm for user debugging", + s->x11_display); + g_execlp3("xterm", "xterm", 0); + + /* should not get here */ + LOG(LOG_LEVEL_ERROR, "A fatal error has occurred attempting to start " + "the window manager on display :%d, aborting connection", + s->x11_display); +} + +/*****************************************************************************/ +/* + * Opens a log file on stdout and stderr + * @param name Unqualified name + * @return != 0 for error + * + * @pre Current directory is $HOME + * + * The file is either opened or not. If not, an error is logged on the + * standard mechanisms + */ +static int +redirect_stdout_stderr(const char *name) +{ + char path[256]; + char *log_path; + int rv; + + rv = 1; + // Use the log path if it exists + if ((log_path = g_cfg->labwc.log_file_path) != NULL && log_path[0] != '\0') + { + char uidstr[64]; + char username[64]; + const struct info_string_tag map[] = + { + {'u', uidstr}, + {'U', username}, + INFO_STRING_END_OF_LIST + }; + + int uid = g_getuid(); + g_snprintf(uidstr, sizeof(uidstr), "%d", uid); + if (g_getlogin(username, sizeof(username)) != 0) + { + /* Fall back to UID */ + g_strncpy(username, uidstr, sizeof(username) - 1); + } + + (void)g_format_info_string(path, sizeof(path), log_path, map); + if (g_create_path(path)) + { + rv = 0; + } + } + else if ((log_path = g_getenv("XDG_DATA_HOME")) != NULL && + log_path[0] != '\0') + { + g_snprintf(path, sizeof(path), "%s/xrdp", log_path); + if (g_create_path(path)) + { + rv = 0; + } + } + + // Always fall back to the home directory + if (rv != 0) + { + strlcpy(path, ".local/share/xrdp", sizeof(path)); + if (g_create_path(path)) + { + rv = 0; + } + } + + if (rv == 0) + { + /* Add the name to the end */ + unsigned int len = strlen(path); + if (len < sizeof(path)) + { + g_snprintf(path + len, sizeof(path) - len, "/%s", name); + } + + int fd = g_file_open_rw(path); + if (fd >= 0) + { + g_file_duplicate_on(fd, 1); + g_file_duplicate_on(fd, 2); + g_file_close(fd); + } + else + { + LOG(LOG_LEVEL_ERROR, "Can't open %s for writing [%s]", + path, g_get_strerror()); + rv = 1; + } + } + return rv; +} + +/******************************************************************************/ +/* Either execs wayvnc, or returns */ +static void +start_wayvnc(struct session_data *baseobj, + const struct login_info *login_info, + void *closure /* unused */) +{ + char execvpparams[2048]; + char sockname[XRDP_SOCKETS_MAXPATH]; + + env_set_user(login_info->uid, + 0, + g_cfg->env_names, + g_cfg->env_values); + + /* Add environment variables for wayvnc */ + auth_set_env(login_info->auth_info); + + /* get path of wayvnc from config */ + const char *wayvnc = g_cfg->labwc.wayvnc_exe; + if (wayvnc == NULL || wayvnc[0] == '\0') + { + wayvnc = "wayvnc"; + } + + /* Get the VNC socket name */ + g_snprintf(sockname, sizeof(sockname), XRDP_X11RDP_STR, + login_info->uid, baseobj->display); + if (g_file_exist(sockname)) + { + (void)g_file_delete(sockname); + } + + struct list *params = list_create(); + if (params == NULL || + (!(params->auto_free = 1)) || // Just set 'auto_free' inline + !list_add_strdup_multi(params, + wayvnc, + "-u", + sockname, + NULL)) + { + LOG(LOG_LEVEL_ERROR, "Out of memory allocating wayvnc params"); + } + else + { + /* fire up wayvnc */ + LOG(LOG_LEVEL_INFO, "Starting wayvnc: %s", + dumpItemsToString(params, execvpparams, 2048)); + LOG_DEVEL_LEAKING_FDS("wayvnc", 3, -1); + + /* Logging? */ + if (g_cfg->labwc.enable_wayvnc_log) + { + char name[64]; + g_snprintf(name, sizeof(name), + "wayvnc.%s.log", baseobj->display); + redirect_stdout_stderr(name); + (void)list_add_strdup_multi(params, "-L", "debug", NULL); + } + + g_execvp_list((const char *)params->items[0], + params); + + /* should not get here */ + LOG(LOG_LEVEL_ERROR, "A fatal error has occurred attempting " + "to start wayvnc [%s]", g_get_strerror()); + } + list_delete(params); +} + +/******************************************************************************/ +/* Either execs labwc, or returns */ +static void +start_labwc(struct session_data *baseobj, + const struct login_info *login_info, + void *closure /* unused */) +{ + char execvpparams[2048]; + + env_set_user(login_info->uid, + 0, + g_cfg->env_names, + g_cfg->env_values); + + /* Add basic environment variables for labwc */ + auth_set_env(login_info->auth_info); + + // XDG_RUNTIME_DIR is needed to store the WAYLAND_DISPLAY name + const char *xdg_runtime_dir = g_getenv("XDG_RUNTIME_DIR"); + if (xdg_runtime_dir == NULL || xdg_runtime_dir[0] == '\0') + { + LOG(LOG_LEVEL_ERROR, "XDG_RUNTIME_DIR is not defined"); + } + else if (!g_directory_exist(xdg_runtime_dir)) + { + LOG(LOG_LEVEL_ERROR, "XDG_RUNTIME_DIR [%s] is not valid", + xdg_runtime_dir); + } + else + { + /* Add other environment variables */ + // ...Run with no backing hardware + g_setenv("WLR_BACKENDS", "headless", 1); + // ...Always have an output + g_setenv("LABWC_FALLBACK_OUTPUT", "NOOP-fallback", 1); + // ...Update dbus-daemon session variables + // (needed if a session is active) + g_setenv("LABWC_UPDATE_ACTIVATION_ENV", "1", 1); + + /* get path of labwc from config */ + const char *labwc = g_cfg->labwc.labwc_exe; + if (labwc == NULL || labwc[0] == '\0') + { + labwc = "labwc"; + } + struct list *params = list_create(); + if (params == NULL || + (!(params->auto_free = 1)) || // Just set 'auto_free' inline + !list_add_strdup_multi(params, + labwc, + "--startup", + XRDP_LIBEXEC_PATH "/labwc-getenv", + NULL)) + { + LOG(LOG_LEVEL_ERROR, "Out of memory allocating labwc params"); + } + else + { + /* fire up labwc */ + LOG(LOG_LEVEL_INFO, "Starting labwc: %s", + dumpItemsToString(params, execvpparams, 2048)); + LOG_DEVEL_LEAKING_FDS("labwc", 3, -1); + + /* Logging? */ + if (g_cfg->labwc.enable_labwc_log) + { + redirect_stdout_stderr("labwc.log"); + (void)list_add_strdup_multi(params, "-d", NULL); + } + + g_execvp_list((const char *)params->items[0], + params); + + /* should not get here */ + LOG(LOG_LEVEL_ERROR, "A fatal error has occurred attempting " + "to start labwc [%s]", g_get_strerror()); + } + list_delete(params); + } +} + +/******************************************************************************/ +/** + * Used by the labwc waiter to set environment variables + * @param name Name of environment variable to set + * @param value Value to give 'name' + * @param closure Used to locate the labwc session object + */ +static void +labwc_setenvvar(const char *name, + const char *value, + void *closure) +{ + // Cast the closure to a pointer to the labwc session object + struct session_data_labwc *self = (struct session_data_labwc *)closure; + + /* Add variables to the configured environment */ + if (!list_add_strdup(g_cfg->env_names, name) || + !list_add_strdup(g_cfg->env_values, value)) + { + LOG(LOG_LEVEL_ERROR, "Out of memory adding %s to env", name); + } + else + { + // Check to set the global display name for the object + if (strcmp(name, "WAYLAND_DISPLAY") == 0) + { + strlcpy(self->base.display, value, sizeof(self->base.display)); + } + } +} + +/******************************************************************************/ +/** + * Waits for labwc to start using a helper prog + * + * @param self session_data_labwc object + * @param login_info Login information + * @param env_names List of environment variables for the helper (names) + * @param env_values List of environment variables for the helper (values) + * + * @post If DS_STATUS_OK is returned, the environment will be modified, and + * self->base.name should be set to the WAYLAND_DISPLAY + * + */ +static enum display_server_status +wait_for_labwc(struct session_data_labwc *self, + struct login_info *login_info, + const struct list *env_names, + const struct list *env_values) +{ + enum display_server_status rv = DS_STATUS_MISC_ERROR; + struct list *cmd = list_create(); + if (cmd == NULL || + (!(cmd->auto_free = 1)) || // Just set 'auto_free' inline + !list_add_strdup_multi(cmd, + XRDP_LIBEXEC_PATH "/waitforw", + NULL)) + { + LOG(LOG_LEVEL_ERROR, "Out of memory running waitforw"); + } + else + { + rv = wait_for_display_server(login_info, + env_names, env_values, cmd, + labwc_setenvvar, self); + } + + list_delete(cmd); + return rv; +} + +/******************************************************************************/ +static enum scp_screate_status +start(struct session_data *baseobj, + struct login_info *login_info, + const struct session_parameters *s) +{ + int chansrv_pid; + int labwc_pid; + int wayvnc_pid; + int window_manager_pid; + enum scp_screate_status status = E_SCP_SCREATE_GENERAL_ERROR; + + // Downcast the base object pointer to a pointer to the labwc session object + struct session_data_labwc *self = (struct session_data_labwc *)baseobj; + + /* start labwc in a new process group. + * + * We group all the session processes in a single process group, as + * it allows signals to be sent to the user session without affecting + * sesexec (and vice-versa). This is particularly important when + * debugging sesexec as we don't want a SIGINT in the debugger to + * be passed to the children */ + labwc_pid = session_base_fork_child(&self->base, login_info, 0, + start_labwc, NULL); + if (labwc_pid > 0) + { + enum display_server_status dss; + dss = wait_for_labwc(self, login_info, + g_cfg->env_names, g_cfg->env_values); + + if (dss != DS_STATUS_OK) + { + switch (dss) + { + case DS_STATUS_TIMED_OUT: + LOG(LOG_LEVEL_ERROR, "Timed out waiting for labwc"); + break; + case DS_STATUS_FAILED_TO_START: + LOG(LOG_LEVEL_ERROR, "labwc failed to start"); + break; + default: + LOG(LOG_LEVEL_ERROR, + "An error occurred waiting for labwc"); + } + status = E_SCP_SCREATE_LABWC_FAIL; + /* Kill it anyway in case it did start and we just failed to + * pick up on it */ + g_sigterm(labwc_pid); + g_waitpid(labwc_pid); + } + else if (self->base.display[0] == '\0') + { + LOG(LOG_LEVEL_ERROR, "labwc failed to set WAYLAND_DISPLAY"); + status = E_SCP_SCREATE_LABWC_FAIL; + g_sigterm(labwc_pid); + g_waitpid(labwc_pid); + } + else + { + LOG(LOG_LEVEL_INFO, "labwc is working on %s", self->base.display); + + LOG(LOG_LEVEL_INFO, "Starting wayvnc for display %s", + self->base.display); + + wayvnc_pid = session_base_fork_child(baseobj, login_info, + labwc_pid, start_wayvnc, + NULL); + if (wayvnc_pid < 0) + { + status = E_SCP_SCREATE_WAYVNC_FAIL; + g_sigterm(labwc_pid); + g_waitpid(labwc_pid); + } + else + { + LOG(LOG_LEVEL_INFO, "Starting window manager for display %s", + self->base.display); + + window_manager_pid = session_base_fork_child(baseobj, + login_info, + labwc_pid, start_window_manager, + NULL); + if (window_manager_pid < 0) + { + g_sigterm(labwc_pid); + g_waitpid(labwc_pid); + g_sigterm(wayvnc_pid); + g_waitpid(wayvnc_pid); + } + else + { + utmp_login(window_manager_pid, self->base.display, + login_info); + LOG(LOG_LEVEL_INFO, + "Starting the xrdp channel server for display %s", + self->base.display); + + chansrv_pid = session_base_fork_child( + baseobj, login_info, labwc_pid, + session_base_start_chansrv, NULL); + + self->win_mgr_pid = window_manager_pid; + self->labwc_pid = labwc_pid; + self->wayvnc_pid = wayvnc_pid; + + // Set the rest of the base class member variables we + // are responsible for + self->base.chansrv_pid = chansrv_pid; + self->base.start_time = time(NULL); + + if (session_base_process_startup_wait_time(&self->base) == 0) + { + // Tell the caller we've started + LOG(LOG_LEVEL_INFO, + "Session in progress on display %s. Waiting until the " + "window manager (pid %d) exits to end the session", + self->base.display, window_manager_pid); + + status = E_SCP_SCREATE_OK; + } + else + { + LOG(LOG_LEVEL_ERROR, + "Session failed during startup wait time"); + status = E_SCP_SCREATE_SESSION_FAIL; + } + } + } + } + } + + return status; +} + +/******************************************************************************/ +static void +sess_free(struct session_data *baseobj) +{ + // Downcast the base object pointer to a pointer to the labwc session object + struct session_data_labwc *self = (struct session_data_labwc *)baseobj; + + if (self != NULL) + { +#ifdef USE_DEVEL_LOGGING + if (self->labwc_pid > 0) + { + LOG_DEVEL(LOG_LEVEL_WARNING, + "Freeing session data with valid labwc PID %d", + self->labwc_pid); + } + if (self->wayvnc_pid > 0) + { + LOG_DEVEL(LOG_LEVEL_WARNING, + "Freeing session data with valid wayvnc PID %d", + self->wayvnc_pid); + } +#endif + } +} + + +/******************************************************************************/ +static int +cleanup_sockets(struct session_data_labwc *self) +{ + LOG_DEVEL(LOG_LEVEL_INFO, "cleanup_sockets:"); + + char file[XRDP_SOCKETS_MAXPATH]; + + int uid = g_login_info->uid; + char *display = STRIP_COLON(self->base.display); + + // Cleanup sockets used by the base display class + int error = session_base_cleanup_sockets(&self->base); + + if (self->base.params->type == SCP_SESSION_TYPE_LABWC_OVER_VNC) + { + /* Clean up the VNC socket */ + g_snprintf(file, sizeof(file), XRDP_X11RDP_STR, uid, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + } + + return error; +} + +/******************************************************************************/ +static void +exit_status_to_str(const struct proc_exit_status *e, char buff[], int bufflen) +{ + switch (e->reason) + { + case E_PXR_STATUS_CODE: + if (e->val == 0) + { + g_snprintf(buff, bufflen, "exit code zero"); + } + else + { + g_snprintf(buff, bufflen, "non-zero exit code %d", e->val); + } + break; + + case E_PXR_SIGNAL: + { + char sigstr[MAXSTRSIGLEN]; + g_snprintf(buff, bufflen, "signal %s", + g_sig2text(e->val, sigstr)); + } + break; + + default: + g_snprintf(buff, bufflen, "an unexpected error"); + break; + } +} + +/******************************************************************************/ +static unsigned int +active_processes(const struct session_data *baseobj) +{ + // Downcast the base object pointer to a pointer to the labwc session object + struct session_data_labwc *self = (struct session_data_labwc *)baseobj; + + return (self->base.chansrv_pid > 0) + (self->labwc_pid > 0) + + (self->wayvnc_pid > 0) + (self->win_mgr_pid > 0); +} + +/******************************************************************************/ +/** + * Processes an exited child + * + * The PID of the child process is removed from the session_data. + * + * @param baseobj session_data object + * @param pid PID of exited process + * @param e Exit status of the exited process + */ +static void +process_child_exit(struct session_data *baseobj, + int pid, + const struct proc_exit_status *e) +{ + // Downcast the base object pointer to a pointer to the labwc session object + struct session_data_labwc *self = (struct session_data_labwc *)baseobj; + + if (pid == self->labwc_pid) + { + // Don't log the exit if we haven't got as far as registering + // the display name + if (baseobj->display[0] != '\0') + { + LOG(LOG_LEVEL_INFO, "labwc pid %d on display %s finished", + self->labwc_pid, baseobj->display); + } + self->labwc_pid = -1; + // No other action - window manager should be going soon + } + else if (pid == self->wayvnc_pid) + { + LOG(LOG_LEVEL_INFO, "wayvnc pid %d on display %s finished", + self->wayvnc_pid, baseobj->display); + self->wayvnc_pid = -1; + // No other action - window manager should be going soon + } + else if (pid == self->win_mgr_pid) + { + int wm_wait_time = time(NULL) - self->base.start_time; + + if (e->reason == E_PXR_STATUS_CODE && e->val == 0) + { + LOG(LOG_LEVEL_INFO, + "Window manager (pid %d, display %s) " + "finished normally in %d secs", + self->win_mgr_pid, baseobj->display, wm_wait_time); + } + else + { + char reason[128]; + exit_status_to_str(e, reason, sizeof(reason)); + + LOG(LOG_LEVEL_WARNING, "Window manager (pid %d, display %s) " + "exited with %s. This " + "could indicate a window manager config problem", + self->win_mgr_pid, baseobj->display, reason); + } + if (wm_wait_time < 10) + { + /* This could be a config issue. Log a significant error */ + LOG(LOG_LEVEL_WARNING, "Window manager (pid %d, display %s) " + "exited quickly (%d secs). This could indicate a window " + "manager config problem", + self->win_mgr_pid, baseobj->display, wm_wait_time); + } + + utmp_logout(self->win_mgr_pid, self->base.display, e); + self->win_mgr_pid = -1; + + if (self->labwc_pid > 0) + { + LOG(LOG_LEVEL_INFO, "Terminating labwc (pid %d) on display %s", + self->labwc_pid, baseobj->display); + g_sigterm(self->labwc_pid); + } + + if (self->wayvnc_pid > 0) + { + LOG(LOG_LEVEL_INFO, "Terminating wayvnc (pid %d) on display %s", + self->wayvnc_pid, baseobj->display); + g_sigterm(self->wayvnc_pid); + } + + if (self->base.chansrv_pid > 0) + { + LOG(LOG_LEVEL_INFO, "Terminating the xrdp channel server (pid %d) " + "on display %s", + self->base.chansrv_pid, baseobj->display); + g_sigterm(self->base.chansrv_pid); + } + } + + if (active_processes(&self->base) == 0) + { + cleanup_sockets(self); + } +} + +/******************************************************************************/ +static int +main_sess_proc_active(const struct session_data *baseobj) +{ + // Downcast the base object pointer to a pointer to the labwc session object + struct session_data_labwc *self = (struct session_data_labwc *)baseobj; + + // For labwc at present, check the wayvnc server too + return (self->win_mgr_pid > 0 && self->labwc_pid > 0); +} + +/******************************************************************************/ +static int +labwc_getpgid(const struct session_data *baseobj) +{ + // Downcast the base object pointer to a pointer to the labwc session object + struct session_data_labwc *self = (struct session_data_labwc *)baseobj; + + return self->labwc_pid; +} + +/******************************************************************************/ +static void +send_term(struct session_data *baseobj) +{ + // Downcast the base object pointer to a pointer to the labwc session object + struct session_data_labwc *self = (struct session_data_labwc *)baseobj; + if (self->win_mgr_pid > 0) + { + // Killing the window manager only is appropriate here. + // When we process SIGCHLD for the window manager, we + // will kill other processes as appropriate + g_sigterm(self->win_mgr_pid); + } +} + +/******************************************************************************/ +static int +get_display_server_fd( + const struct session_data *baseobj, + const struct login_info *login_info) +{ + char portname[XRDP_SOCKETS_MAXPATH]; + + int rv = -1; + + // Downcast the base object pointer to a pointer to the labwc session object + struct session_data_labwc *self = (struct session_data_labwc *)baseobj; + + portname[0] = '\0'; + + switch (self->base.params->type) + { + case SCP_SESSION_TYPE_LABWC_OVER_VNC: + if (self->wayvnc_pid <= 0) + { + LOG(LOG_LEVEL_ERROR, + "Request to connect to VNC display server %s" + " which has exited", baseobj->display); + } + else + { + g_snprintf(portname, sizeof(portname), XRDP_X11RDP_STR, + login_info->uid, STRIP_COLON(self->base.display)); + } + break; + + default: + LOG(LOG_LEVEL_ERROR, "Unsupported session type %d for connect", + self->base.params->type); + } + + if (portname[0] != '\0') + { + // Use the transport library to get the fd + struct trans *t = trans_create(TRANS_MODE_UNIX, 8 * 8192, 8192); + if (t == NULL) + { + LOG(LOG_LEVEL_ERROR, "Out of memory creating transport"); + } + else if (trans_connect(t, NULL, portname, 3000) != 0) + { + LOG(LOG_LEVEL_ERROR, "Can't connect to display server %s [%s]", + baseobj->display, + g_get_strerror()); + } + else + { + rv = t->sck; + t->sck = -1; + } + trans_delete(t); + } + + return rv; +} + +/******************************************************************************/ +static const struct session_data_vtable + labwc_vtable = +{ + .start = start, + .free = sess_free, + .process_child_exit = process_child_exit, + .active_processes = active_processes, + .main_sess_proc_active = main_sess_proc_active, + .getpgid = labwc_getpgid, + .send_term = send_term, + .get_display_server_fd = get_display_server_fd +}; + +/******************************************************************************/ +/** + * Create a new session_data structure from a session_parameters object + * + * @param sp Session parameters passed to session_start() + * @return semi-initialised session_data struct + */ +struct session_data_labwc * +session_labwc_new(void) +{ + struct session_data_labwc *self; + self = (struct session_data_labwc *)g_malloc(sizeof(*self), 0); + + if (self == NULL) + { + LOG(LOG_LEVEL_ERROR, "Out of memory allocating session data struct"); + } + else + { + self->base.vtable = &labwc_vtable; + self->labwc_pid = -1; + self->wayvnc_pid = -1; + self->win_mgr_pid = -1; + } + + return self; +} diff --git a/sesman/sesexec/session_labwc.h b/sesman/sesexec/session_labwc.h new file mode 100644 index 0000000000..26063e97c0 --- /dev/null +++ b/sesman/sesexec/session_labwc.h @@ -0,0 +1,57 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2025 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * + * @file session_labwc.h + * @brief Derived class for labwc session objects + * @author Matt Burt + * + */ + + +#ifndef SESSION_LABWC_H +#define SESSION_LABWC_H + + +#include "arch.h" +#include "session_base.h" + +/** + * Data involved in running an labwc session (derived class) + */ +struct session_data_labwc +{ + struct session_data base; + pid_t labwc_pid; ///< PID of labwc + pid_t wayvnc_pid; ///< PID of wayvnc + pid_t win_mgr_pid; ///< PID of window manager +}; + +/** + * Allocates memory for a new labwc session object + * @return New object, or NULL for no memory. + * + * The vtable of the base, and all the labwc-specific member variables + * will be initialised. The caller must initialise the rest of the + * base object. + */ +struct session_data_labwc * +session_labwc_new(void); + +#endif // SESSION_LABWC_H diff --git a/sesman/sesexec/session_parameters.c b/sesman/sesexec/session_parameters.c new file mode 100644 index 0000000000..3654647cb3 --- /dev/null +++ b/sesman/sesexec/session_parameters.c @@ -0,0 +1,68 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2025 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * + * @file session_parameters.c + * @brief Parameters used to start a session + * @author Jay Sorg, Simone Fedele, Matt Burt + * + */ + +#include +#include + +#if defined(HAVE_CONFIG_H) +#include "config_ac.h" +#endif + +#include "session_parameters.h" + +/******************************************************************************/ +struct session_parameters * +copy_session_parameters(const struct session_parameters *sp) +{ + struct session_parameters *cp; + // What string length do we need? + unsigned int string_length = 0; + string_length += strlen(sp->shell) + 1; + string_length += strlen(sp->directory) + 1; + + cp = (struct session_parameters *)malloc(sizeof(*cp) + string_length); + + if (cp != NULL) + { + /* Copy all the scalar parameters... */ + *cp = *sp; + + /* ...and then the strings */ + char *memptr = (char *)(cp + 1); + +#define COPY_STRING(dest,src) \ + (dest) = memptr; \ + strcpy(memptr, src); \ + memptr += strlen(memptr) + 1 + + COPY_STRING(cp->shell, sp->shell); + COPY_STRING(cp->directory, sp->directory); + +#undef COPY_STRING + } + + return cp; +} diff --git a/sesman/sesexec/session_parameters.h b/sesman/sesexec/session_parameters.h new file mode 100644 index 0000000000..2fdb6133c2 --- /dev/null +++ b/sesman/sesexec/session_parameters.h @@ -0,0 +1,59 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2013 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * + * @file session_parameters.h + * @brief Parameters used to start a session + * @author Jay Sorg, Simone Fedele, Matt Burt + * + */ + + +#ifndef SESSION_PARAMETERS_H +#define SESSION_PARAMETERS_H + +#include "guid.h" +#include "scp_application_types.h" + +/** + * Information used to start a session + */ +struct session_parameters +{ + int x11_display; // >= for X11 only + enum scp_session_type type; + unsigned short width; + unsigned short height; + unsigned char bpp; + struct guid guid; + const char *shell; // Must not be NULL + const char *directory; // Must not be NULL +}; + +/** + * Make a copy of a session_parameters object + * + * The object is allocated so it can be freed with a single call to free() + * @param sp Object to copy + * @return deep copy of object + */ +struct session_parameters * +copy_session_parameters(const struct session_parameters *sp); + +#endif // SESSION_PARAMETERS_H diff --git a/sesman/sesexec/session_x11.c b/sesman/sesexec/session_x11.c new file mode 100644 index 0000000000..e6f3677200 --- /dev/null +++ b/sesman/sesexec/session_x11.c @@ -0,0 +1,949 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2025 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * + * @file session_x11.c + * @brief Derived class for X11 session objects + * @author Matt Burt + * + */ + +#if defined(HAVE_CONFIG_H) +#include "config_ac.h" +#endif + +#include +#include + +#include "arch.h" +#include "session_x11.h" +#include "session_parameters.h" + +#include "sesman_auth.h" +#include "sesman_config.h" +#include "display_server_util.h" +#include "env.h" +#include "guid.h" +#include "list.h" +#include "log.h" +#include "login_info.h" +#include "os_calls.h" +#include "sesexec.h" +#include "sessionrecord.h" +#include "string_calls.h" +#include "trans.h" +#include "xauth.h" +#include "xrdp_sockets.h" + +/******************************************************************************/ +/** + * Creates a string consisting of all parameters that is hosted in the param list + * @param self + * @param outstr, allocate this buffer before you use this function + * @param len the allocated len for outstr + * @return + */ +static char * +dumpItemsToString(struct list *self, char *outstr, int len) +{ + int index; + int totalLen = 0; + + g_memset(outstr, 0, len); + if (self->count == 0) + { + LOG_DEVEL(LOG_LEVEL_TRACE, "List is empty"); + } + + for (index = 0; index < self->count; index++) + { + /* +1 = one space*/ + totalLen = totalLen + g_strlen((char *)list_get_item(self, index)) + 1; + + if (len > totalLen) + { + g_strcat(outstr, (char *)list_get_item(self, index)); + g_strcat(outstr, " "); + } + } + + return outstr ; +} + +/******************************************************************************/ +static void +start_window_manager(struct session_data *baseobj, + const struct login_info *login_info, + void *closure /* unused */) +{ + char text[256]; + const struct session_parameters *s = baseobj->params; + + env_set_user(login_info->uid, + 0, + g_cfg->env_names, + g_cfg->env_values); + + auth_set_env(login_info->auth_info); + LOG_DEVEL_LEAKING_FDS("window manager", 3, -1); + + if (s->directory[0] != '\0') + { + if (g_cfg->sec.allow_alternate_shell) + { + g_set_current_dir(s->directory); + } + else + { + LOG(LOG_LEVEL_WARNING, + "Directory change to %s requested, but not " + "allowed by AllowAlternateShell config value.", + s->directory); + } + } + + if (s->shell[0] != '\0') + { + if (g_cfg->sec.allow_alternate_shell) + { + if (g_strchr(s->shell, ' ') != 0 || g_strchr(s->shell, '\t') != 0) + { + LOG(LOG_LEVEL_INFO, + "Using user requested window manager on " + "display :%d with embedded arguments using a shell: %s", + s->x11_display, s->shell); + const char *argv[] = {"sh", "-c", s->shell, NULL}; + g_execvp("/bin/sh", (char **)argv); + } + else + { + LOG(LOG_LEVEL_INFO, + "Using user requested window manager on " + "display :%d : %s", s->x11_display, s->shell); + g_execlp3(s->shell, s->shell, 0); + } + } + else + { + LOG(LOG_LEVEL_WARNING, + "Shell %s requested by user, but not allowed by " + "AllowAlternateShell config value.", + s->shell); + } + } + else + { + LOG(LOG_LEVEL_DEBUG, "The user session on display :%d did not" + " request a specific window manager", s->x11_display); + } + + /* try to execute user window manager if enabled */ + if (g_cfg->enable_user_wm) + { + g_snprintf(text, sizeof(text), "%s/%s", + g_getenv("HOME"), g_cfg->user_wm); + if (g_file_exist(text)) + { + LOG(LOG_LEVEL_INFO, + "Using window manager on display :%d from user" + " home directory: %s", s->x11_display, text); + g_execlp3(text, g_cfg->user_wm, 0); + } + else + { + LOG(LOG_LEVEL_DEBUG, + "The user home directory window manager configuration " + "is enabled but window manager program does not exist: %s", + text); + } + } + + LOG(LOG_LEVEL_INFO, + "Using the default window manager on display :%d : %s", + s->x11_display, g_cfg->default_wm); + g_execlp3(g_cfg->default_wm, g_cfg->default_wm, 0); + + /* still a problem starting window manager just start xterm */ + LOG(LOG_LEVEL_WARNING, + "No window manager on display :%d started, " + "so falling back to starting xterm for user debugging", + s->x11_display); + g_execlp3("xterm", "xterm", 0); + + /* should not get here */ + LOG(LOG_LEVEL_ERROR, "A fatal error has occurred attempting to start " + "the window manager on display :%d, aborting connection", + s->x11_display); +} + +/******************************************************************************/ +static struct list * +prepare_xorg_xserver_params(const struct session_parameters *s, + const char *authfile) +{ + + char screen[32]; /* display number */ + char text[128]; + const char *xserver; + + struct list *params = list_create(); + if (params != NULL) + { + params->auto_free = 1; + + /* + * Make sure Xorg doesn't run setuid root. Root access is not + * needed. Xorg can fail when run as root and the user has no + * console permissions. + */ + if (g_cfg->sec.xorg_no_new_privileges && g_no_new_privs() != 0) + { + LOG(LOG_LEVEL_WARNING, + "[session start] (display :%d): Failed to disable " + "setuid on X server: %s", + s->x11_display, g_get_strerror()); + } + + g_snprintf(screen, sizeof(screen), ":%d", s->x11_display); + + /* some args are passed via env vars */ + g_snprintf(text, sizeof(text), "%d", s->width); + g_setenv_log("XRDP_START_WIDTH", text, 1); + + g_snprintf(text, sizeof(text), "%d", s->height); + g_setenv_log("XRDP_START_HEIGHT", text, 1); + + g_snprintf(text, sizeof(text), "%d", g_cfg->sess.max_idle_time); + g_setenv_log("XRDP_SESMAN_MAX_IDLE_TIME", text, 1); + + g_snprintf(text, sizeof(text), "%d", g_cfg->sess.max_disc_time); + g_setenv_log("XRDP_SESMAN_MAX_DISC_TIME", text, 1); + + g_snprintf(text, sizeof(text), "%d", g_cfg->sess.kill_disconnected); + g_setenv_log("XRDP_SESMAN_KILL_DISCONNECTED", text, 1); + + /* get path of Xorg from config */ + xserver = (const char *)list_get_item(g_cfg->xorg_params, 0); + + /* these are the must have parameters */ + list_add_strdup_multi(params, + xserver, screen, + "-auth", authfile, + NULL); + + /* additional parameters from sesman.ini file */ + list_append_list_strdup(g_cfg->xorg_params, params, 1); + } + + return params; +} + +/******************************************************************************/ +/** + * Prepare a list of parameters for the Xvnc X server + * @param s Session parameters + * @params authfile XAUTHORITY file + * @params passwd_file VNC password file, or NULL + * @params port UDS port to connect to, or NULL + * @return parameters list + * + * One of passwd_file and port must be set + */ +static struct list * +prepare_xvnc_xserver_params(const struct session_parameters *s, + const char *authfile, + const char *passwd_file, + const char *port) +{ + char screen[32] = {0}; /* display number */ + char geometry[32] = {0}; + char depth[32] = {0}; + char guid_str[GUID_STR_SIZE]; + const char *xserver; + + struct list *params = list_create(); + if (params != NULL) + { + params->auto_free = 1; + + g_snprintf(screen, sizeof(screen), ":%d", s->x11_display); + g_snprintf(geometry, sizeof(geometry), "%dx%d", s->width, s->height); + g_snprintf(depth, sizeof(depth), "%d", s->bpp); + + guid_to_str(&s->guid, guid_str); + env_check_password_file(passwd_file, guid_str); + + /* get path of Xvnc from config */ + xserver = (const char *)list_get_item(g_cfg->vnc_params, 0); + + /* these are the must have parameters */ + list_add_strdup_multi(params, + xserver, screen, + "-auth", authfile, + "-geometry", geometry, + "-depth", depth, + NULL); + + if (passwd_file != NULL) + { + /* RFB authorization */ + list_add_strdup_multi(params, + "-rfbauth", passwd_file, + NULL); + } + else if (port != NULL) + { + /* UDS connection. Authorization is handled by standard socket + * permissions, so we do not need to authorize within the + * VNC protocol exchange as well */ + char sock_mode[16]; + + /* Convert a standard permissions mask into decimal + * for the -rfbunixmode switch argument + */ + g_snprintf(sock_mode, sizeof(sock_mode), + "%d", 0660); /* rw-rw---- */ + + list_add_strdup_multi(params, + "-rfbunixpath", port, + "-rfbunixmode", sock_mode, + "-SecurityTypes", "None", + NULL); + } + + /* additional parameters from sesman.ini file */ + //config_read_xserver_params(SCP_SESSION_TYPE_XVNC, + // xserver_params); + list_append_list_strdup(g_cfg->vnc_params, params, 1); + } + return params; +} + +/******************************************************************************/ +/* Either execs the X server, or returns */ +static void +start_x_server(struct session_data *baseobj, + const struct login_info *login_info, + void *closure /* unused */) +{ + char authfile[256]; /* The filename for storing xauth information */ + char execvpparams[2048]; + char *passwd_file = NULL; + struct list *xserver_params = NULL; + int unknown_session_type = 0; + + if (baseobj->params->type == SCP_SESSION_TYPE_XVNC) + { + env_set_user(login_info->uid, + &passwd_file, + g_cfg->env_names, + g_cfg->env_values); + } + else + { + env_set_user(login_info->uid, + 0, + g_cfg->env_names, + g_cfg->env_values); + } + + /* prepare the Xauthority stuff */ + if (g_getenv("XAUTHORITY") != NULL) + { + g_snprintf(authfile, sizeof(authfile), "%s", + g_getenv("XAUTHORITY")); + } + else + { + g_snprintf(authfile, sizeof(authfile), "%s", ".Xauthority"); + } + + /* Add the entry in XAUTHORITY file or exit if error */ + if (add_xauth_cookie(baseobj->params->x11_display, authfile) != 0) + { + LOG(LOG_LEVEL_ERROR, + "Error setting the xauth cookie for display :%d in file %s", + baseobj->params->x11_display, authfile); + } + else + { + switch (baseobj->params->type) + { + char port[256]; + + case SCP_SESSION_TYPE_XORG: + xserver_params = prepare_xorg_xserver_params(baseobj->params, + authfile); + break; + + case SCP_SESSION_TYPE_XVNC: + xserver_params = prepare_xvnc_xserver_params(baseobj->params, + authfile, passwd_file, NULL); + break; + + case SCP_SESSION_TYPE_XVNC_UDS: + { + char displaystr[32]; + g_snprintf(displaystr, sizeof(displaystr), "%d", + baseobj->params->x11_display); + g_snprintf(port, sizeof(port), XRDP_X11RDP_STR, + login_info->uid, displaystr); + } + xserver_params = prepare_xvnc_xserver_params(baseobj->params, + authfile, NULL, port); + break; + + default: + unknown_session_type = 1; + } + + if (xserver_params == NULL) + { + LOG(LOG_LEVEL_ERROR, "Out of memory allocating X server params"); + } + else if (unknown_session_type) + { + LOG(LOG_LEVEL_ERROR, "Unknown session type: %d", + baseobj->params->type); + } + else + { + /* fire up X server */ + LOG(LOG_LEVEL_INFO, "Starting X server on display :%d: %s", + baseobj->params->x11_display, + dumpItemsToString(xserver_params, execvpparams, 2048)); + LOG_DEVEL_LEAKING_FDS("X server", 3, -1); + g_execvp_list((const char *)xserver_params->items[0], + xserver_params); + } + } + + /* should not get here */ + g_free(passwd_file); + list_delete(xserver_params); + LOG(LOG_LEVEL_ERROR, "A fatal error has occurred attempting " + "to start the X server on display :%d, aborting connection", + baseobj->params->x11_display); +} + +/******************************************************************************/ +/** + * Waits for the X server to start using a helper prog + * + * @param login_info Login info for user + * @param env_names List of environment variables for the helper (names) + * @param env_values List of environment variables for the helper (values) + * @param display X11 display we're waiting for + */ +static enum display_server_status +wait_for_xserver(struct login_info *login_info, + const struct list *env_names, + const struct list *env_values, + const char *display) +{ + enum display_server_status rv = DS_STATUS_MISC_ERROR; + struct list *cmd = list_create(); + if (cmd == NULL || + (!(cmd->auto_free = 1)) || // Just set 'auto_free' inline + !list_add_strdup_multi(cmd, + XRDP_LIBEXEC_PATH "/waitforx", + "-d", display, NULL)) + { + LOG(LOG_LEVEL_ERROR, "Out of memory running waitforx"); + } + else + { + rv = wait_for_display_server(login_info, env_names, env_values, cmd, + NULL, NULL); + } + + list_delete(cmd); + return rv; +} + +/******************************************************************************/ +static enum scp_screate_status +start(struct session_data *baseobj, + struct login_info *login_info, + const struct session_parameters *s) +{ + int chansrv_pid; + int display_pid; + int window_manager_pid; + char displayname[32]; + enum scp_screate_status status = E_SCP_SCREATE_GENERAL_ERROR; + + // Downcast the base object pointer to a pointer to the X11 session object + struct session_data_x11 *self = (struct session_data_x11 *)baseobj; + + snprintf(displayname, sizeof(displayname), ":%d", + s->x11_display); + + /* Add the DISPLAY to the list of environment variables we + * set for all the sub-processes */ + if (!list_add_strdup(g_cfg->env_names, "DISPLAY") || + !list_add_strdup(g_cfg->env_values, displayname)) + { + LOG(LOG_LEVEL_ERROR, "Out of memory adding DISPLAY to env"); + return E_SCP_SCREATE_NO_MEMORY; + } + + /* start the X server in a new process group. + * + * We group the X server, window manager and chansrv in a single + * process group, as it allows signals to be sent to the user session + * without affecting sesexec (and vice-versa). This is particularly + * important when debugging sesexec as we don't want a SIGINT in + * the debugger to be passed to the children */ + display_pid = session_base_fork_child(&self->base, login_info, 0, + start_x_server, NULL); + if (display_pid > 0) + { + enum display_server_status dss; + dss = wait_for_xserver(login_info, + g_cfg->env_names, + g_cfg->env_values, + displayname); + + if (dss != DS_STATUS_OK) + { + switch (dss) + { + case DS_STATUS_TIMED_OUT: + LOG(LOG_LEVEL_ERROR, "Timed out waiting for X server"); + break; + case DS_STATUS_FAILED_TO_START: + LOG(LOG_LEVEL_ERROR, "X server failed to start"); + break; + default: + LOG(LOG_LEVEL_ERROR, + "An error occurred waiting for the X server"); + } + status = E_SCP_SCREATE_X_SERVER_FAIL; + /* Kill it anyway in case it did start and we just failed to + * pick up on it */ + g_sigterm(display_pid); + g_waitpid(display_pid); + } + else + { + LOG(LOG_LEVEL_INFO, "X server :%d is working", s->x11_display); + // Set the base class display name so other methods can use it + snprintf(self->base.display, sizeof(self->base.display), ":%d", + s->x11_display); + + LOG(LOG_LEVEL_INFO, "Starting window manager for display %s", + self->base.display); + + window_manager_pid = session_base_fork_child(baseobj, login_info, + display_pid, start_window_manager, + NULL); + if (window_manager_pid < 0) + { + g_sigterm(display_pid); + g_waitpid(display_pid); + } + else + { + utmp_login(window_manager_pid, self->base.display, login_info); + LOG(LOG_LEVEL_INFO, + "Starting the xrdp channel server for display %s", + self->base.display); + + chansrv_pid = session_base_fork_child( + baseobj, login_info, display_pid, + session_base_start_chansrv, NULL); + + self->win_mgr_pid = window_manager_pid; + self->x_server_pid = display_pid; + + // Set the rest of the base class member variables we + // are responsible for + self->base.chansrv_pid = chansrv_pid; + self->base.start_time = time(NULL); + + if (session_base_process_startup_wait_time(&self->base) == 0) + { + // Tell the caller we've started + LOG(LOG_LEVEL_INFO, + "Session in progress on display %s. Waiting until the " + "window manager (pid %d) exits to end the session", + self->base.display, window_manager_pid); + + status = E_SCP_SCREATE_OK; + } + else + { + LOG(LOG_LEVEL_ERROR, + "Session failed during startup wait time"); + status = E_SCP_SCREATE_SESSION_FAIL; + } + } + } + } + + return status; +} + +/******************************************************************************/ +static void +sess_free(struct session_data *baseobj) +{ + // Downcast the base object pointer to a pointer to the X11 session object + struct session_data_x11 *self = (struct session_data_x11 *)baseobj; + + if (self != NULL) + { +#ifdef USE_DEVEL_LOGGING + if (self->win_mgr_pid > 0) + { + LOG_DEVEL(LOG_LEVEL_WARNING, + "Freeing session data with valid window manager PID %d", + self->win_mgr_pid); + } + if (self->x_server_pid > 0) + { + LOG_DEVEL(LOG_LEVEL_WARNING, + "Freeing session data with valid X server PID %d", + self->x_server_pid); + } +#endif + } +} + + +/******************************************************************************/ +static int +cleanup_sockets(struct session_data_x11 *self) +{ + LOG_DEVEL(LOG_LEVEL_INFO, "cleanup_sockets:"); + + char file[XRDP_SOCKETS_MAXPATH]; + + int uid = g_login_info->uid; + char *display = STRIP_COLON(self->base.display); + + // Cleanup sockets used by the base display class + int error = session_base_cleanup_sockets(&self->base); + + /* the following files should be deleted by xorgxrdp + * but just in case the deletion failed */ + + g_snprintf(file, sizeof(file), XRDP_X11RDP_STR, uid, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + + g_snprintf(file, sizeof(file), XRDP_DISCONNECT_STR, uid, display); + if (g_file_exist(file)) + { + LOG(LOG_LEVEL_DEBUG, "cleanup_sockets: deleting %s", file); + if (g_file_delete(file) == 0) + { + LOG(LOG_LEVEL_WARNING, + "cleanup_sockets: failed to delete %s (%s)", + file, g_get_strerror()); + error++; + } + } + + return error; +} + +/******************************************************************************/ +static void +exit_status_to_str(const struct proc_exit_status *e, char buff[], int bufflen) +{ + switch (e->reason) + { + case E_PXR_STATUS_CODE: + if (e->val == 0) + { + g_snprintf(buff, bufflen, "exit code zero"); + } + else + { + g_snprintf(buff, bufflen, "non-zero exit code %d", e->val); + } + break; + + case E_PXR_SIGNAL: + { + char sigstr[MAXSTRSIGLEN]; + g_snprintf(buff, bufflen, "signal %s", + g_sig2text(e->val, sigstr)); + } + break; + + default: + g_snprintf(buff, bufflen, "an unexpected error"); + break; + } +} + +/******************************************************************************/ +static unsigned int +active_processes(const struct session_data *baseobj) +{ + // Downcast the base object pointer to a pointer to the X11 session object + struct session_data_x11 *self = (struct session_data_x11 *)baseobj; + + return (self->win_mgr_pid > 0) + + (self->x_server_pid > 0) + (self->base.chansrv_pid > 0); +} + +/******************************************************************************/ +/** + * Processes an exited child + * + * The PID of the child process is removed from the session_data. + * + * @param self session_data object + * @param pid PID of exited process + * @param e Exit status of the exited process + */ +static void +process_child_exit(struct session_data *baseobj, + int pid, + const struct proc_exit_status *e) +{ + // Downcast the base object pointer to a pointer to the X11 session object + struct session_data_x11 *self = (struct session_data_x11 *)baseobj; + + if (pid == self->x_server_pid) + { + // Don't log the exit if we haven't got as far as registering + // the display name + if (baseobj->display[0] != '\0') + { + LOG(LOG_LEVEL_INFO, "X server pid %d on display %s finished", + self->x_server_pid, baseobj->display); + } + self->x_server_pid = -1; + // No other action - window manager should be going soon + } + else if (pid == self->win_mgr_pid) + { + int wm_wait_time = time(NULL) - self->base.start_time; + + if (e->reason == E_PXR_STATUS_CODE && e->val == 0) + { + LOG(LOG_LEVEL_INFO, + "Window manager (pid %d, display %s) " + "finished normally in %d secs", + self->win_mgr_pid, baseobj->display, wm_wait_time); + } + else + { + char reason[128]; + exit_status_to_str(e, reason, sizeof(reason)); + + LOG(LOG_LEVEL_WARNING, "Window manager (pid %d, display %s) " + "exited with %s. This " + "could indicate a window manager config problem", + self->win_mgr_pid, baseobj->display, reason); + } + if (wm_wait_time < 10) + { + /* This could be a config issue. Log a significant error */ + LOG(LOG_LEVEL_WARNING, "Window manager (pid %d, display %s) " + "exited quickly (%d secs). This could indicate a window " + "manager config problem", + self->win_mgr_pid, baseobj->display, wm_wait_time); + } + + utmp_logout(self->win_mgr_pid, self->base.display, e); + self->win_mgr_pid = -1; + + if (self->x_server_pid > 0) + { + LOG(LOG_LEVEL_INFO, "Terminating X server (pid %d) on display %s", + self->x_server_pid, baseobj->display); + g_sigterm(self->x_server_pid); + } + + if (self->base.chansrv_pid > 0) + { + LOG(LOG_LEVEL_INFO, "Terminating the xrdp channel server (pid %d) " + "on display %s", + self->base.chansrv_pid, baseobj->display); + g_sigterm(self->base.chansrv_pid); + } + } + + if (active_processes(&self->base) == 0) + { + cleanup_sockets(self); + } +} + +/******************************************************************************/ +static int +main_sess_proc_active(const struct session_data *baseobj) +{ + // Downcast the base object pointer to a pointer to the X11 session object + struct session_data_x11 *self = (struct session_data_x11 *)baseobj; + + return (self->win_mgr_pid > 0); +} + +/******************************************************************************/ +static int +x11_getpgid(const struct session_data *baseobj) +{ + // Downcast the base object pointer to a pointer to the X11 session object + struct session_data_x11 *self = (struct session_data_x11 *)baseobj; + + return self->x_server_pid; +} + +/******************************************************************************/ +static void +send_term(struct session_data *baseobj) +{ + // Downcast the base object pointer to a pointer to the X11 session object + struct session_data_x11 *self = (struct session_data_x11 *)baseobj; + if (self->win_mgr_pid > 0) + { + // Killing the window manager only is appropriate here. + // When we process SIGCHLD for the window manager, we + // will kill other processes as appropriate + g_sigterm(self->win_mgr_pid); + } +} + +/******************************************************************************/ +static int +get_display_server_fd( + const struct session_data *baseobj, + const struct login_info *login_info) +{ + char portname[XRDP_SOCKETS_MAXPATH]; + const char *localhost = "localhost"; // Ignored for TRANS_MODE_UNIX + int socket_mode; + + int rv = -1; + + // Downcast the base object pointer to a pointer to the X11 session object + struct session_data_x11 *self = (struct session_data_x11 *)baseobj; + + if (self->x_server_pid <= 0) + { + LOG(LOG_LEVEL_ERROR, + "Request to connect to display server %s" + " which has exited", baseobj->display); + } + else + { + switch (self->base.params->type) + { + case SCP_SESSION_TYPE_XVNC: + socket_mode = TRANS_MODE_TCP; + g_snprintf(portname, sizeof(portname), "%d", + 5900 + self->base.params->x11_display); + break; + + case SCP_SESSION_TYPE_XVNC_UDS: + case SCP_SESSION_TYPE_XORG: + socket_mode = TRANS_MODE_UNIX; + g_snprintf(portname, sizeof(portname), XRDP_X11RDP_STR, + login_info->uid, STRIP_COLON(self->base.display)); + + break; + + default: + LOG(LOG_LEVEL_ERROR, "Unsupported session type %d for connect", + self->base.params->type); + portname[0] = '\0'; + } + + if (portname[0] != '\0') + { + // Use the transport library to get the fd + struct trans *t = trans_create(socket_mode, 8 * 8192, 8192); + if (t == NULL) + { + LOG(LOG_LEVEL_ERROR, "Out of memory creating transport"); + } + else if (trans_connect(t, localhost, portname, 3000) != 0) + { + LOG(LOG_LEVEL_ERROR, "Can't connect to display server %s [%s]", + baseobj->display, + g_get_strerror()); + } + else + { + rv = t->sck; + t->sck = -1; + } + trans_delete(t); + } + } + + return rv; +} + +/******************************************************************************/ +static const struct session_data_vtable + x11_vtable = +{ + .start = start, + .free = sess_free, + .process_child_exit = process_child_exit, + .active_processes = active_processes, + .main_sess_proc_active = main_sess_proc_active, + .getpgid = x11_getpgid, + .send_term = send_term, + .get_display_server_fd = get_display_server_fd +}; + +/******************************************************************************/ +/** + * Create a new session_data structure from a session_parameters object + * + * @param sp Session parameters passed to session_start() + * @return semi-initialised session_data struct + */ +struct session_data_x11 * +session_x11_new(void) +{ + struct session_data_x11 *self; + self = (struct session_data_x11 *)g_malloc(sizeof(*self), 0); + + if (self == NULL) + { + LOG(LOG_LEVEL_ERROR, "Out of memory allocating session data struct"); + } + else + { + self->base.vtable = &x11_vtable; + self->win_mgr_pid = -1; + self->x_server_pid = -1; + } + + return self; +} diff --git a/sesman/sesexec/session_x11.h b/sesman/sesexec/session_x11.h new file mode 100644 index 0000000000..74682c354c --- /dev/null +++ b/sesman/sesexec/session_x11.h @@ -0,0 +1,56 @@ +/** + * xrdp: A Remote Desktop Protocol server. + * + * Copyright (C) Jay Sorg 2004-2025 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * + * @file session_x11.h + * @brief Derived class for X11 session objects + * @author Matt Burt + * + */ + + +#ifndef SESSION_X11_H +#define SESSION_X11_H + + +#include "arch.h" +#include "session_base.h" + +/** + * Data involved in running an X11 session (derived class) + */ +struct session_data_x11 +{ + struct session_data base; + pid_t x_server_pid; ///< PID of X server + pid_t win_mgr_pid; ///< PID of window manager +}; + +/** + * Allocates memory for a new X11 session object + * @return New object, or NULL for no memory. + * + * The vtable of the base, and all the x11-specific member variables + * will be initialised. The caller must initialise the rest of the + * base object. + */ +struct session_data_x11 * +session_x11_new(void); + +#endif // SESSION_X11_H diff --git a/sesman/sesexec/sessionrecord.c b/sesman/sesexec/sessionrecord.c index d9dce5a4aa..1ffe310559 100644 --- a/sesman/sesexec/sessionrecord.c +++ b/sesman/sesexec/sessionrecord.c @@ -68,7 +68,7 @@ typedef struct utmp _utmp; #include "os_calls.h" #include "string_calls.h" -#define XRDP_LINE_FORMAT "xrdp:%d" +#define XRDP_LINE_FORMAT "xrdp:%s" // ut_id is a very small field on some platforms, so use the display // number in hex #define XRDP_ID_FORMAT ":%x" @@ -107,14 +107,15 @@ str2memcpy(void *dest, const char *src, size_t n) * Prepare the utmp struct and write it. * * @param pid PID of session manager - * @param display Display number of session + * @param display Display name of session * @param login_info Login info (NULL for MODE_LOGOUT) * @param mode see enum add_xtmp_mode * @param e Exit status (NULL unless MODE_LOGOUT) */ static void -add_xtmp_entry(int pid, int display, const struct login_info *login_info, +add_xtmp_entry(int pid, const char *display, + const struct login_info *login_info, enum add_xtmp_mode mode, const struct proc_exit_status *e) { char idbuff[16]; @@ -124,8 +125,30 @@ add_xtmp_entry(int pid, int display, const struct login_info *login_info, struct timeval tv; g_memset(&ut, 0, sizeof(ut)); - g_snprintf(str_display, sizeof(str_display), XRDP_LINE_FORMAT, display); - g_snprintf(idbuff, sizeof(idbuff), XRDP_ID_FORMAT, display); + if (display[0] == ':') + { + // X11 display + g_snprintf(str_display, sizeof(str_display), XRDP_LINE_FORMAT, + display + 1); + g_snprintf(idbuff, sizeof(idbuff), XRDP_ID_FORMAT, + g_atoi(display + 1)); + } + else if (strncmp(display, "wayland-", 8) == 0) + { + // Conventional name of a wayland display + g_snprintf(str_display, sizeof(str_display), XRDP_LINE_FORMAT, + display + 8); + g_snprintf(idbuff, sizeof(idbuff), XRDP_ID_FORMAT, + g_atoi(display + 8)); + } + else + { + LOG(LOG_LEVEL_WARNING, + "Unconventional display name '%s' for utmp entry", + display); + str_display[0] = '\0'; + idbuff[0] = '\0'; + } gettimeofday(&tv, NULL); ut.ut_type = (mode == MODE_LOGIN) ? USER_PROCESS : DEAD_PROCESS; @@ -170,7 +193,8 @@ add_xtmp_entry(int pid, int display, const struct login_info *login_info, } #else // USE_UTMP static void -add_xtmp_entry(int pid, int display, const struct login_info *login_info, +add_xtmp_entry(int pid, const char *display, + const struct login_info *login_info, short state, const struct proc_exit_status *e) { } @@ -179,10 +203,10 @@ add_xtmp_entry(int pid, int display, const struct login_info *login_info, /******************************************************************************/ void -utmp_login(int pid, int display, const struct login_info *login_info) +utmp_login(int pid, const char *display, const struct login_info *login_info) { log_message(LOG_LEVEL_DEBUG, - "adding login info for utmp: %d - %d - %s - %s", + "adding login info for utmp: %d - %s - %s - %s", pid, display, login_info->username, login_info->ip_addr); add_xtmp_entry(pid, display, login_info, MODE_LOGIN, NULL); @@ -190,10 +214,11 @@ utmp_login(int pid, int display, const struct login_info *login_info) /******************************************************************************/ void -utmp_logout(int pid, int display, const struct proc_exit_status *exit_status) +utmp_logout(int pid, const char *display, + const struct proc_exit_status *exit_status) { - log_message(LOG_LEVEL_DEBUG, "adding logout info for utmp: %d - %d", + log_message(LOG_LEVEL_DEBUG, "adding logout info for utmp: %d - %s", pid, display); add_xtmp_entry(pid, display, NULL, MODE_LOGOUT, exit_status); diff --git a/sesman/sesexec/sessionrecord.h b/sesman/sesexec/sessionrecord.h index d547877182..402a875625 100644 --- a/sesman/sesexec/sessionrecord.h +++ b/sesman/sesexec/sessionrecord.h @@ -33,20 +33,21 @@ struct proc_exit_status; * @brief Record login in utmp * * @param pid PID of window manager - * @param display Display number + * @param display Display name * @param login_info Information about logged in user */ void -utmp_login(int pid, int display, const struct login_info *login_info); +utmp_login(int pid, const char *display, const struct login_info *login_info); /** * @brief Record logout in utmp * * @param pid PID of window manager - * @param display Display number + * @param display Display name * @param exit_status Exit status of process */ void -utmp_logout(int pid, int display, const struct proc_exit_status *exit_status); +utmp_logout(int pid, const char *display, + const struct proc_exit_status *exit_status); #endif diff --git a/sesman/sesexec/wayland/Makefile.am b/sesman/sesexec/wayland/Makefile.am new file mode 100644 index 0000000000..5fbcad9fab --- /dev/null +++ b/sesman/sesexec/wayland/Makefile.am @@ -0,0 +1,3 @@ +dist_pkglibexec_SCRIPTS = \ + waitforw \ + labwc-getenv diff --git a/sesman/sesexec/wayland/labwc-getenv b/sesman/sesexec/wayland/labwc-getenv new file mode 100755 index 0000000000..5eda2648b0 --- /dev/null +++ b/sesman/sesexec/wayland/labwc-getenv @@ -0,0 +1,78 @@ +#!/bin/sh +# +# xrdp: A Remote Desktop Protocol server. +# +# Copyright (C) Jay Sorg and contributors 2004-2024 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Copies an environment list to a file on the filesystem in an +# atomic operation. We use this to find out when the display +# server has started. + +#Uncomment these for debugging +#exec >/var/run/user/$(id -u)/labwc-getenv.log 2>&1 +#set -x +#env | sort + +# ------------------------------------------------------------------------------ +# G L O B A L S +# ------------------------------------------------------------------------------ + +ENV_LIST=" + WAYLAND_DISPLAY + DISPLAY +" + +# ------------------------------------------------------------------------------ +# C H E C K R E Q U I R E D E N V I R O N M E N T +# ------------------------------------------------------------------------------ +#ok=true + +# shellcheck disable=SC2043 +for envvar in XRDP_SESSION XDG_RUNTIME_DIR; do + eval val="\$$envvar" + if [ -z "$val" ]; then + echo "** $0: Environment variable '$envvar' is missing" >&2 + ok=false + fi +done >/dev/null + +if ! $ok; then + false +else + # Get the name for the environment file + mkdir -p "$XDG_RUNTIME_DIR/xrdp" + env_file="$XDG_RUNTIME_DIR/xrdp/session-$XRDP_SESSION.env" + + # It shouldn't be here already + if [ -e "$env_file" ]; then + echo "** $0: $env_file already exists" >&2 + false + else + tmp=$(mktemp "$env_file.XXXXXXXX") + if [ -z "$tmp" ]; then + echo "** $0: Can't open temp file to dump environment" >&2 + false + else + for env in $ENV_LIST; do + eval val="\$$env" + if [ -n "$val" ]; then + echo "$env=$val" + fi + done >"$tmp" + mv "$tmp" "$env_file" + fi + fi +fi +exit $? diff --git a/sesman/sesexec/wayland/waitforw b/sesman/sesexec/wayland/waitforw new file mode 100755 index 0000000000..94e31ca9fd --- /dev/null +++ b/sesman/sesexec/wayland/waitforw @@ -0,0 +1,105 @@ +#!/bin/sh +# +# xrdp: A Remote Desktop Protocol server. +# +# Copyright (C) Jay Sorg and contributors 2004-2025 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Wait for the wayland server to start +# +# At present, we just look for the environment file created when +# labwc is started. When the file is available, we read it and +# send the contents to stdout tagged with "" so sesman can +# set environment variables. +# +# This is an analogue to waitforx.c in the X server world + +#Uncomment these for debugging +#exec 2>/var/run/user/$(id -u)/waitforw.log +#set -x + +# See sesman/sesexec/display_server_util.h +DS_STATUS_OK=0 +DS_STATUS_MISC_ERROR=1 +DS_STATUS_TIMED_OUT=2 +#DS_STATUS_FAILED_TO_START=3 + +ATTEMPTS=10 + +# Send the environment to sesexec +parse_env_file() +{ + while read -r line; do + case "$line" in + *=*) echo "$line" ;; + esac + done + + # We must do this to say the parse has been done + return 0 +} + +# ------------------------------------------------------------------------------ +# C H E C K R E Q U I R E D E N V I R O N M E N T +# ------------------------------------------------------------------------------ +#ok=true + +# shellcheck disable=SC2043 +for envvar in XRDP_SESSION XDG_RUNTIME_DIR; do + eval val="\$$envvar" + if [ -z "$val" ]; then + echo "Environment variable '$envvar' is missing" >&2 + ok=false + fi +done >/dev/null + +if ! $ok; then + exit "$DS_STATUS_MISC_ERROR" +fi + +# Construct the name for the environment file +mkdir -p "$XDG_RUNTIME_DIR/xrdp" +env_file="$XDG_RUNTIME_DIR/xrdp/session-$XRDP_SESSION.env" + +# Wait for the env file to appear. We have to jump through some hoops +# with Bourne shell to get an atomic test-and-open on the file. +file_parsed=false +count="$ATTEMPTS" + +exec 4>&2 ; # Save stderr in FD 4 +while [ "$count" -gt 0 ]; do + exec 2>/dev/null ;# Close stderr for the open attempt + # Try to open the file and parse it. If the open succeeds, during + # the call, the env_file is on stdin, and stderr is working + parse_env_file <"$env_file" 2>&4 + status=$? + # Put stderr back again + exec 2>&4 + + if [ $status -eq 0 ]; then + file_parsed=true + rm "$env_file" + break + fi + sleep 1 + count=$((count - 1)) +done +exec 4>/dev/null + +if ! $file_parsed; then + echo "Timed out waiting for display server" + exit "$DS_STATUS_TIMED_OUT" +fi + +exit "$DS_STATUS_OK" diff --git a/sesman/sesexec/xwait.c b/sesman/sesexec/xwait.c deleted file mode 100644 index 7531d4514e..0000000000 --- a/sesman/sesexec/xwait.c +++ /dev/null @@ -1,186 +0,0 @@ -#if defined(HAVE_CONFIG_H) -#include "config_ac.h" -#endif - -#include "env.h" -#include "log.h" -#include "os_calls.h" -#include "string_calls.h" -#include "xwait.h" - -#include -#include - -/******************************************************************************/ -static void -log_waitforx_messages(FILE *dp) -{ - char buffer[256]; - while (fgets(buffer, sizeof(buffer), dp)) - { - const char *msg = buffer; - enum logLevels level = LOG_LEVEL_ERROR; - - g_strtrim(buffer, 2); - - // Has the message got a class at the start? - if (strlen(buffer) > 3 && buffer[0] == '<' && buffer[2] == '>') - { - switch (buffer[1]) - { - case 'D': - level = LOG_LEVEL_DEBUG; - break; - case 'I': - level = LOG_LEVEL_INFO; - break; - case 'W': - level = LOG_LEVEL_WARNING; - break; - default: - level = LOG_LEVEL_ERROR; - break; - } - msg = buffer + 3; - } - - if (strlen(msg) > 0) - { - LOG(level, "waitforx: %s", msg); - } - } -} - -/******************************************************************************/ -/** - * Contruct the command to run to check the X server - */ -static struct list * -make_xwait_command(int display) -{ - const char exe[] = XRDP_LIBEXEC_PATH "/waitforx"; - char displaystr[64]; - - struct list *cmd = list_create(); - if (cmd != NULL) - { - cmd->auto_free = 1; - g_snprintf(displaystr, sizeof(displaystr), ":%d", display); - - if (!list_add_strdup_multi(cmd, exe, "-d", displaystr, NULL)) - { - list_delete(cmd); - cmd = NULL; - } - } - - return cmd; -} - -/******************************************************************************/ -enum xwait_status -wait_for_xserver(uid_t uid, - struct list *env_names, - struct list *env_values, - int display) -{ - enum xwait_status rv = XW_STATUS_MISC_ERROR; - int fd[2] = {-1, -1}; - struct list *cmd = make_xwait_command(display); - - - // Construct the command to execute to check the display - if (cmd == NULL) - { - LOG(LOG_LEVEL_ERROR, "Can't create xwait command list - no mem"); - } - else if (g_pipe(fd) != 0) - { - LOG(LOG_LEVEL_ERROR, "Can't create pipe : %s", g_get_strerror()); - } - else - { - pid_t pid = g_fork(); - if (pid < 0) - { - // Error already logged - } - else if (pid == 0) - { - /* Child process */ - - /* Send stdout and stderr up the pipe */ - g_file_close(fd[0]); - g_file_duplicate_on(fd[1], 1); - g_file_duplicate_on(fd[1], 2); - - /* Move to the user context... */ - env_set_user(uid, - 0, - display, - env_names, - env_values); - - /* ...and run the program */ - g_execvp_list((const char *)cmd->items[0], cmd); - LOG(LOG_LEVEL_ERROR, "Can't run %s - %s", - (const char *)cmd->items[0], g_get_strerror()); - g_exit(rv); - } - else - { - LOG(LOG_LEVEL_DEBUG, - "Waiting for X server to start on display :%d", - display); - - g_file_close(fd[1]); - fd[1] = -1; - FILE *dp = fdopen(fd[0], "r"); - if (dp == NULL) - { - LOG(LOG_LEVEL_ERROR, "Unable to launch waitforx"); - } - else - { - struct proc_exit_status e; - - fd[0] = -1; // File descriptor closed by fclose() - log_waitforx_messages(dp); - fclose(dp); - e = g_waitpid_status(pid); - switch (e.reason) - { - case E_PXR_STATUS_CODE: - rv = (enum xwait_status)e.val; - break; - - case E_PXR_SIGNAL: - { - char sigstr[MAXSTRSIGLEN]; - LOG(LOG_LEVEL_ERROR, - "waitforx failed with unexpected signal %s", - g_sig2text(e.val, sigstr)); - } - break; - - default: - LOG(LOG_LEVEL_ERROR, - "waitforx failed with unknown reason"); - } - } - } - if (fd[0] >= 0) - { - g_file_close(fd[0]); - } - - if (fd[1] >= 0) - { - g_file_close(fd[1]); - } - } - - list_delete(cmd); - - return rv; -} diff --git a/sesman/sesexec/xwait.h b/sesman/sesexec/xwait.h deleted file mode 100644 index 236307dd5d..0000000000 --- a/sesman/sesexec/xwait.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef XWAIT_H -#define XWAIT_H - -#include - -enum xwait_status -{ - XW_STATUS_OK = 0, - XW_STATUS_MISC_ERROR, - XW_STATUS_TIMED_OUT, - XW_STATUS_FAILED_TO_START -}; - -/** - * - * @brief waits for X to start - * @param uid User to run program under - * @param env_names Environment to set for user (names) - * @param env_values Environment to set for user (values) - * @param display number - * @return status - * - */ -enum xwait_status -wait_for_xserver(uid_t uid, - struct list *env_names, - struct list *env_values, - int display); -#endif diff --git a/sesman/sesman.ini.in b/sesman/sesman.ini.in index ae9c6b0a8f..bb0a3affd4 100644 --- a/sesman/sesman.ini.in +++ b/sesman/sesman.ini.in @@ -180,6 +180,19 @@ param=-localhost param=-dpi param=96 +[Labwc] +#LabwcExe=labwc +#WayvncExe=wayvnc +; Enable debug logs +; +; The labwc log cannot be qualified with a WAYLAND_DISPLAY. If a user +; has two or more labwc sessions running at once, the labwc logs for +; the sessions will collide. +#EnableLabwcLog=0 +#EnableWayvncLog=0 +; Log file path (the default is in $XDG_DATA_HOME/xrdp) +#LogFilePath=/run/user/%u/xrdp + [Chansrv] ; drive redirection ; See sesman.ini(5) for the format of this parameter diff --git a/sesman/sesman_restart.c b/sesman/sesman_restart.c index 7b5062d28a..69b3ac5427 100644 --- a/sesman/sesman_restart.c +++ b/sesman/sesman_restart.c @@ -150,7 +150,7 @@ add_sesexec_fd_to_session_list(const char *filename) // E_SESSION_STARTING state s_item->sesexec_trans = t; s_item->sesexec_pid = sesexec_pid; - s_item->display = -1; + s_item->display[0] = '\0'; // Tell the caller we've added one status = 1; diff --git a/sesman/session_list.c b/sesman/session_list.c index 073b8b2370..1d7eaee139 100644 --- a/sesman/session_list.c +++ b/sesman/session_list.c @@ -160,7 +160,7 @@ session_list_new(void) /******************************************************************************/ void -session_list_get_session_displays(struct set_int *alloc_displays) +session_list_get_session_x11_displays(struct set_int *alloc_displays) { int count = (g_session_list == NULL) ? 0 : g_session_list->count; @@ -170,9 +170,10 @@ session_list_get_session_displays(struct set_int *alloc_displays) struct session_item *si; si = (struct session_item *)list_get_item(g_session_list, i); - if (SESSION_IN_USE(si)) + if (SESSION_IN_USE(si) && si->display[0] == ':') { - set_int_add(alloc_displays, si->display); + int displaynum = g_atoi(si->display + 1); + set_int_add(alloc_displays, displaynum); } } } @@ -275,7 +276,7 @@ session_list_get_bydata(uid_t uid, } LOG(LOG_LEVEL_DEBUG, - "%s: Got match, display=%d", __func__, si->display); + "%s: Got match, display=%s", __func__, si->display); return si; } @@ -337,7 +338,7 @@ session_list_get_byuid(const uid_t *uid, unsigned int *cnt, unsigned int flags) if (SESSION_IN_USE(si) && (uid == NULL || *uid == si->uid)) { sess[index].sid = si->sesexec_pid; - sess[index].display = si->display; + sess[index].display = g_strdup(si->display); sess[index].type = si->type; sess[index].height = si->start_height; sess[index].width = si->start_width; @@ -350,7 +351,8 @@ session_list_get_byuid(const uid_t *uid, unsigned int *cnt, unsigned int flags) sess[index].last_connect_disconnect = si->last_connect_disconnect; /* Check for string allocation failures */ - if (sess[index].start_ip_addr == NULL || + if (sess[index].display == NULL || + sess[index].start_ip_addr == NULL || sess[index].client_ip == NULL || sess[index].client_name == NULL) { @@ -394,6 +396,7 @@ free_session_info_list(struct scp_session_info *sesslist, unsigned int cnt) unsigned int i; for (i = 0 ; i < cnt ; ++i) { + g_free(sesslist[i].display); g_free(sesslist[i].start_ip_addr); g_free(sesslist[i].client_ip); g_free(sesslist[i].client_name); diff --git a/sesman/session_list.h b/sesman/session_list.h index bca1acb408..65e86419dc 100644 --- a/sesman/session_list.h +++ b/sesman/session_list.h @@ -58,8 +58,8 @@ struct session_item struct trans *sesexec_trans; // trans for sesexec process. Always valid. pid_t sesexec_pid; // pid for sesexec process. Always valid /** - * May be valid if known when the session is starting, otherwise -1 */ - int display; + * May be valid if known when the session is starting, otherwise "" */ + char display[SCP_DISPLAY_NAME_SIZE]; uid_t uid; enum scp_session_type type; unsigned short start_width; @@ -123,10 +123,10 @@ session_list_new(void); /** * @brief Get all session displays * - * Adds displays allocated to sessions to a set + * Adds X11 displays allocated to sessions to a set */ void -session_list_get_session_displays(struct set_int *alloc_displays); +session_list_get_session_x11_displays(struct set_int *alloc_displays); /** * diff --git a/sesman/tools/authtest.c b/sesman/tools/authtest.c index 9224204b95..ff1ca374a2 100644 --- a/sesman/tools/authtest.c +++ b/sesman/tools/authtest.c @@ -304,10 +304,10 @@ main(int argc, char **argv) rv = (int)errorcode; if (auth_info && rv == 0 && amp.command != NULL) { - int display = 10; - rv = auth_start_session(auth_info, display); + int x11_display = 10; + rv = auth_start_session(auth_info, x11_display); LOG(LOG_LEVEL_INFO, "auth_start_session(,%d) returned %d", - display, rv); + x11_display, rv); if (rv == 0) { rv = g_system(amp.command); diff --git a/sesman/tools/dis.c b/sesman/tools/dis.c index 2e744ef719..814f2b3dfb 100644 --- a/sesman/tools/dis.c +++ b/sesman/tools/dis.c @@ -35,10 +35,9 @@ int main(int argc, char **argv) { int sck; - int dis; + char disstr[32]; struct sockaddr_un sa; size_t len; - char *display; if (argc != 1) { @@ -47,23 +46,16 @@ int main(int argc, char **argv) return 0; } - display = getenv("DISPLAY"); - - if (display == 0) + if (g_get_display_string(disstr, sizeof(disstr)) < 0) { - printf("display not set\n"); + printf("Can't find the display from the environment\n"); return 1; } - dis = g_get_display_num_from_display(display); - if (dis < 0) - { - printf("Can't parse DISPLAY='%s'\n", display); - return 1; - } memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; - sprintf(sa.sun_path, XRDP_DISCONNECT_STR, g_getuid(), dis); + g_snprintf(sa.sun_path, sizeof(sa.sun_path), XRDP_DISCONNECT_STR, + g_getuid(), STRIP_COLON(disstr)); if (access(sa.sun_path, F_OK) != 0) { diff --git a/sesman/tools/sesadmin.c b/sesman/tools/sesadmin.c index 18e02335b9..38e2b80323 100644 --- a/sesman/tools/sesadmin.c +++ b/sesman/tools/sesadmin.c @@ -141,7 +141,7 @@ print_session(const struct scp_session_info *s) uptr = (username == NULL) ? "" : username; printf("Session ID: %d\n", s->sid); - printf("\tDisplay: :%u\n", s->display); + printf("\tDisplay: %s\n", s->display); printf("\tUser: %s\n", uptr); printf("\tSession type: %s\n", SCP_SESSION_TYPE_TO_STR(s->type)); printf("\tScreen size: %dx%d, color depth %d\n", diff --git a/sesman/tools/sesrun.c b/sesman/tools/sesrun.c index c4a9817dc3..716b0babb7 100644 --- a/sesman/tools/sesrun.c +++ b/sesman/tools/sesrun.c @@ -85,6 +85,7 @@ static struct { "Xvnc", SCP_SESSION_TYPE_XVNC}, { "Xvnc-UDS", SCP_SESSION_TYPE_XVNC_UDS}, { "Xorg", SCP_SESSION_TYPE_XORG}, + { "labwc-vnc", SCP_SESSION_TYPE_LABWC_OVER_VNC}, { NULL, (enum scp_session_type) - 1} }; @@ -510,7 +511,7 @@ static int handle_create_session_response(struct trans *t) { enum scp_screate_status status; - int display; + const char *display; struct guid guid; int rv = scp_sync_wait_specific(t, E_SCP_CREATE_SESSION_RESPONSE); @@ -531,7 +532,7 @@ handle_create_session_response(struct trans *t) else { char guid_str[GUID_STR_SIZE]; - g_printf("ok display=:%d GUID=%s\n", + g_printf("ok display=%s GUID=%s\n", display, guid_to_str(&guid, guid_str)); } diff --git a/waitforx/waitforx.c b/waitforx/waitforx.c index c12c2f1430..2df17efa78 100644 --- a/waitforx/waitforx.c +++ b/waitforx/waitforx.c @@ -11,7 +11,7 @@ #include "os_calls.h" #include "string_calls.h" #include "xrdp_sockets.h" -#include "xwait.h" // For return status codes +#include "display_server_util.h" // For return status codes #define ATTEMPTS 10 #define ALARM_WAIT 30 @@ -26,7 +26,7 @@ alarm_handler(int signal_num) * has been partly output */ const char msg[] = "\nTimed out waiting for X display\n"; g_file_write(1, msg, g_strlen(msg)); - exit(XW_STATUS_TIMED_OUT); + exit(DS_STATUS_TIMED_OUT); } /*****************************************************************************/ @@ -224,7 +224,7 @@ main(int argc, char **argv) { const char *display_name = NULL; int opt; - int status = XW_STATUS_MISC_ERROR; + int status = DS_STATUS_MISC_ERROR; Display *dpy = NULL; /* Disable stdout buffering so any messages are passed immediately @@ -254,13 +254,13 @@ main(int argc, char **argv) if (!dpy) { printf("Unable to open display %s\n", display_name); - status = XW_STATUS_FAILED_TO_START; + status = DS_STATUS_FAILED_TO_START; } else { if (wait_for_r_and_r(dpy) == 0) { - status = XW_STATUS_OK; + status = DS_STATUS_OK; } XCloseDisplay(dpy); } diff --git a/xrdp/xrdp.ini.in b/xrdp/xrdp.ini.in index e7ea9823f2..79cde0794c 100644 --- a/xrdp/xrdp.ini.in +++ b/xrdp/xrdp.ini.in @@ -370,3 +370,18 @@ password=ask #channel.cliprdr=true #channel.rail=true #channel.xrdpvr=true + +; This is currently experimental only. +[labwc-vnc] +name=labwc-vnc +lib=libvnc.@lib_extension@ +username=ask +password=ask +; Set port to -1 (sesman controlled). Other values do not work at present. +port=-1 +; Tell xrdp we're using labwc. +code=1001 +; These parameters are VNC module specific +#xserverbpp=24 +#delay_ms=2000 +#disabled_encodings_mask=0 diff --git a/xrdp/xrdp_mm.c b/xrdp/xrdp_mm.c index 700ffc34d6..a64a396133 100644 --- a/xrdp/xrdp_mm.c +++ b/xrdp/xrdp_mm.c @@ -317,10 +317,20 @@ xrdp_mm_create_session(struct xrdp_mm *self) type = SCP_SESSION_TYPE_XVNC_UDS; break; - case XORG_SESSION_CODE: + case XORG_SESSION_CODE: type = SCP_SESSION_TYPE_XORG; break; +#if 0 + case LABWC_SESSION_CODE: + type = SCP_SESSION_TYPE_LABWC; + break; +#endif + + case LABWC_OVER_VNC_SESSION_CODE: + type = SCP_SESSION_TYPE_LABWC_OVER_VNC; + break; + default: xrdp_wm_log_msg(self->wm, LOG_LEVEL_ERROR, "Unrecognised session code %d", self->code); @@ -2682,7 +2692,7 @@ static int xrdp_mm_process_create_session_response(struct xrdp_mm *self) { enum scp_screate_status status; - int display; + const char *display; struct guid guid; int rv; @@ -2704,11 +2714,11 @@ xrdp_mm_process_create_session_response(struct xrdp_mm *self) if (status == E_SCP_SCREATE_OK) { xrdp_wm_log_msg(self->wm, LOG_LEVEL_INFO, - "session is available on display %d for user %s", + "session is available on display %s for user %s", display, username); /* Carry on with the connect state machine */ - self->display = display; + strlcpy(self->display, display, sizeof(self->display)); self->guid = guid; xrdp_mm_connect_sm(self); } @@ -2849,7 +2859,7 @@ cleanup_states(struct xrdp_mm *self) //self->sesman_trans = NULL; /* connection to sesman */ self->chan_trans = NULL; /* connection to chansrv */ self->delete_sesman_trans = 0; - self->display = 0; /* 10 for :10.0, 11 for :11.0, etc */ + memset(self->display, '\0', sizeof(self->display)); guid_clear(&self->guid); self->code = 0; /* ???_SESSION_CODE value */ close_sesman_file_descriptors(self); @@ -2878,7 +2888,7 @@ static int parse_chansrvport(const char *value, char *dest, int dest_size, int uid) { int rv = 0; - int dnum = 0; + char dstr[32]; if (value == NULL) { @@ -2891,21 +2901,21 @@ parse_chansrvport(const char *value, char *dest, int dest_size, int uid) const char *p = value + 8; const char *end = p; - /* Check next chars are digits */ - while (isdigit(*end)) + /* Look for a ',' or ')', or '\0' */ + while (*end != ',' && *end != ')' && *end != '\0') { ++end; } - if (end == p) + if (end == p || (unsigned int)(end - p) > (sizeof(dstr) - 1)) { LOG(LOG_LEVEL_WARNING, - "Ignoring chansrvport string with bad display number '%s'", + "Ignoring chansrvport string with bad display string '%s'", value); return -1; } - - dnum = g_atoi(p); + memcpy(dstr, p, end - p); + dstr[end - p] = '\0'; if (*end == ',') { @@ -2937,7 +2947,7 @@ parse_chansrvport(const char *value, char *dest, int dest_size, int uid) return -1; } - g_snprintf(dest, dest_size, XRDP_CHANSRV_STR, uid, dnum); + g_snprintf(dest, dest_size, XRDP_CHANSRV_STR, uid, STRIP_COLON(dstr)); } else { @@ -5305,21 +5315,29 @@ xrdp_mm_setup_mod2(struct xrdp_mm *self) if (!g_is_wait_obj_set(self->wm->pro_layer->self_term_event)) { - if (self->display < 0) - { - LOG(LOG_LEVEL_ERROR, - "Unexpected display value %d setting up module", self->display); - xrdp_mm_set_fatal(self, ERRINFO_SERVER_DWM_CRASH); - } - else if (self->code == XVNC_SESSION_CODE) + if (self->code == XVNC_SESSION_CODE) { - g_snprintf(text, sizeof(text), "%d", 5900 + self->display); + // We have to make assumptions about the display format for + // classic VNC + if (self->display[0] != ':' || !isdigit(self->display[1])) + { + LOG(LOG_LEVEL_ERROR, + "Unexpected display value %s setting up VNC module", + self->display); + xrdp_mm_set_fatal(self, ERRINFO_SERVER_DWM_CRASH); + } + else + { + g_snprintf(text, sizeof(text), "%d", + 5900 + g_atoi(self->display + 1)); + } } else if (self->code == XORG_SESSION_CODE || - self->code == XVNC_UDS_SESSION_CODE) + self->code == XVNC_UDS_SESSION_CODE || + self->code == LABWC_OVER_VNC_SESSION_CODE) { g_snprintf(text, sizeof(text), XRDP_X11RDP_STR, - self->uid, self->display); + self->uid, STRIP_COLON(self->display)); } else { diff --git a/xrdp/xrdp_types.h b/xrdp/xrdp_types.h index 231db9fa15..e0b4a4c063 100644 --- a/xrdp/xrdp_types.h +++ b/xrdp/xrdp_types.h @@ -39,11 +39,12 @@ #define XVNC_SESSION_CODE 0 #define XVNC_UDS_SESSION_CODE 1 #define XORG_SESSION_CODE 20 +/* #define LABWC_SESSION_CODE 1000 */ +#define LABWC_OVER_VNC_SESSION_CODE 1001 /* To check whether touch events has been implemented on session type 'mm' */ #define XRDP_MM_IMPLEMENTS_TOUCH(mm) \ - (((mm)->code != XVNC_SESSION_CODE) && \ - ((mm)->code != XVNC_UDS_SESSION_CODE)) + (((mm)->code == XORG_SESSION_CODE)) struct source_info; struct list16; @@ -439,7 +440,7 @@ struct xrdp_mm struct xrdp_mod *(*mod_init)(void); int (*mod_exit)(struct xrdp_mod *); struct xrdp_mod *mod; /* module interface */ - int display; /* 10 for :10.0, 11 for :11.0, etc */ + char display[32]; /* ":n" or "wayland-n" */ int uid; /* UID for a successful login, -1 otherwise */ struct guid guid; /* GUID for the session, or all zeros */ int code; /* 0=Xvnc session, 20=xorg driver mode */ diff --git a/xrdpapi/xrdpapi.c b/xrdpapi/xrdpapi.c index b8ce4ff244..f444a4a863 100644 --- a/xrdpapi/xrdpapi.c +++ b/xrdpapi/xrdpapi.c @@ -21,6 +21,7 @@ #include #endif +#include #include #include #include @@ -42,7 +43,7 @@ struct wts_obj { int fd; - int display_num; + char display_name[32]; }; /* helper functions used by WTSxxx API - do not invoke directly */ @@ -112,6 +113,7 @@ WTSVirtualChannelOpenEx(unsigned int SessionId, const char *pVirtualName, char *connect_data; int chan_name_bytes; int lerrno; + const char *display; if (SessionId != WTS_CURRENT_SESSION) { @@ -125,14 +127,21 @@ WTSVirtualChannelOpenEx(unsigned int SessionId, const char *pVirtualName, return 0; } wts->fd = -1; - wts->display_num = g_get_display_num_from_display(getenv("DISPLAY")); - if (wts->display_num < 0) + + if ((display = getenv("WAYLAND_DISPLAY")) == NULL) { - LOG(LOG_LEVEL_ERROR, "WTSVirtualChannelOpenEx: fatal error; invalid DISPLAY"); - free_wts(wts); - return NULL; + display = getenv("DISPLAY"); + if (display == NULL || display[0] != ':' || !isdigit(display[1])) + { + LOG(LOG_LEVEL_ERROR, + "WTSVirtualChannelOpenEx: fatal error; invalid DISPLAY"); + free_wts(wts); + return NULL; + } } + strlcpy(wts->display_name, display, sizeof(wts->display_name)); + /* we use unix domain socket to communicate with chansrv */ if ((wts->fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { @@ -153,7 +162,8 @@ WTSVirtualChannelOpenEx(unsigned int SessionId, const char *pVirtualName, memset(&s, 0, sizeof(struct sockaddr_un)); s.sun_family = AF_UNIX; bytes = sizeof(s.sun_path); - snprintf(s.sun_path, bytes - 1, CHANSRV_API_STR, getuid(), wts->display_num); + snprintf(s.sun_path, bytes - 1, CHANSRV_API_STR, + getuid(), STRIP_COLON(wts->display_name)); s.sun_path[bytes - 1] = 0; bytes = sizeof(struct sockaddr_un);