directory.c 9.08 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
    switch(conn->state) {
      case DIR_CONN_STATE_CLIENT_READING_GET:
        /* kill it, but first process the_directory and learn about new routers. */
144
145
        switch(fetch_from_buf_http(conn->inbuf,&conn->inbuf_datalen,
                                   NULL, 0, the_directory, MAX_DIR_SIZE)) {
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
          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
177
    }
178
179
  }

180
181
182
183
  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 */
184

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

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

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

195
196
  switch(fetch_from_buf_http(conn->inbuf,&conn->inbuf_datalen,
                             headers, sizeof(headers), body, sizeof(body))) {
197
198
199
200
201
202
203
    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 */
204
205
  }

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

209
    directory_rebuild(); /* rebuild it now, iff it's dirty */
210

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

216
217
218
219
220
221
222
223
    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;
224
225
  }

226
227
228
229
230
231
232
233
  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;
234
235
  }

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

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

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

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

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

262
263
264
265
266
267
268
269
270
      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;
271
272
      connection_watch_events(conn, POLLIN);
      return 0;
273
274
    case DIR_CONN_STATE_SERVER_WRITING:
      log_fn(LOG_DEBUG,"Finished writing server response. Closing.");
275
276
      return -1; /* kill it */
    default:
277
      log_fn(LOG_DEBUG,"BUG: called in unexpected state.");
278
279
280
281
282
283
      return 0;
  }

  return 0;
}

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