summaryrefslogtreecommitdiffstats
path: root/src/packet1.c
blob: 7ac8318d4c25c9e76faebf3c2db6dea246d8c0b3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
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
/*
 * This file is part of the SSH Library
 *
 * Copyright (c) 2010 by Aris Adamantiadis
 *
 * The SSH Library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version.
 *
 * The SSH Library 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 Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the SSH Library; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA.
 */

#include "config.h"

#include <stdlib.h>

#ifndef _WIN32
#include <netinet/in.h>
#endif /* _WIN32 */

#include "libssh/priv.h"
#include "libssh/ssh1.h"
#include "libssh/crc32.h"
#include "libssh/packet.h"
#include "libssh/session.h"
#include "libssh/buffer.h"
#include "libssh/socket.h"
#include "libssh/kex.h"
#include "libssh/crypto.h"

#ifdef WITH_SSH1

static ssh_packet_callback default_packet_handlers1[]= {
  NULL,                           //SSH_MSG_NONE                        0
  ssh_packet_disconnect1,         //SSH_MSG_DISCONNECT                  1
  ssh_packet_publickey1,          //SSH_SMSG_PUBLIC_KEY                 2
  NULL,                           //SSH_CMSG_SESSION_KEY                3
  NULL,                           //SSH_CMSG_USER                       4
  NULL,                           //SSH_CMSG_AUTH_RHOSTS                5
  NULL,                           //SSH_CMSG_AUTH_RSA                   6
  NULL,                           //SSH_SMSG_AUTH_RSA_CHALLENGE         7
  NULL,                           //SSH_CMSG_AUTH_RSA_RESPONSE          8
  NULL,                           //SSH_CMSG_AUTH_PASSWORD              9
  NULL,                           //SSH_CMSG_REQUEST_PTY                10
  NULL,                           //SSH_CMSG_WINDOW_SIZE                11
  NULL,                           //SSH_CMSG_EXEC_SHELL                 12
  NULL,                           //SSH_CMSG_EXEC_CMD                   13
  ssh_packet_smsg_success1,       //SSH_SMSG_SUCCESS                    14
  ssh_packet_smsg_failure1,       //SSH_SMSG_FAILURE                    15
  NULL,                           //SSH_CMSG_STDIN_DATA                 16
  ssh_packet_data1,               //SSH_SMSG_STDOUT_DATA                17
  ssh_packet_data1,               //SSH_SMSG_STDERR_DATA                18
  NULL,                           //SSH_CMSG_EOF                        19
  ssh_packet_exist_status1,       //SSH_SMSG_EXITSTATUS                 20
  NULL,                           //SSH_MSG_CHANNEL_OPEN_CONFIRMATION   21
  NULL,                           //SSH_MSG_CHANNEL_OPEN_FAILURE        22
  NULL,                           //SSH_MSG_CHANNEL_DATA                23
  ssh_packet_close1,              //SSH_MSG_CHANNEL_CLOSE               24
  NULL,                           //SSH_MSG_CHANNEL_CLOSE_CONFIRMATION  25
  NULL,                           //SSH_CMSG_X11_REQUEST_FORWARDING     26
  NULL,                           //SSH_SMSG_X11_OPEN                   27
  NULL,                           //SSH_CMSG_PORT_FORWARD_REQUEST       28
  NULL,                           //SSH_MSG_PORT_OPEN                   29
  NULL,                           //SSH_CMSG_AGENT_REQUEST_FORWARDING   30
  NULL,                           //SSH_SMSG_AGENT_OPEN                 31
  ssh_packet_ignore_callback,     //SSH_MSG_IGNORE                      32
  NULL,                           //SSH_CMSG_EXIT_CONFIRMATION          33
  NULL,                           //SSH_CMSG_X11_REQUEST_FORWARDING     34
  NULL,                           //SSH_CMSG_AUTH_RHOSTS_RSA            35
  ssh_packet_ignore_callback,     //SSH_MSG_DEBUG                       36
};

/** @internal
 * @brief sets the default packet handlers
 */
void ssh_packet_set_default_callbacks1(ssh_session session){
  session->default_packet_callbacks.start=0;
  session->default_packet_callbacks.n_callbacks=sizeof(default_packet_handlers1)/sizeof(ssh_packet_callback);
  session->default_packet_callbacks.user=session;
  session->default_packet_callbacks.callbacks=default_packet_handlers1;
  ssh_packet_set_callbacks(session, &session->default_packet_callbacks);
}


/** @internal
 * @handles a data received event. It then calls the handlers for the different packet types
 * or and exception handler callback. Adapted for SSH-1 packets.
 * @param user pointer to current ssh_session
 * @param data pointer to the data received
 * @len length of data received. It might not be enough for a complete packet
 * @returns number of bytes read and processed.
 */

int ssh_packet_socket_callback1(const void *data, size_t receivedlen, void *user) {
  void *packet = NULL;
  int to_be_read;
  size_t processed=0;
  uint32_t padding;
  uint32_t crc;
  uint32_t len;
  ssh_session session=(ssh_session)user;

  switch (session->packet_state){
    case PACKET_STATE_INIT:
      memset(&session->in_packet, 0, sizeof(PACKET));

      if (session->in_buffer) {
        if (buffer_reinit(session->in_buffer) < 0) {
          goto error;
        }
      } else {
        session->in_buffer = ssh_buffer_new();
        if (session->in_buffer == NULL) {
          goto error;
        }
      }
      /* must have at least enough bytes for size */
      if(receivedlen < sizeof(uint32_t)){
        return 0;
      }
      memcpy(&len,data,sizeof(uint32_t));
      processed += sizeof(uint32_t);

      /* len is not encrypted */
      len = ntohl(len);
      if (len > MAX_PACKET_LEN) {
        ssh_set_error(session, SSH_FATAL,
            "read_packet(): Packet len too high (%u %.8x)", len, len);
        goto error;
      }

      SSH_LOG(SSH_LOG_PACKET, "Reading a %d bytes packet", len);

      session->in_packet.len = len;
      session->packet_state = PACKET_STATE_SIZEREAD;
      /* FALL THROUGH */
    case PACKET_STATE_SIZEREAD:
      len = session->in_packet.len;
      /* SSH-1 has a fixed padding lenght */
      padding = 8 - (len % 8);
      to_be_read = len + padding;
      if(to_be_read + processed > receivedlen){
        /* wait for rest of packet */
        return processed;
      }
      /* it is _not_ possible that to_be_read be < 8. */
      packet = (char *)data + processed;

      if (buffer_add_data(session->in_buffer,packet,to_be_read) < 0) {
        goto error;
      }
      processed += to_be_read;
#ifdef DEBUG_CRYPTO
      ssh_print_hexa("read packet:", ssh_buffer_get_begin(session->in_buffer),
          ssh_buffer_get_len(session->in_buffer));
#endif
      if (session->current_crypto) {
        /*
         * We decrypt everything, missing the lenght part (which was
         * previously read, unencrypted, and is not part of the buffer
         */
        if (packet_decrypt(session,
              ssh_buffer_get_begin(session->in_buffer),
              ssh_buffer_get_len(session->in_buffer)) < 0) {
          ssh_set_error(session, SSH_FATAL, "Packet decrypt error");
          goto error;
        }
      }
#ifdef DEBUG_CRYPTO
      ssh_print_hexa("read packet decrypted:", ssh_buffer_get_begin(session->in_buffer),
          ssh_buffer_get_len(session->in_buffer));
#endif
      SSH_LOG(SSH_LOG_PACKET, "%d bytes padding", padding);
      if(((len + padding) != buffer_get_rest_len(session->in_buffer)) ||
          ((len + padding) < sizeof(uint32_t))) {
        SSH_LOG(SSH_LOG_RARE, "no crc32 in packet");
        ssh_set_error(session, SSH_FATAL, "no crc32 in packet");
        goto error;
      }

      memcpy(&crc,
          (unsigned char *)buffer_get_rest(session->in_buffer) + (len+padding) - sizeof(uint32_t),
          sizeof(uint32_t));
      buffer_pass_bytes_end(session->in_buffer, sizeof(uint32_t));
      crc = ntohl(crc);
      if (ssh_crc32(buffer_get_rest(session->in_buffer),
            (len + padding) - sizeof(uint32_t)) != crc) {
#ifdef DEBUG_CRYPTO
        ssh_print_hexa("crc32 on",buffer_get_rest(session->in_buffer),
            len + padding - sizeof(uint32_t));
#endif
        SSH_LOG(SSH_LOG_RARE, "Invalid crc32");
        ssh_set_error(session, SSH_FATAL,
            "Invalid crc32: expected %.8x, got %.8x",
            crc,
            ssh_crc32(buffer_get_rest(session->in_buffer),
              len + padding - sizeof(uint32_t)));
        goto error;
      }
      /* pass the padding */
      buffer_pass_bytes(session->in_buffer, padding);
      SSH_LOG(SSH_LOG_PACKET, "The packet is valid");

/* TODO FIXME
#ifdef WITH_ZLIB
    if(session->current_crypto && session->current_crypto->do_compress_in){
        decompress_buffer(session,session->in_buffer);
    }
#endif
*/
      session->recv_seq++;
      /* We don't want to rewrite a new packet while still executing the packet callbacks */
      session->packet_state = PACKET_STATE_PROCESSING;
      ssh_packet_parse_type(session);
      /* execute callbacks */
      ssh_packet_process(session, session->in_packet.type);
      session->packet_state = PACKET_STATE_INIT;
      if(processed < receivedlen){
        int rc;
        /* Handle a potential packet left in socket buffer */
        SSH_LOG(SSH_LOG_PACKET,"Processing %" PRIdS " bytes left in socket buffer",
            receivedlen-processed);
        rc = ssh_packet_socket_callback1((char *)data + processed,
            receivedlen - processed,user);
        processed += rc;
      }

      return processed;
    case PACKET_STATE_PROCESSING:
      SSH_LOG(SSH_LOG_RARE, "Nested packet processing. Delaying.");
      return 0;
  }

error:
  session->session_state=SSH_SESSION_STATE_ERROR;

  return processed;
}


int packet_send1(ssh_session session) {
  unsigned int blocksize = (session->current_crypto ?
      session->current_crypto->out_cipher->blocksize : 8);
  uint32_t currentlen = ssh_buffer_get_len(session->out_buffer) + sizeof(uint32_t);
  char padstring[32] = {0};
  int rc = SSH_ERROR;
  uint32_t finallen;
  uint32_t crc;
  uint8_t padding;

  SSH_LOG(SSH_LOG_PACKET,"Sending a %d bytes long packet",currentlen);

/* TODO FIXME
#ifdef WITH_ZLIB
  if (session->current_crypto && session->current_crypto->do_compress_out) {
    if (compress_buffer(session, session->out_buffer) < 0) {
      goto error;
    }
    currentlen = buffer_get_len(session->out_buffer);
  }
#endif
*/
  padding = blocksize - (currentlen % blocksize);
  if (session->current_crypto) {
    ssh_get_random(padstring, padding, 0);
  } else {
    memset(padstring, 0, padding);
  }

  finallen = htonl(currentlen);
  SSH_LOG(SSH_LOG_PACKET,
      "%d bytes after comp + %d padding bytes = %d bytes packet",
      currentlen, padding, ntohl(finallen));

  if (buffer_prepend_data(session->out_buffer, &padstring, padding) < 0) {
    goto error;
  }
  if (buffer_prepend_data(session->out_buffer, &finallen, sizeof(uint32_t)) < 0) {
    goto error;
  }

  crc = ssh_crc32((char *)ssh_buffer_get_begin(session->out_buffer) + sizeof(uint32_t),
      ssh_buffer_get_len(session->out_buffer) - sizeof(uint32_t));

  if (buffer_add_u32(session->out_buffer, ntohl(crc)) < 0) {
    goto error;
  }

#ifdef DEBUG_CRYPTO
  ssh_print_hexa("Clear packet", ssh_buffer_get_begin(session->out_buffer),
      ssh_buffer_get_len(session->out_buffer));
#endif

  packet_encrypt(session, (unsigned char *)ssh_buffer_get_begin(session->out_buffer) + sizeof(uint32_t),
      ssh_buffer_get_len(session->out_buffer) - sizeof(uint32_t));

#ifdef DEBUG_CRYPTO
  ssh_print_hexa("encrypted packet",ssh_buffer_get_begin(session->out_buffer),
      ssh_buffer_get_len(session->out_buffer));
#endif
  rc=ssh_socket_write(session->socket, ssh_buffer_get_begin(session->out_buffer),
      ssh_buffer_get_len(session->out_buffer));
  if(rc== SSH_ERROR) {
    goto error;
  }

  session->send_seq++;

  if (buffer_reinit(session->out_buffer) < 0) {
    rc = SSH_ERROR;
  }
error:

  return rc;     /* SSH_OK, AGAIN or ERROR */
}

SSH_PACKET_CALLBACK(ssh_packet_disconnect1){
  (void)packet;
  (void)user;
  (void)type;
  SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_DISCONNECT");
  ssh_set_error(session, SSH_FATAL, "Received SSH_MSG_DISCONNECT");
  ssh_socket_close(session->socket);
  session->alive = 0;
  session->session_state=SSH_SESSION_STATE_DISCONNECTED;
  return SSH_PACKET_USED;
}

SSH_PACKET_CALLBACK(ssh_packet_smsg_success1){
  if(session->session_state==SSH_SESSION_STATE_KEXINIT_RECEIVED){
    session->session_state=SSH_SESSION_STATE_AUTHENTICATING;
    return SSH_PACKET_USED;
  } else if(session->session_state==SSH_SESSION_STATE_AUTHENTICATING){
    ssh_auth1_handler(session,type);
    return SSH_PACKET_USED;
  } else {
    return ssh_packet_channel_success(session,type,packet,user);
  }
}

SSH_PACKET_CALLBACK(ssh_packet_smsg_failure1){
  if(session->session_state==SSH_SESSION_STATE_KEXINIT_RECEIVED){
    session->session_state=SSH_SESSION_STATE_ERROR;
    ssh_set_error(session,SSH_FATAL,"Key exchange failed: received SSH_SMSG_FAILURE");
    return SSH_PACKET_USED;
  } else if(session->session_state==SSH_SESSION_STATE_AUTHENTICATING){
    ssh_auth1_handler(session,type);
    return SSH_PACKET_USED;
  } else {
    return ssh_packet_channel_failure(session,type,packet,user);
  }
}


#endif /* WITH_SSH1 */

/* vim: set ts=2 sw=2 et cindent: */