server.c 12.2 KB
Newer Older
Niels Möller's avatar
Niels Möller committed
1
2
/* server.c
 *
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 *
 *
 * $Id$ */

/* lsh, an implementation of the ssh protocol
 *
 * Copyright (C) 1998 Niels Mller
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
Niels Möller's avatar
Niels Möller committed
24
25
26
27
28
 */

#include "server.h"

#include "abstract_io.h"
29
#include "channel.h"
30
#include "connection.h"
Niels Möller's avatar
Niels Möller committed
31
32
#include "debug.h"
#include "format.h"
33
34
35
#include "keyexchange.h"
#include "read_line.h"
#include "read_packet.h"
36
#include "ssh.h"
Niels Möller's avatar
Niels Möller committed
37
#include "unpad.h"
38
#include "version.h"
Niels Möller's avatar
Niels Möller committed
39
40
41
#include "werror.h"
#include "xalloc.h"

42
43
44
45
46
47
48
#include <assert.h>
#include <string.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/socket.h>

49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
struct server_callback
{
  struct fd_callback super;
  struct io_backend *backend;

  struct signer *secret;        /* secret key */
  struct lsh_string *host_key;  /* public key */
  UINT32 block_size;
  char *id_comment;

  struct randomness *random;
  struct make_kexinit *init;
  struct packet_handler *kexinit_handler;
};

Niels Möller's avatar
Niels Möller committed
64
static int server_initiate(struct fd_callback **c,
Niels Möller's avatar
Niels Möller committed
65
66
			   int fd)
{
Niels Möller's avatar
Niels Möller committed
67
68
  struct server_callback *closure = (struct server_callback *) *c;
  
69
70
71
  struct ssh_connection *connection
    = make_ssh_connection(closure->kexinit_handler);

Niels Möller's avatar
Niels Möller committed
72
73
  int res;
  
74
  verbose("server_initiate()\n");
Niels Möller's avatar
Niels Möller committed
75
76
77
78
79
80
81
82

  connection_init_io(connection,
		     io_read_write(closure->backend, fd,
				   make_server_read_line(connection),
				   closure->block_size,
				   make_server_close_handler()),
		     closure->random);

Niels Möller's avatar
Niels Möller committed
83
84
  
  connection->server_version
Niels Möller's avatar
Niels Möller committed
85
    = ssh_format("SSH-%lz-%lz %lz",
Niels Möller's avatar
Niels Möller committed
86
87
88
		 PROTOCOL_VERSION,
		 SOFTWARE_SERVER_VERSION,
		 closure->id_comment);
Niels Möller's avatar
Niels Möller committed
89

Niels Möller's avatar
Niels Möller committed
90
91
  res = A_WRITE(connection->raw,
		 ssh_format("%lS\r\n", connection->server_version));
92
  if (LSH_CLOSEDP(res))
Niels Möller's avatar
Niels Möller committed
93
94
    return res;

95
96
97
  return res | initiate_keyexchange(connection, CONNECTION_SERVER,
				    MAKE_KEXINIT(closure->init),
				    NULL);
Niels Möller's avatar
Niels Möller committed
98
99
100
101
102
103
104
105
}

struct server_line_handler
{
  struct line_handler super;
  struct ssh_connection *connection;
};

Niels Möller's avatar
Niels Möller committed
106
static struct read_handler *do_line(struct line_handler **h,
Niels Möller's avatar
Niels Möller committed
107
108
109
				    UINT32 length,
				    UINT8 *line)
{
Niels Möller's avatar
Niels Möller committed
110
111
  struct server_line_handler *closure = (struct server_line_handler *) *h;
  
Niels Möller's avatar
Niels Möller committed
112
113
  MDEBUG(closure);
  
Niels Möller's avatar
Niels Möller committed
114
115
116
117
118
  if ( (length >= 4) && !memcmp(line, "SSH-", 4))
    {
      /* Parse and remember format string */
      if ((length >= 8) && !memcmp(line + 4, "2.0-", 4))
	{
Niels Möller's avatar
Niels Möller committed
119
120
121
	  struct read_handler *new = make_read_packet
	    (make_packet_unpad
	     (make_packet_debug(&closure->connection->super,
122
				"recieved")),
Niels Möller's avatar
Niels Möller committed
123
	     closure->connection);
Niels Möller's avatar
Niels Möller committed
124
125
	  
	  closure->connection->client_version
Niels Möller's avatar
Niels Möller committed
126
	    = ssh_format("%ls", length, line);
Niels Möller's avatar
Niels Möller committed
127

Niels Möller's avatar
Niels Möller committed
128
129
130
131
132
	  verbose("Client version: ");
	  verbose_safe(closure->connection->client_version->length,
		       closure->connection->client_version->data);
	  verbose("\n");
	  
Niels Möller's avatar
Niels Möller committed
133
	  /* FIXME: Cleanup properly. */
Niels Möller's avatar
Niels Möller committed
134
	  lsh_free(closure);
Niels Möller's avatar
Niels Möller committed
135
136
137
138
139
140
141
142
143

	  return new;
	}
      else
	{
	  werror("Unsupported protocol version: ");
	  werror_safe(length, line);
	  werror("\n");

Niels Möller's avatar
Niels Möller committed
144
145
146
147
	  /* FIXME: Clean up properly */
	  lsh_free(closure);
	  *h = 0;
		  
Niels Möller's avatar
Niels Möller committed
148
149
150
151
152
153
154
155
156
157
158
159
	  return 0;
	}
    }
  else
    {
      /* Display line */
      werror_safe(length, line);

      /* Read next line */
      return 0;
    }
}
Niels Möller's avatar
Niels Möller committed
160

Niels Möller's avatar
Niels Möller committed
161
struct read_handler *make_server_read_line(struct ssh_connection *c)
Niels Möller's avatar
Niels Möller committed
162
{
Niels Möller's avatar
Niels Möller committed
163
164
165
  struct server_line_handler *closure;

  NEW(closure);
Niels Möller's avatar
Niels Möller committed
166
167
  
  closure->super.handler = do_line;
Niels Möller's avatar
Niels Möller committed
168
  closure->connection = c;
Niels Möller's avatar
Niels Möller committed
169
170
171
172
  
  return make_read_line(&closure->super);
}

173
174
175
176
struct fd_callback *
make_server_callback(struct io_backend *b,
		     char *comment,
		     UINT32 block_size,
Niels Möller's avatar
Niels Möller committed
177
178
		     struct randomness *random,
		     struct make_kexinit *init,
179
		     struct packet_handler *kexinit_handler)
Niels Möller's avatar
Niels Möller committed
180
{
Niels Möller's avatar
Niels Möller committed
181
182
183
  struct server_callback *connected;

  NEW(connected);
Niels Möller's avatar
Niels Möller committed
184
185
186
187
188

  connected->super.f = server_initiate;
  connected->backend = b;
  connected->block_size = block_size;
  connected->id_comment = comment;
189

Niels Möller's avatar
Niels Möller committed
190
191
  connected->random = random;  
  connected->init = init;
192
  connected->kexinit_handler = kexinit_handler;
Niels Möller's avatar
Niels Möller committed
193
194
195
196
  
  return &connected->super;
}

197
static int server_die(struct close_callback *closure, int reason)
Niels Möller's avatar
Niels Möller committed
198
{
199
200
201
202
  verbose("Connection died, for reason %d.\n", reason);
  if (reason != CLOSE_EOF)
    werror("Connection died.\n");

Niels Möller's avatar
Niels Möller committed
203
204
205
  return 0;  /* Ignored */
}

206
struct close_callback *make_server_close_handler(void)
Niels Möller's avatar
Niels Möller committed
207
{
Niels Möller's avatar
Niels Möller committed
208
209
210
  struct close_callback *c;

  NEW(c);
Niels Möller's avatar
Niels Möller committed
211
212
213
214
215
216

  c->f = server_die;

  return c;
}

217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
/* Session */
struct server_session
{
  struct ssh_channel super;

  UINT32 max_window;

  /* User information */
  struct unix_user *user;

  /* Non-zero if a shell or command has been started. */
  int running;
};

struct ssh_channel *make_server_session(struct unix_user *user,
					UINT32 max_window,
					struct alist *request_types)
{
  struct server_session *self;

  NEW(self);

  init_channel(&self->super);

  self->super.max_window = max_window;
  self->super.rec_window_size = max_window;

  /* FIXME: Make maximum packet size configurable. */
  self->super.rec_max_packet = SSH_MAX_PACKET;

  self->super.request_types = request_types;
  self->user = user;

  self->running = 0;
  
  return &self->super;
}

struct open_session
{
  struct channel_open super;

  struct unix_user *user;
  struct alist *session_requests;
};

#define WINDOW_SIZE (SSH_MAX_PACKET << 3)

static struct ssh_channel *do_open_session(struct channel_open *c,
					   struct simple_buffer *args,
					   UINT32 *error,
					   char **error_msg,
					   struct lsh_string **data)
{
  struct open_session *closure = (struct open_session *) c;
  
  MDEBUG(closure);

  debug("server.c: do_open_session()\n");
  
  if (!parse_eod(args))
    return 0;
  
  return make_server_session(closure->user, WINDOW_SIZE, closure->session_requests);
}

struct channel_open *make_open_session(struct unix_user *user,
				       struct alist *session_requests)
{
  struct open_session *closure;

  NEW(closure);

  closure->super.handler = do_open_session;
  closure->user = user;
  closure->session_requests = session_requests;
  
  return &closure->super;
}

struct server_connection_service
{
  struct unix_service super;

  struct alist *global_requests;

  /* Requests specific to session channels */
  struct alist *session_requests; 

  /* FIXME: Doesn't support any channel types but "session".
   * This must be fixed to support for "direct-tcpip" channels. */
};

/* Start an authenticated ssh-connection service */
static struct ssh_service *do_login(struct unix_service *c,
				    struct unix_user *user)
{
  struct server_connection_service *closure
    = (struct server_connection_service *) c;

  MDEBUG(closure);

  debug("server.c: do_login()\n");
  
  return
    make_connection_service(closure->global_requests,
			    make_alist(1, ATOM_SESSION,
				       make_open_session(user,
							 closure->session_requests),
				       -1),
			    NULL);
}

struct unix_service *make_server_session_service(struct alist *global_requests,
						 struct alist *session_requests)
{
  struct server_connection_service *closure;

  NEW(closure);

  closure->super.login = do_login;
  closure->global_requests = global_requests;
  closure->session_requests = session_requests;
  
  return &closure->super;
}

struct shell_request
{
  struct channel_request super;

  struct io_backend *backend;
};

/* Creates a one-way socket connection. Returns 1 on successm 0 on
 * failure. fds[0] is for reading, fds[1] for writing (like for the
 * pipe() system call). */
static int make_pipe(int *fds)
{
  /* From the shutdown(2) man page */
#define REC 0
#define SEND 1

  return !socketpair(AF_UNIX, SOCK_STREAM, 0, fds)
    && !shutdown(fds[0], SEND)
    && !shutdown(fds[1], REC);
}

static char *make_env_pair(char *name, struct lsh_string *value)
{
  return ssh_format("%z=%lS\0", name, value)->data;
}

static char *make_env_pair_c(char *name, char *value)
{
  return ssh_format("%z=%z\0", name, value)->data;
}

static int do_spawn_shell(struct channel_request *c,
			  struct ssh_channel *channel,
			  int want_reply,
			  struct simple_buffer *args)
{
  struct shell_request *closure = (struct shell_request *) c;
  struct server_session *session = (struct server_session *) channel;

  int in_fds[2];
  int out_fds[2];
  int err_fds[2];

  MDEBUG(closure);
  MDEBUG(channel);

  if (!parse_eod(args))
    return LSH_FAIL | LSH_DIE;

  if (session->running)
    /* Already spawned a shell or command */
    goto fail;
  
  /* {in_fds|out_fds|err_fds}[0] is for reading,
   * {in_fds|out_fds|err_fds}[1] for writing. */

  if (make_pipe(in_fds))
    {
      if (make_pipe(out_fds))
	{
	  if (make_pipe(err_fds))
	    {
	      pid_t child;
	      
	      switch(child = fork())
		{
		case -1:
		  werror("fork() failed: %s\n", strerror(errno));
		  /* Close and return channel_failure */
		  break; 
		case 0:
		  /* Child */
		  if (!session->user->shell)
		    {
		      werror("No login shell!\n");
		      exit(EXIT_FAILURE);
		    }
		      
		  if (getuid() != session->user->uid)
		    if (!change_uid(session->user))
		      {
			werror("Changing uid failed!\n");
			exit(EXIT_FAILURE);
		      }
		  
		  assert(getuid() == session->user->uid);

		  if (!change_dir(session->user))
		    {
		      werror("Could not change to home (or root) directory!\n");
		      exit(EXIT_FAILURE);
		    }

		  /* Close all descriptors but those used for
		   * communicationg with parent. We rely on the
		   * close-on-exec flag for all fd:s handled by the
		   * backend. */

		  close(STDIN_FILENO);
		  if (dup2(in_fds[0], STDIN_FILENO) < 0)
		    {
		      werror("Can't dup stdin!\n");
		      exit(EXIT_FAILURE);
		    }
		  close(in_fds[0]);
		  close(in_fds[1]);

		  close(STDOUT_FILENO);
		  if (dup2(out_fds[1], STDOUT_FILENO) < 0)
		    {
		      werror("Can't dup stdout!\n");
		      exit(EXIT_FAILURE);
		    }
		  close(out_fds[0]);
		  close(out_fds[1]);

		  close(STDERR_FILENO);
		  if (dup2(err_fds[1], STDERR_FILENO) < 0)
		    {
		      /* Can't write any message to stderr. */ 
		      exit(EXIT_FAILURE);
		    }
		  close(err_fds[0]);
		  close(err_fds[1]);

		  {
		    char *shell = session->user->shell->data;
#define MAX_ENV 7
		    char *env[MAX_ENV];
		    char *tz = getenv("TZ");
		    int i = 0;

		    env[i++] = make_env_pair("LOGNAME", session->user->name);
		    env[i++] = make_env_pair("USER", session->user->name);
		    env[i++] = make_env_pair("SHELL", session->user->shell);
		    if (session->user->home)
		      env[i++] = make_env_pair("HOME", session->user->home);
		    if (tz)
		      env[i++] = make_env_pair_c("TZ", tz);

		    /* FIXME: The value of $PATH should not be hard-coded */
		    env[i++] = "PATH=/bin:/usr/bin";
		    env[i++] = NULL;
		    
		    assert(i <= MAX_ENV);

		    if (execle(shell, shell, NULL, env) < 0)
		      exit(EXIT_FAILURE);
#undef MAX_ENV
		  }
		default:
		  /* Parent */
		  /* FIXME: Install a calback to catch dying children */
		    
		  /* Close the child's fd:s */
		  close(in_fds[0]);
		  close(out_fds[1]);
		  close(err_fds[2]);

		  io_write(closure->backend, in_fds[1],
			   SSH_MAX_PACKET,
			   /* FIXME: Use a proper close callback */
			   NULL);
		  io_read(closure->backend, out_fds[0],
			  make_channel_read_data(channel),
			  NULL);
		  io_read(closure->backend, err_fds[0],
			  make_channel_read_stderr(channel),
			  NULL);

		  session->running = 1;
		  return want_reply
		    ? A_WRITE(channel->write,
			      format_channel_success(channel->channel_number))
		    : LSH_OK | LSH_GOON;
		  
		}
	      close(err_fds[0]);
	      close(err_fds[1]);
	    }
	  close(out_fds[0]);
	  close(out_fds[1]);
	}
      close(in_fds[0]);
      close(in_fds[1]);
    }
 fail:
  return want_reply
    ? A_WRITE(channel->write, format_channel_failure(channel->channel_number))
    : LSH_OK | LSH_GOON;
}

struct channel_request *make_shell_handler(struct io_backend *backend)
{
  struct shell_request *closure;

  NEW(closure);
  closure->super.handler = do_spawn_shell;

  return &closure->super;
}