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