void client_read(struct bufferevent *bufev, void *arg) { struct session *s = arg; size_t buf_avail, read; int n; do { buf_avail = sizeof s->cbuf - s->cbuf_valid; read = bufferevent_read(bufev, s->cbuf + s->cbuf_valid, buf_avail); s->cbuf_valid += read; while ((n = get_line(s->cbuf, &s->cbuf_valid)) > 0) { logmsg(LOG_DEBUG, "#%d client: %s", s->id, linebuf); if (!client_parse(s)) { end_session(s); return; } bufferevent_write(s->server_bufev, linebuf, linelen); } if (n == -1) { logmsg(LOG_ERR, "#%d client command too long or not" " clean", s->id); end_session(s); return; } } while (read == buf_avail); }
/* * Read a client definition from the given filename. */ RADCLIENT *client_read(char const *filename, int in_server, int flag) { char const *p; RADCLIENT *c; CONF_SECTION *cs; char buffer[256]; if (!filename) return NULL; cs = cf_file_read(filename); if (!cs) return NULL; cs = cf_section_sub_find(cs, "client"); if (!cs) { ERROR("No \"client\" section found in client file"); return NULL; } c = client_parse(cs, in_server); if (!c) return NULL; p = strrchr(filename, FR_DIR_SEP); if (p) { p++; } else { p = filename; } if (!flag) return c; /* * Additional validations */ ip_ntoh(&c->ipaddr, buffer, sizeof(buffer)); if (strcmp(p, buffer) != 0) { DEBUG("Invalid client definition in %s: IP address %s does not match name %s", filename, buffer, p); client_free(c); return NULL; } return c; }
static uint8_t client_parse(Client *client, int size) { char *end, *str; uint16_t status_code; switch (client->parser_state) { case PARSER_START: //printf("parse (START):\n%s\n", &client->buffer[client->parser_offset]); /* look for HTTP/1.1 200 OK */ if (client->buffer_offset < sizeof("HTTP/1.1 200\r\n")) return 1; if (strncmp(client->buffer, "HTTP/1.1 ", sizeof("HTTP/1.1 ")-1) != 0) return 0; // now the status code status_code = 0; str = client->buffer + sizeof("HTTP/1.1 ")-1; for (end = str + 3; str != end; str++) { if (*str < '0' || *str > '9') return 0; status_code *= 10; status_code += *str - '0'; } // look for next \r\n end = memchr(end, '\r', client->buffer_offset); if (!end || *(end+1) != '\n') return (!end || *(end+1) == '\0') && client->buffer_offset < 1024 ? 1 : 0; if (status_code >= 200 && status_code < 300) { client->worker->stats.req_2xx++; client->status_success = 1; } else if (status_code < 400) { client->worker->stats.req_3xx++; client->status_success = 1; } else if (status_code < 500) { client->worker->stats.req_4xx++; } else if (status_code < 600) { client->worker->stats.req_5xx++; } else { // invalid status code return 0; } client->parser_offset = end + 2 - client->buffer; client->parser_state = PARSER_HEADER; case PARSER_HEADER: //printf("parse (HEADER)\n"); /* look for Content-Length and Connection header */ while (NULL != (end = memchr(&client->buffer[client->parser_offset], '\r', client->buffer_offset - client->parser_offset))) { if (*(end+1) != '\n') return *(end+1) == '\0' && client->buffer_offset - client->parser_offset < 1024 ? 1 : 0; if (end == &client->buffer[client->parser_offset]) { /* body reached */ client->parser_state = PARSER_BODY; client->header_size = end + 2 - client->buffer; //printf("body reached\n"); return client_parse(client, size - client->header_size); } *end = '\0'; str = &client->buffer[client->parser_offset]; //printf("checking header: '%s'\n", str); if (strncasecmp(str, "Content-Length: ", sizeof("Content-Length: ")-1) == 0) { /* content length header */ client->content_length = str_to_uint64(str + sizeof("Content-Length: ") - 1); } else if (strncasecmp(str, "Connection: ", sizeof("Connection: ")-1) == 0) { /* connection header */ str += sizeof("Connection: ") - 1; if (strncasecmp(str, "close", sizeof("close")-1) == 0) client->keepalive = 0; else if (strncasecmp(str, "keep-alive", sizeof("keep-alive")-1) == 0) client->keepalive = client->worker->config->keep_alive; else return 0; } else if (strncasecmp(str, "Transfer-Encoding: ", sizeof("Transfer-Encoding: ")-1) == 0) { /* transfer encoding header */ str += sizeof("Transfer-Encoding: ") - 1; if (strncasecmp(str, "chunked", sizeof("chunked")-1) == 0) client->chunked = 1; else return 0; } if (*(end+2) == '\r' && *(end+3) == '\n') { /* body reached */ client->parser_state = PARSER_BODY; client->header_size = end + 4 - client->buffer; client->parser_offset = client->header_size; //printf("body reached\n"); return client_parse(client, size - client->header_size); } client->parser_offset = end - client->buffer + 2; } return 1; case PARSER_BODY: //printf("parse (BODY)\n"); /* do nothing, just consume the data */ /*printf("content-l: %"PRIu64", header: %d, recevied: %"PRIu64"\n", client->content_length, client->header_size, client->bytes_received);*/ if (client->chunked) { int consume_max; str = &client->buffer[client->parser_offset]; /*printf("parsing chunk: '%s'\n(%"PRIi64" received, %"PRIi64" size, %d parser offset)\n", str, client->chunk_received, client->chunk_size, client->parser_offset );*/ if (client->chunk_size == -1) { /* read chunk size */ client->chunk_size = 0; client->chunk_received = 0; end = str + size; for (; str < end; str++) { if (*str == ';' || *str == '\r') break; client->chunk_size *= 16; if (*str >= '0' && *str <= '9') client->chunk_size += *str - '0'; else if (*str >= 'A' && *str <= 'Z') client->chunk_size += 10 + *str - 'A'; else if (*str >= 'a' && *str <= 'z') client->chunk_size += 10 + *str - 'a'; else return 0; /*(src < end checked above)*/ } if (str[0] != '\r') { str = memchr(str, '\r', end-str); if (!str) { client->chunk_size = -1; return size < 1024 ? 1 : 0; } } if (str[1] != '\n') { client->chunk_size = -1; return str+1 == end ? 1 : 0; } str += 2; //printf("---------- chunk size: %"PRIi64", %d read, %d offset, data: '%s'\n", client->chunk_size, size, client->parser_offset, str); size -= str - &client->buffer[client->parser_offset]; client->parser_offset = str - client->buffer; if (client->chunk_size == 0) { /* chunk of size 0 marks end of content body */ client->state = CLIENT_END; client->success = client->status_success ? 1 : 0; return 1; } } /* consume chunk till chunk_size is reached */ consume_max = client->chunk_size - client->chunk_received; if (size < consume_max) consume_max = size; client->chunk_received += consume_max; client->parser_offset += consume_max; //printf("---------- chunk consuming: %d, received: %"PRIi64" of %"PRIi64", offset: %d\n", consume_max, client->chunk_received, client->chunk_size, client->parser_offset); if (client->chunk_received == client->chunk_size) { if (size - consume_max < 2) return 1; if (client->buffer[client->parser_offset] != '\r' || client->buffer[client->parser_offset+1] != '\n') return 0; /* got whole chunk, next! */ //printf("---------- got whole chunk!!\n"); client->chunk_size = -1; client->chunk_received = 0; client->parser_offset += 2; consume_max += 2; /* there is stuff left to parse */ if (size - consume_max > 0) return client_parse(client, size - consume_max); } client->parser_offset = 0; client->buffer_offset = 0; return 1; } else { /* not chunked, just consume all data till content-length is reached */ client->buffer_offset = 0; if (client->content_length == -1) return 0; if (client->bytes_received == (uint64_t) (client->header_size + client->content_length)) { /* full response received */ client->state = CLIENT_END; client->success = client->status_success ? 1 : 0; } } return 1; } return 1; }
void client_state_machine(Client *client) { int r; Config *config = client->worker->config; start: //printf("state: %d\n", client->state); switch (client->state) { case CLIENT_START: client->worker->stats.req_started++; do { r = socket(config->saddr->ai_family, config->saddr->ai_socktype, config->saddr->ai_protocol); } while (-1 == r && errno == EINTR); if (-1 == r) { client->state = CLIENT_ERROR; strerror_r(errno, client->buffer, sizeof(client->buffer)); W_ERROR("socket() failed: %s (%d)", client->buffer, errno); goto start; } /* set non-blocking */ fcntl(r, F_SETFL, O_NONBLOCK | O_RDWR); ev_init(&client->sock_watcher, client_io_cb); ev_io_set(&client->sock_watcher, r, EV_WRITE); ev_io_start(client->worker->loop, &client->sock_watcher); if (!client_connect(client)) { client->state = CLIENT_ERROR; goto start; } else { client_set_events(client, EV_WRITE); return; } case CLIENT_CONNECTING: if (!client_connect(client)) { client->state = CLIENT_ERROR; goto start; } case CLIENT_WRITING: while (1) { r = write(client->sock_watcher.fd, &config->request[client->request_offset], config->request_size - client->request_offset); //printf("write(%d - %d = %d): %d\n", config->request_size, client->request_offset, config->request_size - client->request_offset, r); if (r == -1) { /* error */ if (errno == EINTR) continue; strerror_r(errno, client->buffer, sizeof(client->buffer)); W_ERROR("write() failed: %s (%d)", client->buffer, errno); client->state = CLIENT_ERROR; goto start; } else if (r != 0) { /* success */ client->request_offset += r; if (client->request_offset == config->request_size) { /* whole request was sent, start reading */ client->state = CLIENT_READING; client_set_events(client, EV_READ); } return; } else { /* disconnect */ client->state = CLIENT_END; goto start; } } case CLIENT_READING: while (1) { r = read(client->sock_watcher.fd, &client->buffer[client->buffer_offset], sizeof(client->buffer) - client->buffer_offset - 1); //printf("read(): %d, offset was: %d\n", r, client->buffer_offset); if (r == -1) { /* error */ if (errno == EINTR) continue; strerror_r(errno, client->buffer, sizeof(client->buffer)); W_ERROR("read() failed: %s (%d)", client->buffer, errno); client->state = CLIENT_ERROR; } else if (r != 0) { /* success */ client->bytes_received += r; client->buffer_offset += r; client->worker->stats.bytes_total += r; if (client->buffer_offset >= sizeof(client->buffer)) { /* too big response header */ client->state = CLIENT_ERROR; break; } client->buffer[client->buffer_offset] = '\0'; //printf("buffer:\n==========\n%s\n==========\n", client->buffer); if (!client_parse(client, r)) { client->state = CLIENT_ERROR; //printf("parser failed\n"); break; } else { if (client->state == CLIENT_END) goto start; else return; } } else { /* disconnect */ if (client->parser_state == PARSER_BODY && !client->keepalive && client->status_success && !client->chunked && client->content_length == -1) { client->success = 1; client->state = CLIENT_END; } else { client->state = CLIENT_ERROR; } goto start; } } case CLIENT_ERROR: //printf("client error\n"); client->worker->stats.req_error++; client->keepalive = 0; client->success = 0; client->state = CLIENT_END; case CLIENT_END: /* update worker stats */ client->worker->stats.req_done++; if (client->success) { client->worker->stats.req_success++; client->worker->stats.bytes_body += client->bytes_received - client->header_size; } else { client->worker->stats.req_failed++; } /* print progress every 10% done */ if (client->worker->id == 1 && client->worker->stats.req_done % client->worker->progress_interval == 0) { printf("progress: %3d%% done\n", (int) (client->worker->stats.req_done * 100 / client->worker->stats.req_todo) ); } if (client->worker->stats.req_started == client->worker->stats.req_todo) { /* this worker has started all requests */ client->keepalive = 0; client_reset(client); if (client->worker->stats.req_done == client->worker->stats.req_todo) { /* this worker has finished all requests */ ev_unref(client->worker->loop); } } else { client_reset(client); goto start; } } }
RADCLIENT_LIST *clients_parse_section(CONF_SECTION *section, UNUSED bool tls_required) #endif { bool global = false, in_server = false; CONF_SECTION *cs; RADCLIENT *c; RADCLIENT_LIST *clients; /* * Be forgiving. If there's already a clients, return * it. Otherwise create a new one. */ clients = cf_data_find(section, "clients"); if (clients) return clients; clients = clients_init(section); if (!clients) return NULL; if (cf_top_section(section) == section) global = true; if (strcmp("server", cf_section_name1(section)) == 0) in_server = true; /* * Associate the clients structure with the section. */ if (cf_data_add(section, "clients", clients, NULL) < 0) { cf_log_err_cs(section, "Failed to associate clients with section %s", cf_section_name1(section)); clients_free(clients); return NULL; } for (cs = cf_subsection_find_next(section, NULL, "client"); cs != NULL; cs = cf_subsection_find_next(section, cs, "client")) { c = client_parse(cs, in_server); if (!c) { return NULL; } #ifdef WITH_TLS /* * TLS clients CANNOT use non-TLS listeners. * non-TLS clients CANNOT use TLS listeners. */ if (tls_required != c->tls_required) { cf_log_err_cs(cs, "Client does not have the same TLS configuration as the listener"); client_free(c); clients_free(clients); return NULL; } #endif /* * FIXME: Add the client as data via cf_data_add, * for migration issues. */ #ifdef WITH_DYNAMIC_CLIENTS #ifdef HAVE_DIRENT_H if (c->client_server) { char const *value; CONF_PAIR *cp; DIR *dir; struct dirent *dp; struct stat stat_buf; char buf2[2048]; /* * Find the directory where individual * client definitions are stored. */ cp = cf_pair_find(cs, "directory"); if (!cp) goto add_client; value = cf_pair_value(cp); if (!value) { cf_log_err_cs(cs, "The \"directory\" entry must not be empty"); client_free(c); return NULL; } DEBUG("including dynamic clients in %s", value); dir = opendir(value); if (!dir) { cf_log_err_cs(cs, "Error reading directory %s: %s", value, fr_syserror(errno)); client_free(c); return NULL; } /* * Read the directory, ignoring "." files. */ while ((dp = readdir(dir)) != NULL) { char const *p; RADCLIENT *dc; if (dp->d_name[0] == '.') continue; /* * Check for valid characters */ for (p = dp->d_name; *p != '\0'; p++) { if (isalpha((int)*p) || isdigit((int)*p) || (*p == ':') || (*p == '.')) continue; break; } if (*p != '\0') continue; snprintf(buf2, sizeof(buf2), "%s/%s", value, dp->d_name); if ((stat(buf2, &stat_buf) != 0) || S_ISDIR(stat_buf.st_mode)) continue; dc = client_read(buf2, in_server, true); if (!dc) { cf_log_err_cs(cs, "Failed reading client file \"%s\"", buf2); client_free(c); closedir(dir); return NULL; } /* * Validate, and add to the list. */ if (!client_validate(clients, c, dc)) { client_free(c); closedir(dir); return NULL; } } /* loop over the directory */ closedir(dir); } #endif /* HAVE_DIRENT_H */ add_client: #endif /* WITH_DYNAMIC_CLIENTS */ if (!client_add(clients, c)) { cf_log_err_cs(cs, "Failed to add client %s", cf_section_name2(cs)); client_free(c); return NULL; } } /* * Replace the global list of clients with the new one. * The old one is still referenced from the original * configuration, and will be freed when that is freed. */ if (global) { root_clients = clients; } return clients; }