@@ -8,6 +8,8 @@ use super::listener;
8
8
use listener:: Connection ;
9
9
use listener:: Listen ;
10
10
use log:: error;
11
+ #[ cfg( not( feature = "no-parsec-user-and-clients-group" ) ) ]
12
+ use std:: ffi:: CString ;
11
13
use std:: fs;
12
14
use std:: fs:: Permissions ;
13
15
use std:: io:: { Error , ErrorKind , Result } ;
@@ -18,6 +20,10 @@ use std::path::Path;
18
20
use std:: time:: Duration ;
19
21
20
22
static SOCKET_PATH : & str = "/tmp/parsec/parsec.sock" ;
23
+ #[ cfg( not( feature = "no-parsec-user-and-clients-group" ) ) ]
24
+ const PARSEC_USERNAME : & str = "parsec" ;
25
+ #[ cfg( not( feature = "no-parsec-user-and-clients-group" ) ) ]
26
+ const PARSEC_GROUPNAME : & str = "parsec-clients" ;
21
27
22
28
/// Unix Domain Socket IPC manager
23
29
///
@@ -32,24 +38,26 @@ pub struct DomainSocketListener {
32
38
33
39
impl DomainSocketListener {
34
40
/// Initialise the connection to the Unix socket.
35
- ///
36
- /// # Panics
37
- /// - if a file/socket exists at the path specified for the socket and `remove_file`
38
- /// fails
39
- /// - if binding to the socket path fails
40
41
pub fn new ( timeout : Duration ) -> Result < Self > {
41
- // If this Parsec instance was socket activated (see the `parsec.socket`
42
+ #[ cfg( not( feature = "no-parsec-user-and-clients-group" ) ) ]
43
+ DomainSocketListener :: check_user_details ( ) ?;
44
+
45
+ // is Parsec instance was socket activated (see the `parsec.socket`
42
46
// file), the listener will be opened by systemd and passed to the
43
47
// process.
44
48
// If Parsec was service activated or not started under systemd, this
45
49
// will return `0`.
46
50
let listener = match sd_notify:: listen_fds ( ) ? {
47
51
0 => {
48
52
let socket = Path :: new ( SOCKET_PATH ) ;
49
-
50
- if socket. exists ( ) {
53
+ let parent_dir = socket. parent ( ) . unwrap ( ) ;
54
+ if !parent_dir. exists ( ) {
55
+ fs:: create_dir_all ( parent_dir) ?;
56
+ } else if socket. exists ( ) {
51
57
fs:: remove_file ( & socket) ?;
52
58
}
59
+ #[ cfg( not( feature = "no-parsec-user-and-clients-group" ) ) ]
60
+ DomainSocketListener :: set_socket_dir_permissions ( parent_dir) ?;
53
61
54
62
let listener = UnixListener :: bind ( SOCKET_PATH ) ?;
55
63
listener. set_nonblocking ( true ) ?;
@@ -84,6 +92,95 @@ impl DomainSocketListener {
84
92
85
93
Ok ( Self { listener, timeout } )
86
94
}
95
+
96
+ #[ cfg( not( feature = "no-parsec-user-and-clients-group" ) ) ]
97
+ fn set_socket_dir_permissions ( parent_dir : & Path ) -> Result < ( ) > {
98
+ if let Some ( parent_dir_str) = parent_dir. to_str ( ) {
99
+ fs:: set_permissions ( parent_dir, Permissions :: from_mode ( 0o750 ) ) ?;
100
+ // Although `parsec` has to be part of the `parsec_clients` group, it may not be the primary group. Therefore force group ownership to `parsec_clients`
101
+ if unsafe {
102
+ let parent_dir_cstr = CString :: new ( parent_dir_str)
103
+ . expect ( "Failed to convert socket path parent to cstring" ) ;
104
+ {
105
+ libc:: chown (
106
+ parent_dir_cstr. as_ptr ( ) ,
107
+ users:: get_current_uid ( ) , // To get to this point, user has to be `parsec`
108
+ users:: get_group_by_name ( PARSEC_GROUPNAME ) . unwrap ( ) . gid ( ) , // `parsec_clients` exists by this point so should be safe
109
+ )
110
+ }
111
+ } != 0
112
+ {
113
+ error ! (
114
+ "Changing ownership of {} to user {} and group {} failed." ,
115
+ parent_dir_str, PARSEC_USERNAME , PARSEC_GROUPNAME
116
+ ) ;
117
+ return Err ( Error :: new (
118
+ ErrorKind :: Other ,
119
+ "Changing ownership of socket directory failed" ,
120
+ ) ) ;
121
+ }
122
+ } else {
123
+ error ! (
124
+ "Error converting {} parent directory to string." ,
125
+ SOCKET_PATH
126
+ ) ;
127
+ return Err ( Error :: new (
128
+ ErrorKind :: InvalidInput ,
129
+ "Error retrieving parent directory for socket" ,
130
+ ) ) ;
131
+ }
132
+ Ok ( ( ) )
133
+ }
134
+
135
+ #[ cfg( not( feature = "no-parsec-user-and-clients-group" ) ) ]
136
+ fn check_user_details ( ) -> Result < ( ) > {
137
+ // Check Parsec is running as parsec user
138
+ if users:: get_current_username ( ) != Some ( PARSEC_USERNAME . into ( ) ) {
139
+ error ! (
140
+ "Incorrect user. Parsec should be run as user {}." ,
141
+ PARSEC_USERNAME
142
+ ) ;
143
+ return Err ( Error :: new (
144
+ ErrorKind :: PermissionDenied ,
145
+ "Parsec run as incorrect user" ,
146
+ ) ) ;
147
+ }
148
+ // Check Parsec client group exists and parsec user is a member of it
149
+ if let Some ( parsec_clients_group) = users:: get_group_by_name ( PARSEC_GROUPNAME ) {
150
+ if let Some ( groups) = users:: get_user_groups ( PARSEC_USERNAME , users:: get_current_gid ( ) )
151
+ {
152
+ // Split to make `clippy` happy
153
+ let parsec_user_in_parsec_clients_group = groups. into_iter ( ) . any ( |group| {
154
+ group. gid ( ) == parsec_clients_group. gid ( )
155
+ && group. name ( ) == parsec_clients_group. name ( )
156
+ } ) ;
157
+ // Check the parsec user is a member of the parsec clients group
158
+ if parsec_user_in_parsec_clients_group {
159
+ return Ok ( ( ) ) ;
160
+ }
161
+ error ! (
162
+ "{} user not a member of {}." ,
163
+ PARSEC_USERNAME , PARSEC_GROUPNAME
164
+ ) ;
165
+ Err ( Error :: new (
166
+ ErrorKind :: PermissionDenied ,
167
+ "User permissions incorrect" ,
168
+ ) )
169
+ } else {
170
+ error ! ( "Retrieval of groups for user {} failed." , PARSEC_USERNAME ) ;
171
+ Err ( Error :: new (
172
+ ErrorKind :: InvalidInput ,
173
+ "Failed to retrieve user groups" ,
174
+ ) )
175
+ }
176
+ } else {
177
+ error ! ( "{} group does not exist." , PARSEC_GROUPNAME ) ;
178
+ Err ( Error :: new (
179
+ ErrorKind :: PermissionDenied ,
180
+ "Group permissions incorrect" ,
181
+ ) )
182
+ }
183
+ }
87
184
}
88
185
89
186
impl Listen for DomainSocketListener {
0 commit comments