1 #include <libssh/libssh.h>
2 #include <libssh/server.h>
3 #include <libssh/callbacks.h>
4 #include <sys/socket.h>
5 #include <netinet/tcp.h>
6 #include <sys/wait.h>
7 #include <ctype.h>
8 #include <stdio.h>
9 #include <signal.h>
10 #include <event.h>
11 #include <arpa/inet.h>
12 #ifndef IPPROTO_TCP
13 #include <netinet/in.h>
14 #endif
15
16 #include "tmate.h"
17
get_ssh_conn_string(const char * session_token)18 char *get_ssh_conn_string(const char *session_token)
19 {
20 char port_arg[16] = {0};
21 char *ret;
22
23 int ssh_port_advertized = tmate_settings->ssh_port_advertized == -1 ?
24 tmate_settings->ssh_port :
25 tmate_settings->ssh_port_advertized;
26
27 if (ssh_port_advertized != 22)
28 sprintf(port_arg, " -p%d", ssh_port_advertized);
29 xasprintf(&ret, "ssh%s %s@%s", port_arg, session_token, tmate_settings->tmate_host);
30 return ret;
31 }
32
pty_request(__unused ssh_session session,__unused ssh_channel channel,__unused const char * term,int width,int height,__unused int pxwidth,__unused int pwheight,void * userdata)33 static int pty_request(__unused ssh_session session,
34 __unused ssh_channel channel,
35 __unused const char *term,
36 int width, int height,
37 __unused int pxwidth, __unused int pwheight,
38 void *userdata)
39 {
40 struct tmate_ssh_client *client = userdata;
41
42 client->winsize_pty.ws_col = width;
43 client->winsize_pty.ws_row = height;
44
45 return 0;
46 }
47
shell_request(__unused ssh_session session,__unused ssh_channel channel,void * userdata)48 static int shell_request(__unused ssh_session session,
49 __unused ssh_channel channel,
50 void *userdata)
51 {
52 struct tmate_ssh_client *client = userdata;
53
54 if (client->role)
55 return 1;
56
57 client->role = TMATE_ROLE_PTY_CLIENT;
58
59 return 0;
60 }
61
subsystem_request(__unused ssh_session session,__unused ssh_channel channel,const char * subsystem,void * userdata)62 static int subsystem_request(__unused ssh_session session,
63 __unused ssh_channel channel,
64 const char *subsystem, void *userdata)
65 {
66 struct tmate_ssh_client *client = userdata;
67
68 if (client->role)
69 return 1;
70
71 if (!strcmp(subsystem, "tmate"))
72 client->role = TMATE_ROLE_DAEMON;
73
74 return 0;
75 }
76
exec_request(__unused ssh_session session,__unused ssh_channel channel,const char * command,void * userdata)77 static int exec_request(__unused ssh_session session,
78 __unused ssh_channel channel,
79 const char *command, void *userdata)
80 {
81 struct tmate_ssh_client *client = userdata;
82
83 if (client->role)
84 return 1;
85
86 if (!tmate_has_websocket())
87 return 1;
88
89 client->role = TMATE_ROLE_EXEC;
90 client->exec_command = xstrdup(command);
91
92 return 0;
93 }
94
channel_open_request_cb(ssh_session session,void * userdata)95 static ssh_channel channel_open_request_cb(ssh_session session, void *userdata)
96 {
97 struct tmate_ssh_client *client = userdata;
98
99 if (!client->username) {
100 /* The authentication did not go through yet */
101 return NULL;
102 }
103
104 if (client->channel) {
105 /*
106 * We already have a channel, and we don't support multi
107 * channels yet. Returning NULL means the channel request will
108 * be denied.
109 */
110 return NULL;
111 }
112
113 client->channel = ssh_channel_new(session);
114 if (!client->channel)
115 tmate_fatal("Error getting channel");
116
117 memset(&client->channel_cb, 0, sizeof(client->channel_cb));
118 ssh_callbacks_init(&client->channel_cb);
119 client->channel_cb.userdata = client;
120 client->channel_cb.channel_pty_request_function = pty_request;
121 client->channel_cb.channel_shell_request_function = shell_request;
122 client->channel_cb.channel_subsystem_request_function = subsystem_request;
123 client->channel_cb.channel_exec_request_function = exec_request;
124 ssh_set_channel_callbacks(client->channel, &client->channel_cb);
125
126 return client->channel;
127 }
128
auth_pubkey_cb(__unused ssh_session session,const char * user,ssh_key pubkey,char signature_state,void * userdata)129 static int auth_pubkey_cb(__unused ssh_session session,
130 const char *user,
131 ssh_key pubkey,
132 char signature_state, void *userdata)
133 {
134 struct tmate_ssh_client *client = userdata;
135
136 switch (signature_state) {
137 case SSH_PUBLICKEY_STATE_VALID:
138 client->username = xstrdup(user);
139
140 const char *key_type = ssh_key_type_to_char(ssh_key_type(pubkey));
141
142 char *b64_key;
143 if (ssh_pki_export_pubkey_base64(pubkey, &b64_key) != SSH_OK)
144 tmate_fatal("error getting public key");
145
146 char *pubkey64;
147 xasprintf(&pubkey64, "%s %s", key_type, b64_key);
148 free(b64_key);
149
150 if (!would_tmate_session_allow_auth(user, pubkey64)) {
151 free(pubkey64);
152 return SSH_AUTH_DENIED;
153 }
154
155 client->pubkey = pubkey64;
156
157 return SSH_AUTH_SUCCESS;
158 case SSH_PUBLICKEY_STATE_NONE:
159 return SSH_AUTH_SUCCESS;
160 default:
161 return SSH_AUTH_DENIED;
162 }
163 }
164
auth_none_cb(__unused ssh_session session,const char * user,void * userdata)165 static int auth_none_cb(__unused ssh_session session, const char *user, void *userdata)
166 {
167 struct tmate_ssh_client *client = userdata;
168
169 if (!would_tmate_session_allow_auth(user, NULL))
170 return SSH_AUTH_DENIED;
171
172 client->username = xstrdup(user);
173 client->pubkey = NULL;
174
175 return SSH_AUTH_SUCCESS;
176 }
177
178 static struct ssh_server_callbacks_struct ssh_server_cb = {
179 .auth_pubkey_function = auth_pubkey_cb,
180 .auth_none_function = auth_none_cb,
181 .channel_open_request_session_function = channel_open_request_cb,
182 };
183
on_ssh_read(__unused evutil_socket_t fd,__unused short what,void * arg)184 static void on_ssh_read(__unused evutil_socket_t fd, __unused short what, void *arg)
185 {
186 struct tmate_ssh_client *client = arg;
187 ssh_execute_message_callbacks(client->session);
188
189 if (!ssh_is_connected(client->session)) {
190 tmate_debug("ssh disconnected");
191
192 event_del(&client->ev_ssh);
193
194 /* For graceful tmux client termination */
195 request_server_termination();
196 }
197 }
198
register_on_ssh_read(struct tmate_ssh_client * client)199 static void register_on_ssh_read(struct tmate_ssh_client *client)
200 {
201 event_set(&client->ev_ssh, ssh_get_fd(client->session),
202 EV_READ | EV_PERSIST, on_ssh_read, client);
203 event_add(&client->ev_ssh, NULL);
204 }
205
handle_sigalrm(__unused int sig)206 static void handle_sigalrm(__unused int sig)
207 {
208 tmate_fatal_quiet("Connection grace period (%ds) passed", TMATE_SSH_GRACE_PERIOD);
209 }
210
client_bootstrap(struct tmate_session * _session)211 static void client_bootstrap(struct tmate_session *_session)
212 {
213 struct tmate_ssh_client *client = &_session->ssh_client;
214 long grace_period = TMATE_SSH_GRACE_PERIOD;
215 ssh_event mainloop;
216 ssh_session session = client->session;
217
218 tmate_debug("Bootstrapping ssh client ip=%s", client->ip_address);
219
220 _session->ev_base = osdep_event_init();
221
222 /* new process group, we don't want to die with our parent (upstart) */
223 setpgid(0, 0);
224
225 {
226 int flag = 1;
227 setsockopt(ssh_get_fd(session), IPPROTO_TCP, TCP_NODELAY,
228 &flag, sizeof(flag));
229 }
230
231 /*
232 * We should die early if we can't connect to websocket server. This
233 * way the tmate daemon will pick another server to work on.
234 */
235 _session->websocket_fd = -1;
236 if (tmate_has_websocket())
237 _session->websocket_fd = tmate_connect_to_websocket();
238
239 ssh_server_cb.userdata = client;
240 ssh_callbacks_init(&ssh_server_cb);
241 ssh_set_server_callbacks(client->session, &ssh_server_cb);
242
243 ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &grace_period);
244 ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes");
245 ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, "curve25519-sha256@libssh.org,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group16-sha512");
246 ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, "aes256-gcm@openssh.com,aes128-gcm@openssh.com,chacha20-poly1305@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr");
247 ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, "aes256-gcm@openssh.com,aes128-gcm@openssh.com,chacha20-poly1305@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr");
248 ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com");
249 ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, "hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com");
250 ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-ed25519,ssh-rsa");
251
252 ssh_set_auth_methods(client->session, SSH_AUTH_METHOD_NONE |
253 SSH_AUTH_METHOD_PUBLICKEY);
254
255 tmate_debug("Exchanging DH keys");
256 if (ssh_handle_key_exchange(session) < 0)
257 tmate_fatal_quiet("Error doing the key exchange: %s", ssh_get_error(session));
258
259 mainloop = ssh_event_new();
260 ssh_event_add_session(mainloop, session);
261
262 while (!client->role) {
263 if (ssh_event_dopoll(mainloop, -1) == SSH_ERROR)
264 tmate_fatal_quiet("Error polling ssh socket: %s", ssh_get_error(session));
265 }
266
267 alarm(0);
268
269 /* The latency callback is set later */
270 start_keepalive_timer(client, TMATE_SSH_KEEPALIVE_SEC * 1000);
271 register_on_ssh_read(client);
272
273 switch (client->role) {
274 case TMATE_ROLE_DAEMON: tmate_spawn_daemon(_session); break;
275 case TMATE_ROLE_PTY_CLIENT: tmate_spawn_pty_client(_session); break;
276 case TMATE_ROLE_EXEC: tmate_spawn_exec(_session); break;
277 }
278 /* never reached */
279 }
280
get_client_ip_socket(int fd,char * dst,size_t len)281 static int get_client_ip_socket(int fd, char *dst, size_t len)
282 {
283 struct sockaddr sa;
284 socklen_t sa_len = sizeof(sa);
285
286 if (getpeername(fd, &sa, &sa_len) < 0)
287 return -1;
288
289
290 switch (sa.sa_family) {
291 case AF_INET:
292 if (!inet_ntop(AF_INET, &((struct sockaddr_in *)&sa)->sin_addr,
293 dst, len))
294 return -1;
295 break;
296 case AF_INET6:
297 if (!inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&sa)->sin6_addr,
298 dst, len))
299 return -1;
300 break;
301 default:
302 return -1;
303 }
304
305 return 0;
306 }
307
read_single_line(int fd,char * dst,size_t len)308 static int read_single_line(int fd, char *dst, size_t len)
309 {
310 /*
311 * This reads exactly one line from fd.
312 * We cannot read bytes after the new line.
313 * We could use recv() with MSG_PEEK to do this more efficiently.
314 */
315 for (size_t i = 0; i < len; i++) {
316 if (read(fd, &dst[i], 1) <= 0)
317 break;
318
319 if (dst[i] == '\r')
320 i--;
321
322 if (dst[i] == '\n') {
323 dst[i] = '\0';
324 return i;
325 }
326 }
327
328 return -1;
329 }
330
get_client_ip_proxy_protocol(int fd,char * dst,size_t len)331 static int get_client_ip_proxy_protocol(int fd, char *dst, size_t len)
332 {
333 char header[110];
334 int tok_num;
335
336 #define SIGNATURE "PROXY "
337 ssize_t ret = read(fd, header, sizeof(SIGNATURE)-1);
338 if (ret <= 0)
339 tmate_fatal_quiet("Disconnected, health checker?");
340 if (ret != sizeof(SIGNATURE)-1)
341 return -1;
342 if (memcmp(header, SIGNATURE, sizeof(SIGNATURE)-1))
343 return -1;
344 #undef SIGNATURE
345
346 if (read_single_line(fd, header, sizeof(header)) < 0)
347 return -1;
348
349 tmate_debug("proxy header: %s", header);
350
351 tok_num = 0;
352 for (char *tok = strtok(header, " "); tok; tok = strtok(NULL, " "), tok_num++) {
353 if (tok_num == 1)
354 strlcpy(dst, tok, len);
355 }
356
357 if (tok_num != 5)
358 return -1;
359
360 return 0;
361 }
362
get_client_ip(int fd,char * dst,size_t len)363 static int get_client_ip(int fd, char *dst, size_t len)
364 {
365 if (tmate_settings->use_proxy_protocol)
366 return get_client_ip_proxy_protocol(fd, dst, len);
367 else
368 return get_client_ip_socket(fd, dst, len);
369 }
370
ssh_log_function(int priority,const char * function,const char * buffer,__unused void * userdata)371 static void ssh_log_function(int priority, const char *function,
372 const char *buffer, __unused void *userdata)
373 {
374 /* loglevel already applied */
375 log_emit(LOG_DEBUG, "[%s] %s", function, buffer);
376 }
377
max(int a,int b)378 static inline int max(int a, int b)
379 {
380 if (a < b)
381 return b;
382 return a;
383 }
384
ssh_import_key(ssh_bind bind,const char * keys_dir,const char * name)385 static void ssh_import_key(ssh_bind bind, const char *keys_dir, const char *name)
386 {
387 char path[PATH_MAX];
388 ssh_key key = NULL;
389
390 sprintf(path, "%s/%s", keys_dir, name);
391
392 if (access(path, F_OK) < 0)
393 return;
394
395 tmate_info("Loading key %s", path);
396
397 ssh_pki_import_privkey_file(path, NULL, NULL, NULL, &key);
398 ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, key);
399 }
400
prepare_ssh(const char * keys_dir,const char * bind_addr,int port)401 static ssh_bind prepare_ssh(const char *keys_dir, const char *bind_addr, int port)
402 {
403 ssh_bind bind;
404 int ssh_log_level;
405
406 ssh_log_level = SSH_LOG_WARNING + max(log_get_level() - LOG_INFO, 0);
407
408 ssh_set_log_callback(ssh_log_function);
409
410 bind = ssh_bind_new();
411 if (!bind)
412 tmate_fatal("Cannot initialize ssh");
413
414 #if LIBSSH_VERSION_INT >= SSH_VERSION_INT(0,9,0)
415 /* Explicitly parse configuration to avoid automatic configuration file
416 * loading which could override options */
417 ssh_bind_options_parse_config(bind, NULL);
418 #endif
419
420 if (bind_addr)
421 ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDADDR, bind_addr);
422 ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT, &port);
423 ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BANNER, TMATE_SSH_BANNER);
424 ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY, &ssh_log_level);
425
426 ssh_import_key(bind, keys_dir, "ssh_host_ed25519_key");
427
428 if (ssh_bind_listen(bind) < 0)
429 tmate_fatal("Error listening to socket: %s\n", ssh_get_error(bind));
430
431 tmate_info("Accepting connections on %s:%d", bind_addr ?: "", port);
432
433 return bind;
434 }
435
handle_sigchld(__unused int sig)436 static void handle_sigchld(__unused int sig)
437 {
438 int status;
439 pid_t pid;
440
441 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) > 0) {
442 /*
443 * It's not safe to call indirectly malloc() here, because
444 * of potential deadlocks with other malloc() calls.
445 */
446 #if 0
447 if (WIFEXITED(status))
448 tmate_debug("Child %d exited (%d)", pid, WEXITSTATUS(status));
449 if (WIFSIGNALED(status))
450 tmate_debug("Child %d killed (%d)", pid, WTERMSIG(status));
451 if (WIFSTOPPED(status))
452 tmate_debug("Child %d stopped (%d)", pid, WSTOPSIG(status));
453 #endif
454 }
455 }
456
tmate_ssh_server_main(struct tmate_session * session,const char * keys_dir,const char * bind_addr,int port)457 void tmate_ssh_server_main(struct tmate_session *session, const char *keys_dir,
458 const char *bind_addr, int port)
459 {
460 struct tmate_ssh_client *client = &session->ssh_client;
461 ssh_bind bind;
462 pid_t pid;
463 int fd;
464
465 tmate_catch_sigsegv();
466 signal(SIGCHLD, handle_sigchld);
467
468 bind = prepare_ssh(keys_dir, bind_addr, port);
469
470 client->session = ssh_new();
471 client->channel = NULL;
472 client->winsize_pty.ws_col = 80;
473 client->winsize_pty.ws_row = 24;
474 session->session_token = "init";
475
476 if (!client->session)
477 tmate_fatal("Cannot initialize session");
478
479 for (;;) {
480 fd = accept(ssh_bind_get_fd(bind), NULL, NULL);
481 if (fd < 0)
482 tmate_fatal("Error accepting connection");
483
484 if ((pid = fork()) < 0)
485 tmate_fatal("Can't fork");
486
487 if (pid) {
488 /* Parent process */
489 close(fd);
490 continue;
491 }
492
493 /* Child process */
494
495 signal(SIGALRM, handle_sigalrm);
496 alarm(TMATE_SSH_GRACE_PERIOD);
497
498 if (get_client_ip(fd, client->ip_address, sizeof(client->ip_address)) < 0) {
499 if (tmate_settings->use_proxy_protocol)
500 tmate_fatal("Proxy header invalid. Load balancer may be misconfigured");
501 else
502 tmate_fatal("Error getting client IP from connection");
503 }
504
505 tmate_debug("Connection accepted ip=%s", client->ip_address);
506
507 if (ssh_bind_accept_fd(bind, client->session, fd) < 0)
508 tmate_fatal("Error accepting connection: %s", ssh_get_error(bind));
509
510 ssh_bind_free(bind);
511
512 client_bootstrap(session);
513 /* never reached */
514 }
515 }
516