directory.c 8.98 KB
Newer Older
1
/* Copyright 2001,2002 Roger Dingledine, Matej Pfajfar. */
2
3
4
5
6
7
8
/* See LICENSE for licensing information */
/* $Id$ */

#include "or.h"

#define MAX_DIR_SIZE 50000 /* XXX, big enough? */

9
static int directory_send_command(connection_t *conn, int command);
Roger Dingledine's avatar
Roger Dingledine committed
10
11
12
static void directory_rebuild(void);
static int directory_handle_command(connection_t *conn);

13
14
15
16
17
18
/********* START VARIABLES **********/

extern or_options_t options; /* command-line and config-file options */

static char the_directory[MAX_DIR_SIZE+1];
static int directorylen=0;
19
static int directory_dirty=1;
20

21
static char getstring[] = "GET / HTTP/1.0\r\n\r\n";
Roger Dingledine's avatar
Roger Dingledine committed
22
static char poststring[] = "POST / HTTP/1.0\r\n\r\n";
23
static char answerstring[] = "HTTP/1.0 200 OK\r\n\r\n";
24
25
26

/********* END VARIABLES ************/

27
void directory_initiate_command(routerinfo_t *router, int command) {
28
29
30
31
32
  connection_t *conn;

  if(!router) /* i guess they didn't have one in mind for me to use */
    return;

33
34
  if(connection_get_by_type(CONN_TYPE_DIR)) { /* there's already a dir conn running */
    log_fn(LOG_DEBUG,"Canceling connect, dir conn already active.");
35
    return;
36
  }
37

38
39
40
41
  if(command == DIR_CONN_STATE_CONNECTING_GET)
    log_fn(LOG_DEBUG,"initiating directory get");
  else
    log_fn(LOG_DEBUG,"initiating directory post");
42

43
44
45
46
47
  conn = connection_new(CONN_TYPE_DIR);
  if(!conn)
    return;

  /* set up conn so it's got all the data we need to remember */
Roger Dingledine's avatar
Roger Dingledine committed
48
49
  conn->addr = router->addr;
  conn->port = router->dir_port;
50
51
52
  conn->address = strdup(router->address);
  conn->receiver_bucket = -1; /* edge connections don't do receiver buckets */
  conn->bandwidth = -1;
Nick Mathewson's avatar
Nick Mathewson committed
53
54
55
  if (router->signing_pkey)
    conn->pkey = crypto_pk_dup_key(router->signing_pkey);
  else {
56
    log_fn(LOG_ERR, "No signing key known for dirserver %s; signature won't be checked", conn->address);
Nick Mathewson's avatar
Nick Mathewson committed
57
58
    conn->pkey = NULL;
  }
59

60
  if(connection_add(conn) < 0) { /* no space, forget it */
61
62
63
64
    connection_free(conn);
    return;
  }

65
66
  switch(connection_connect(conn, router->address, router->addr, router->dir_port)) {
    case -1:
67
      router_forget_router(conn->addr, conn->port); /* XXX don't try him again */
68
69
      connection_free(conn);
      return;
70
71
    case 0:
      connection_set_poll_socket(conn);
72
73
74
      connection_watch_events(conn, POLLIN | POLLOUT | POLLERR);
      /* writable indicates finish, readable indicates broken link,
         error indicates broken link in windowsland. */
75
      conn->state = command;
76
      return;
77
    /* case 1: fall through */
78
79
  }

80
  connection_set_poll_socket(conn);
81
  if(directory_send_command(conn, command) < 0) {
82
83
84
85
86
    connection_remove(conn);
    connection_free(conn);
  }
}

87
88
static int directory_send_command(connection_t *conn, int command) {
  char *s;
89
90
91

  assert(conn && conn->type == CONN_TYPE_DIR);

92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
  switch(command) {
    case DIR_CONN_STATE_CONNECTING_GET:
      if(connection_write_to_buf(getstring, strlen(getstring), conn) < 0) {
        log_fn(LOG_DEBUG,"Couldn't write get to buffer.");
        return -1;
      }
      conn->state = DIR_CONN_STATE_CLIENT_SENDING_GET;
      break;
    case DIR_CONN_STATE_CONNECTING_POST:
      s = router_get_my_descriptor();
      if(!s) {
        log_fn(LOG_DEBUG,"Failed to get my descriptor.");
        return -1;
      }
      if(connection_write_to_buf(poststring, strlen(poststring), conn) < 0 ||
         connection_write_to_buf(s, strlen(s), conn) < 0) {
        log_fn(LOG_DEBUG,"Couldn't write post/descriptor to buffer.");
        return -1;
      }
      conn->state = DIR_CONN_STATE_CLIENT_SENDING_POST;
      break;
113
114
115
116
  }
  return 0;
}

117
118
119
void directory_set_dirty(void) {
  directory_dirty = 1;
}
120

Roger Dingledine's avatar
Roger Dingledine committed
121
static void directory_rebuild(void) {
122
  if(directory_dirty) {
123
124
125
126
127
    if (dump_signed_directory_to_string(the_directory, MAX_DIR_SIZE,
                                        get_signing_privatekey())) {
      log(LOG_ERR, "Error writing directory");
      return;
    }
128
    directorylen = strlen(the_directory);
129
    log(LOG_INFO,"New directory (size %d):\n%s",directorylen,the_directory);
130
131
132
133
    directory_dirty = 0;
  } else {
    log(LOG_INFO,"Directory still clean, reusing.");
  }
134
135
136
137
138
139
140
}

int connection_dir_process_inbuf(connection_t *conn) {

  assert(conn && conn->type == CONN_TYPE_DIR);

  if(conn->inbuf_reached_eof) {
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
    switch(conn->state) {
      case DIR_CONN_STATE_CLIENT_READING_GET:
        /* kill it, but first process the_directory and learn about new routers. */
        switch(connection_fetch_from_buf_http(conn, NULL, 0, the_directory, MAX_DIR_SIZE)) {
          case -1: /* overflow */
            log_fn(LOG_DEBUG,"'get' response too large. Failing.");
            return -1;
          case 0:
            log_fn(LOG_DEBUG,"'get' response not all here, but we're at eof. Closing.");
            return -1;
          /* case 1, fall through */
        }
        directorylen = strlen(the_directory);
        log_fn(LOG_DEBUG,"Received directory (size %d):\n%s", directorylen, the_directory);
        if(directorylen == 0) {
          log_fn(LOG_DEBUG,"Empty directory. Ignoring.");
          return -1;
        }
        if(router_get_dir_from_string(the_directory, conn->pkey) < 0) {
          log_fn(LOG_DEBUG,"...but parsing failed. Ignoring.");
        } else {
          log_fn(LOG_DEBUG,"and got an %s directory; updated routers.", 
              conn->pkey ? "authenticated" : "unauthenticated");
        }
        if(options.OnionRouter) { /* connect to them all */
          router_retry_connections();
        }
        return -1;
      case DIR_CONN_STATE_CLIENT_READING_POST:
        /* XXX make sure there's a 200 OK on the buffer */
        log_fn(LOG_DEBUG,"eof while reading post response. Finished.");
        return -1;
      default:
        log_fn(LOG_DEBUG,"conn reached eof, not reading. Closing.");
        return -1;
Roger Dingledine's avatar
cleanup    
Roger Dingledine committed
176
    }
177
178
  }

179
180
181
182
  if(conn->state == DIR_CONN_STATE_SERVER_COMMAND_WAIT)
    return directory_handle_command(conn);

  /* XXX for READ states, might want to make sure inbuf isn't too big */
183

184
  log_fn(LOG_DEBUG,"Got data, not eof. Leaving on inbuf.");
185
186
187
  return 0;
}

Roger Dingledine's avatar
Roger Dingledine committed
188
static int directory_handle_command(connection_t *conn) {
189
190
  char headers[1024];
  char body[1024];
191
192
193

  assert(conn && conn->type == CONN_TYPE_DIR);

194
195
196
197
198
199
200
201
  switch(connection_fetch_from_buf_http(conn, headers, sizeof(headers), body, sizeof(body))) {
    case -1: /* overflow */
      log_fn(LOG_DEBUG,"input too large. Failing.");
      return -1;
    case 0:
      log_fn(LOG_DEBUG,"command not all here yet.");
      return 0;
    /* case 1, fall through */
202
203
  }

204
205
  log_fn(LOG_DEBUG,"headers '%s', body '%s'.",headers,body);
  if(!strncasecmp(headers,"GET",3)) {
206

207
    directory_rebuild(); /* rebuild it now, iff it's dirty */
208

209
210
211
212
    if(directorylen == 0) {
      log_fn(LOG_DEBUG,"My directory is empty. Closing.");
      return -1;
    }
213

214
215
216
217
218
219
220
221
    log_fn(LOG_DEBUG,"Dumping directory to client."); 
    if((connection_write_to_buf(answerstring, strlen(answerstring), conn) < 0) ||
       (connection_write_to_buf(the_directory, directorylen, conn) < 0)) {
      log_fn(LOG_DEBUG,"Failed to write answerstring+directory to outbuf.");
      return -1;
    }
    conn->state = DIR_CONN_STATE_SERVER_WRITING;
    return 0;
222
223
  }

224
225
226
227
228
229
230
231
  if(!strncasecmp(headers,"POST",4)) {
    log_fn(LOG_DEBUG,"Received POST command, body '%s'", body);
    if(connection_write_to_buf(answerstring, strlen(answerstring), conn) < 0) {
      log_fn(LOG_DEBUG,"Failed to write answerstring to outbuf.");
      return -1;
    }
    conn->state = DIR_CONN_STATE_SERVER_WRITING;
    return 0;
232
233
  }

234
235
  log_fn(LOG_DEBUG,"Got headers with unknown command. Closing.");
  return -1;
236
237
238
239
240
241
242
243
}

int connection_dir_finished_flushing(connection_t *conn) {
  int e, len=sizeof(e);

  assert(conn && conn->type == CONN_TYPE_DIR);

  switch(conn->state) {
244
245
    case DIR_CONN_STATE_CONNECTING_GET:
    case DIR_CONN_STATE_CONNECTING_POST:
246
      if (getsockopt(conn->s, SOL_SOCKET, SO_ERROR, (void*)&e, &len) < 0)  { /* not yet */
247
        if(!ERRNO_CONN_EINPROGRESS(errno)) {
248
          log_fn(LOG_DEBUG,"in-progress connect failed. Removing.");
249
          router_forget_router(conn->addr, conn->port); /* don't try him again */
250
251
252
253
254
255
256
          return -1;
        } else {
          return 0; /* no change, see if next time is better */
        }
      }
      /* the connect has finished. */

257
      log_fn(LOG_DEBUG,"Dir connection to router %s:%u established.",
258
259
          conn->address,conn->port);

260
261
262
263
264
265
266
267
268
      return directory_send_command(conn, conn->state);
    case DIR_CONN_STATE_CLIENT_SENDING_GET:
      log_fn(LOG_DEBUG,"client finished sending 'get' command.");
      conn->state = DIR_CONN_STATE_CLIENT_READING_GET;
      connection_watch_events(conn, POLLIN);
      return 0;
    case DIR_CONN_STATE_CLIENT_SENDING_POST:
      log_fn(LOG_DEBUG,"client finished sending 'post' command.");
      conn->state = DIR_CONN_STATE_CLIENT_READING_POST;
269
270
      connection_watch_events(conn, POLLIN);
      return 0;
271
272
    case DIR_CONN_STATE_SERVER_WRITING:
      log_fn(LOG_DEBUG,"Finished writing server response. Closing.");
273
274
      return -1; /* kill it */
    default:
275
      log_fn(LOG_DEBUG,"BUG: called in unexpected state.");
276
277
278
279
280
281
      return 0;
  }

  return 0;
}

282
283
284
285
286
287
288
/*
  Local Variables:
  mode:c
  indent-tabs-mode:nil
  c-basic-offset:2
  End:
*/