/* ************************************************************* RWhois Software Copyright (c) 1994 Scott Williamson and Mark Kosters Copyright (c) 1996-2000 Network Solutions, Inc. See the file LICENSE for conditions of use and distribution. **************************************************************** */ /* * response.c: file containing function to read from a socket. to be * incorporated into rwhois library. */ /* * This function reads one line from the socket using fetch_data(). * It then parses the string, looking for various rwhois response formats. * It stores schema in schema_name, and the attribute:value pair in * rw_avpair_struct. If a blank line is encountered, it returns the data * structure rw_record_struct. */ /* * General Observations * * In response to any directive, the server returns information in the * following format: * * % : * In case the response contains more than one record, then the server * sends a % line between each record. * * In response to any query, the server returns information in the * following format: * * :: * * with a blank line separating each record. * * Referrals: In cases wherein the response to a query is a * referral from the server, "referral" is stored in the schema_name, and * the referrals are broken into server, port, and soa. * */ #include "rwhois.h" #include "rwhois_internal.h" #include "strutils.h" static rw_attr_value_enum parse_attribute_type(char *typecode) { if (typecode[0] == 'S' || typecode[0] == 's') { return SEE_ALSO; } if (typecode[0] == 'I' || typecode[0] == 'i') { return ID; } return TEXT; } /* parse attribute:value */ static rw_avpair_struct * parse_av_pair(char *av_pair_line) { char *p; char *q; rw_avpair_struct *av_pair; av_pair = (rw_avpair_struct *) CALLOC(1, sizeof(*av_pair)); p = strchr(av_pair_line, ':'); /* no colons means this is malformed */ if (! p) { FREE(av_pair); return NULL; } *p = '\0'; p++; av_pair->value = STRDUP(p); q = strchr(av_pair_line, ';'); if (q) { *q = '\0'; q++; av_pair->attribute = STRDUP(av_pair_line); av_pair->attribute_header = (rw_header_struct *) CALLOC(1, sizeof(rw_header_struct)); av_pair->attribute_header->type = parse_attribute_type(q); q--; *q = ';'; } else { av_pair->attribute = STRDUP(av_pair_line); } p--; *p = ':'; return av_pair; } static int is_record_empty(rw_record_struct *rec) { if (!rec) return TRUE; if (rec->referral) return FALSE; if (rec->text) return FALSE; if (!rec->av_pair_list) return TRUE; if (dl_list_empty(rec->av_pair_list)) return TRUE; return FALSE; } static rw_record_struct * allocate_rw_record_struct(rw_record_struct *rec, rw_server_struct *server) { if (rec) return rec; rec = (rw_record_struct *) CALLOC(1, sizeof(*rec)); rec->av_pair_list = dl_list_create(destroy_rw_avpair_struct); if (server) { rwhois_set_record_source_by_server(rec, server); } return rec; } static void fill_out_record_info(rw_record_struct *rec, rw_avpair_struct *av) { if (!rec) return; if (!av) return; if (NOT_STR_EXISTS(av->attribute)) return; if (NOT_STR_EXISTS(av->value)) return; if (STR_EQ(av->attribute, "Class-Name") || STR_EQ(av->attribute, "CN")) { rec->schema_name = STRDUP(av->value); } else if (STR_EQ(av->attribute, "ID")) { rec->id = STRDUP(av->value); } } static char * fetch_info_block(rw_server_struct *server, rw_response_struct *response) { char line[MAXBUFFER]; char *buffer = NULL; size_t buffer_size = 0; size_t text_size = 0; size_t line_length = 0; buffer = (char *) MALLOC(MAXBUFFER); buffer_size = 0; buffer[0] = '\0'; while ( rwhois_read(server, line, sizeof(line)) > 0 ) { if (STRN_EQ(line, "%info off", 9)) { break; } line_length = strlen(line); /* see if we need to grow the buffer */ if ( (text_size + line_length + 2) > buffer_size && buffer_size < MAXINFOTEXT) { buffer = (char *) REALLOC(buffer, buffer_size + MAXBUFFER); buffer_size += MAXBUFFER; } strcat(buffer, line); text_size += line_length; strcat(buffer, "\n"); text_size++; } return buffer; } void rwhois_set_record_source(rw_record_struct *record, char *protocol, char *host, int port) { rw_record_source_struct *source; assert(record); source = (rw_record_source_struct *) CALLOC(1, sizeof(*source)); if (STR_EXISTS(protocol)) { source->protocol = STRDUP(protocol); } if (STR_EXISTS(host)) { source->host = STRDUP(host); } source->port = port; if (record->source) { destroy_rw_record_source_struct(record->source); } record->source = source; } void rwhois_set_record_source_by_server(rw_record_struct *record, rw_server_struct *server) { assert(server); rwhois_set_record_source(record, "rwhois", server->host, server->port); } rw_record_struct * rwhois_fetch_record(rw_server_struct *server, rw_response_struct *response) { int fetch_ret; char buffer[MAXBUFFER]; char error_buf[MAXBUFFER]; fetch_state_enum state; rw_record_struct *record = NULL; assert(server); assert(response); if (! rwhois_is_open(server)) { rwhois_set_response(response, RW_ERROR, "Connection to rwhois server not open"); return NULL; } /* set initial state */ state = RW_STATE_READ_LINE; /* aren't state machines much easier to figure out? ;-) */ while (state != RW_STATE_FINISHED) { switch(state) { case RW_STATE_READ_LINE: fetch_ret = rwhois_read(server, buffer, sizeof(buffer)); if (fetch_ret == 0) { rwhois_close(server); state = RW_STATE_FINISHED; } else if (fetch_ret < 0) { sprintf(error_buf, "Read from socket failed [%d]: %s", fetch_ret, strerror(errno)); rwhois_set_response(response, RW_ERROR, error_buf); return NULL; } state = RW_STATE_DECODE_LINE; break; case RW_STATE_DECODE_LINE: state = decode_line(buffer, server->version); break; case RW_STATE_ERROR: rwhois_set_response(response, rwhois_extract_error_number(buffer), rwhois_extract_error_msg(buffer)); state = RW_STATE_FINISHED; break; case RW_STATE_OK: rwhois_set_response(response, RW_DONE, ""); state = RW_STATE_FINISHED; break; case RW_STATE_INFO: /* load the info block into the current record */ record = allocate_rw_record_struct(record, server); record->text = fetch_info_block(server, response); state = RW_STATE_READ_LINE; break; case RW_STATE_RWHOIS: server->version = get_version(buffer); get_capability(buffer, &(server->capability)); rwhois_set_response(response, RW_OK, ""); state = RW_STATE_READ_LINE; break; case RW_STATE_REFERRAL: record = allocate_rw_record_struct(record, server); record->referral = rwhois_parse_referral(buffer, server->version, NULL); if (!record->referral) { rwhois_set_response(response, RW_ERROR, "Error while parsing referral"); } else { rwhois_set_response(response, RW_FETCH_MORE, ""); } state = RW_STATE_FINISHED; break; /* these are the record seperator states. They all lead to end states */ case RW_STATE_DIRECTIVE_SEP: case RW_STATE_SCHEMA_SEP: case RW_STATE_DEFINE_SEP: case RW_STATE_DISPLAY_SEP: case RW_STATE_OBJECT_SEP: case RW_STATE_SOA_SEP: case RW_STATE_STATUS_SEP: case RW_STATE_XFER_SEP: case RW_STATE_CLASS_SEP: case RW_STATE_END_OF_RECORD: rwhois_set_response(response, RW_FETCH_MORE, ""); state = RW_STATE_FINISHED; break; /* those things that follow the directive, field, value tuple */ case RW_STATE_IN_DIRECTIVE: case RW_STATE_IN_SCHEMA: case RW_STATE_IN_DEFINE: case RW_STATE_IN_DISPLAY: case RW_STATE_IN_OBJECT: case RW_STATE_IN_SOA: case RW_STATE_IN_STATUS: case RW_STATE_IN_REGISTER: case RW_STATE_IN_CLASS: /* your probably saying to yourself "My god, why doesn't he make */ /* functions for all of this. This switch is huge!" I'm doing it */ /* so that I can see all of the special cases and states in one */ /* place. When its clean and working I'll break it up. */ /* In this state, the directive is the record's schema name, and the rest of the line is broken into av_pairs */ { char *payload; rw_avpair_struct *cur_av; record = allocate_rw_record_struct(record, server); payload = strchr(buffer, ' '); if (payload) { *payload = '\0'; if (!record->schema_name) { record->schema_name = STRDUP(buffer); } *payload = ' '; payload++; } else { payload = buffer; } cur_av = parse_av_pair(payload); if (cur_av) { dl_list_append(record->av_pair_list, cur_av); state = RW_STATE_READ_LINE; } else { rwhois_set_response(response, RW_ERROR, "Improperly formatted directive record line"); state = RW_STATE_FINISHED; } break; } /* the one thing that follows the directive, class, attr, value tuple, so the record schema_name is the class, not the directive name */ case RW_STATE_IN_XFER: { char *schema; char *payload; rw_avpair_struct *cur_av; record = allocate_rw_record_struct(record, server); schema = strchr(buffer, ' '); if (!schema) { rwhois_set_response(response, RW_ERROR, "Improperly formatted xfer line"); state = RW_STATE_FINISHED; break; } schema++; payload = strchr(schema, ':'); if (!payload) { rwhois_set_response(response, RW_ERROR, "Improperly formatted xfer line"); state = RW_STATE_FINISHED; break; } *payload = '\0'; if (!record->schema_name) { record->schema_name = STRDUP(schema); } *payload = ':'; payload++; cur_av = parse_av_pair(payload); if (cur_av) { dl_list_append(record->av_pair_list, cur_av); state = RW_STATE_READ_LINE; } else { rwhois_set_response(response, RW_ERROR, "Improperly formatted xfer line"); state = RW_STATE_FINISHED; } break; } /* the thing that follows the class, attribute, value tuple */ case RW_STATE_IN_RECORD: { char *payload; rw_avpair_struct *cur_av; record = allocate_rw_record_struct(record, server); if (count_char(buffer, ':') > 1) { payload = strchr(buffer, ':'); if (!record->schema_name) { *payload = '\0'; record->schema_name = STRDUP(buffer); *payload = ':'; } payload++; } else { /* this isn't a normal case, but if we just start getting attr:value instead of schema:attr:value */ payload = buffer; } cur_av = parse_av_pair(payload); if (cur_av) { fill_out_record_info(record, cur_av); dl_list_append(record->av_pair_list, cur_av); state = RW_STATE_READ_LINE; } else { rwhois_set_response(response, RW_ERROR, "Improperly formatted record line"); state = RW_STATE_FINISHED; } break; } case RW_STATE_FINISHED: break; default: rwhois_set_response(response, RW_ERROR, "Unknown state encountered in rwhois_fetch_record"); destroy_rw_record_struct(record); return NULL; break; } } return record; } dl_list_type * rwhois_fetch_all_records(rw_server_struct *server, dl_list_type *record_list, rw_response_struct *response) { rw_record_struct *cur_record; int not_done = TRUE; assert(server); assert(response); /* allocate the list */ if (!record_list) { record_list = dl_list_create(destroy_rw_record_struct); } while (not_done) { cur_record = rwhois_fetch_record(server, response); if (response->status != RW_FETCH_MORE) { not_done = FALSE; } if (cur_record) { /* skip meaningless records (not a referral or a real record); we get these from the rwhois_fetch_record state machine, which allocates a record by default */ if (is_record_empty(cur_record)) { destroy_rw_record_struct(cur_record); continue; } /* calculate the schema name if we haven't already */ if (!cur_record->schema_name) { char *schema; if ( (schema = find_value_for_attribute(cur_record, "Class-Name")) || ( schema = find_value_for_attribute(cur_record, "CN")) ) { cur_record->schema_name = STRDUP(schema); } } dl_list_append(record_list, cur_record); } } return record_list; } void rwhois_print_record(rw_record_struct *record) { if (!record || !record->av_pair_list || dl_list_empty(record->av_pair_list)) { return; } if (record->schema_name[0] == '\0') { return; } printf("class: %s\n", record->schema_name); rwhois_print_avlist(record->av_pair_list); } void rwhois_print_avlist(dl_list_type *av_pair_list) { rw_avpair_struct *cur_av; int not_done; if (!av_pair_list) return; not_done = dl_list_first(av_pair_list); while (not_done) { cur_av = (rw_avpair_struct *) dl_list_value(av_pair_list); if (!cur_av) { not_done = dl_list_next(av_pair_list); continue; } printf("%s: %s\n", SAFE_STR(cur_av->attribute, "none"), SAFE_STR(cur_av->value, "none")); not_done = dl_list_next(av_pair_list); } } void rwhois_print_status(rw_server_struct *server, rw_server_status_struct *status) { printf("STATUS:\n"); printf("\tLimit = %d\n", status->limit); if (status->holdconnect == RW_ON) printf("\tHoldconnect = ON\n"); else printf("\tHoldconnect = OFF\n"); if (status->forward == RW_ON) printf("\tForward = ON\n"); else printf("\tForward = OFF\n"); if ( server->version == VER_1_5) { printf("\tObjects = %d\n", status->objects); printf("\tDisplay = %s\n", status->display); printf("\tContact = %s\n", status->contact); } else if (server->version == VER_1_0) { printf("\tLoad = %f\n", status->load); if (status->cache == RW_ON) printf("\tCache = ON\n"); else printf("\tCache = OFF\n"); printf("\tDisplay-Single = %s\n", status->display_single); printf("\tDisplay-Multi = %s\n", status->display_multi); printf("\tAuthority = %d\n", status->authority); printf("\tCached = %d\n", status->cached); } } int destroy_rw_record_struct(rw_record_struct *record) { if (!record) return TRUE; if (record->schema_name) FREE(record->schema_name); if (record->id) FREE(record->id); if (record->text) FREE(record->text); if (record->source) destroy_rw_record_source_struct(record->source); if (record->referral) destroy_rw_referral_struct(record->referral); if (record->av_pair_list) dl_list_destroy(record->av_pair_list); FREE(record); return TRUE; } int destroy_rw_record_source_struct(rw_record_source_struct *source) { if (!source) return TRUE; if (source->protocol) FREE(source->protocol); if (source->host) FREE(source->host); FREE(source); return TRUE; } int destroy_rw_avpair_struct(rw_avpair_struct *avpair) { if (!avpair) return TRUE; if (avpair->attribute_header) { destroy_rw_header_struct(avpair->attribute_header); } if (avpair->attribute) FREE(avpair->attribute); if (avpair->value) FREE(avpair->value); FREE(avpair); return TRUE; } int destroy_rw_header_struct(rw_header_struct *header) { if (!header) return TRUE; if (header->language) FREE(header->language); if (header->charset) FREE(header->charset); if (header->encoding) FREE(header->encoding); if (header->other_headers) FREE(header->other_headers); FREE(header); return TRUE; }