1 #include <libssh/server.h>
2 #include <errno.h>
3 #include <sys/ioctl.h>
4 #include <sys/stat.h>
5 #include <signal.h>
6 #include "tmate.h"
7 
on_ssh_channel_read(__unused ssh_session _session,__unused ssh_channel channel,void * _data,uint32_t total_len,__unused int is_stderr,void * userdata)8 static int on_ssh_channel_read(__unused ssh_session _session,
9 			       __unused ssh_channel channel,
10 			       void *_data, uint32_t total_len,
11 			       __unused int is_stderr, void *userdata)
12 {
13 	struct tmate_session *session = userdata;
14 	char *data = _data;
15 	size_t written = 0;
16 	ssize_t len;
17 
18 	if (session->readonly)
19 		return total_len;
20 
21 	setblocking(session->pty, 1);
22 	while (total_len) {
23 		len = write(session->pty, data, total_len);
24 		if (len < 0)
25 			tmate_fatal("Error writing to pty");
26 
27 		total_len -= len;
28 		written += len;
29 		data += len;
30 	}
31 	setblocking(session->pty, 0);
32 
33 	return written;
34 }
35 
on_ssh_message_callback(__unused ssh_session _session,ssh_message msg,void * arg)36 static int on_ssh_message_callback(__unused ssh_session _session,
37 				   ssh_message msg, void *arg)
38 {
39 	struct tmate_session *session = arg;
40 
41 	if (ssh_message_type(msg) == SSH_REQUEST_CHANNEL &&
42 	    ssh_message_subtype(msg) == SSH_CHANNEL_REQUEST_WINDOW_CHANGE) {
43 		struct winsize ws;
44 
45 		ws.ws_col = ssh_message_channel_request_pty_width(msg);
46 		ws.ws_row = ssh_message_channel_request_pty_height(msg);
47 
48 		ioctl(session->pty, TIOCSWINSZ, &ws);
49 		client_signal(SIGWINCH);
50 
51 		return 1;
52 	}
53 	return 0;
54 }
55 
on_pty_event(struct tmate_session * session)56 static void on_pty_event(struct tmate_session *session)
57 {
58 	ssize_t len, written;
59 	char buf[4096];
60 
61 	for (;;) {
62 		len = read(session->pty, buf, sizeof(buf));
63 		if (len < 0) {
64 			if (errno == EAGAIN)
65 				return;
66 			tmate_fatal("pty read error");
67 		}
68 
69 		if (len == 0)
70 			tmate_fatal("pty reached EOF");
71 
72 		written = ssh_channel_write(session->ssh_client.channel, buf, len);
73 		if (written < 0)
74 			tmate_fatal("Error writing to channel: %s",
75 				    ssh_get_error(session->ssh_client.session));
76 		if (len != written)
77 			tmate_fatal("Cannot write %d bytes, wrote %d",
78 				    (int)len, (int)written);
79 	}
80 }
81 
__on_pty_event(__unused evutil_socket_t fd,__unused short what,void * arg)82 static void __on_pty_event(__unused evutil_socket_t fd, __unused short what, void *arg)
83 {
84 	on_pty_event(arg);
85 }
86 
tmate_flush_pty(struct tmate_session * session)87 static void tmate_flush_pty(struct tmate_session *session)
88 {
89 	on_pty_event(session);
90 	close(session->pty);
91 }
92 
tmate_client_pty_init(struct tmate_session * session)93 static void tmate_client_pty_init(struct tmate_session *session)
94 {
95 	struct tmate_ssh_client *client = &session->ssh_client;
96 
97 	ioctl(session->pty, TIOCSWINSZ, &session->ssh_client.winsize_pty);
98 
99 	memset(&client->channel_cb, 0, sizeof(client->channel_cb));
100 	ssh_callbacks_init(&client->channel_cb);
101 	client->channel_cb.userdata = session;
102 	client->channel_cb.channel_data_function = on_ssh_channel_read,
103 	ssh_set_channel_callbacks(client->channel, &client->channel_cb);
104 
105 	ssh_set_message_callback(session->ssh_client.session,
106 				 on_ssh_message_callback, session);
107 
108 	setblocking(session->pty, 0);
109 	event_set(&session->ev_pty, session->pty,
110 		  EV_READ | EV_PERSIST, __on_pty_event, session);
111 	event_add(&session->ev_pty, NULL);
112 }
113 
random_sleep(void)114 static void random_sleep(void)
115 {
116 	struct timespec ts;
117 	ts.tv_sec = 0;
118 	ts.tv_nsec = 50000000 + (tmate_get_random_long() % 150000000);
119 	nanosleep(&ts, NULL);
120 }
121 
122 #define BAD_TOKEN_ERROR_STR						\
123 "Invalid session token"						 "\r\n"
124 
125 #define EXPIRED_TOKEN_ERROR_STR						\
126 "Invalid or expired session token"				 "\r\n"
127 
ssh_echo(struct tmate_ssh_client * ssh_client,const char * str)128 static void ssh_echo(struct tmate_ssh_client *ssh_client,
129 		     const char *str)
130 {
131 	ssh_channel_write(ssh_client->channel, str, strlen(str));
132 }
133 
134 
135 /*
136  * Note: get_socket_path() replaces '/' and '.' by '_' to
137  * avoid wondering around the file system.
138  */
139 static char valid_digits[] = "abcdefghijklmnopqrstuvwxyz"
140                              "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
141 			     "0123456789-_/";
142 
tmate_validate_session_token(const char * token)143 int tmate_validate_session_token(const char *token)
144 {
145 	int len;
146 	int i;
147 
148 	len = strlen(token);
149 	if (len <= 2)
150 		return -1;
151 
152 	for (i = 0; i < len; i++) {
153 		if (!strchr(valid_digits, token[i]))
154 			return -1;
155 	}
156 
157 	return 0;
158 }
159 
tmate_spawn_pty_client(struct tmate_session * session)160 void tmate_spawn_pty_client(struct tmate_session *session)
161 {
162 	struct tmate_ssh_client *client = &session->ssh_client;
163 	char *argv_rw[] = {(char *)"attach", NULL};
164 	char *argv_ro[] = {(char *)"attach", (char *)"-r", NULL};
165 	char **argv = argv_rw;
166 	int argc = 1;
167 	char *token = client->username;
168 	struct stat fstat;
169 	int slave_pty;
170 	int ret;
171 
172 	if (tmate_validate_session_token(token) < 0) {
173 		ssh_echo(client, BAD_TOKEN_ERROR_STR);
174 		tmate_fatal("Invalid token");
175 	}
176 
177 	set_session_token(session, token);
178 
179 	tmate_info("Spawning pty client ip=%s", client->ip_address);
180 
181 	session->tmux_socket_fd = client_connect(session->ev_base, socket_path, 0);
182 	if (session->tmux_socket_fd < 0) {
183 		if (tmate_has_websocket()) {
184 			/* Turn the response into an exec to show a better error */
185 			client->exec_command = xstrdup("explain-session-not-found");
186 			tmate_spawn_exec(session);
187 			/* No return */
188 		}
189 
190 		random_sleep(); /* for making timing attacks harder */
191 		ssh_echo(client, EXPIRED_TOKEN_ERROR_STR);
192 		tmate_fatal("Expired token");
193 	}
194 
195 	/*
196 	 * If we are connecting through a symlink, it means that we are a
197 	 * readonly client.
198 	 * 1) We mark the client as CLIENT_READONLY on the server
199 	 * 2) We prevent any input (aside from the window size) to go through
200 	 *    to the server.
201 	 */
202 	session->readonly = false;
203 	if (lstat(socket_path, &fstat) < 0)
204 		tmate_fatal("Cannot fstat()");
205 	if (S_ISLNK(fstat.st_mode)) {
206 		session->readonly = true;
207 		argv = argv_ro;
208 		argc = 2;
209 	}
210 
211 	if (openpty(&session->pty, &slave_pty, NULL, NULL, NULL) < 0)
212 		tmate_fatal("Cannot allocate pty");
213 
214 	dup2(slave_pty, STDIN_FILENO);
215 	dup2(slave_pty, STDOUT_FILENO);
216 	dup2(slave_pty, STDERR_FILENO);
217 
218 	setup_ncurse(slave_pty, "screen-256color");
219 
220 	tmate_client_pty_init(session);
221 
222 	/* the unused session->websocket_fd will get closed automatically */
223 	close_fds_except((int[]){STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO,
224 				 session->tmux_socket_fd,
225 				 ssh_get_fd(session->ssh_client.session),
226 				 session->pty, log_file ? fileno(log_file) : -1}, 7);
227 	get_in_jail();
228 	event_reinit(session->ev_base);
229 
230 	ret = client_main(session->ev_base, argc, argv,
231 			  CLIENT_UTF8 | CLIENT_256COLOURS, NULL);
232 	tmate_flush_pty(session);
233 	exit(ret);
234 }
235