// // File: // // Written by: David M. Stanhope [voip@fobbit.com] // // routines to send and receive messages from a phonebook server // // TODO: // 1> will break badly if '|' in any strings sending back and forth // #include "vblast.h" static time_t last_registration_time = 0; static int control_flags = 0; // control flags #define NO_STATS 0x0001 // --------------------------------------------------------------------------- // build a time string for display // --------------------------------------------------------------------------- char * time_string_time_t(time_t t) { static char buf[4][64]; static int idx = 0; char *s = ctime(&t); strcpy(buf[idx], s); s = buf[idx++]; if(idx > 3) idx = 0; s[24] = '\0'; // drop '\n' return s; } char * time_string_str(char *ts) { time_t t = atol (ts); return time_string_time_t(t); } char * timestamp(void) { time_t t = RIGHT_NOW; return time_string_time_t(t); } // --------------------------------------------------------------------------- // send a message to the server and expect a response // --------------------------------------------------------------------------- #define MAX_REDIRECTS 5 static int _do_message_to_server(char *msg, char *name, struct sockaddr_in *address) { static char buf[BIG_BUF_SIZE]; // may want big alerts SOCKET fd_server; char *s, *d, *p; int c, n, have; if((fd_server = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { ERR(("message_to_server(%s): socket() error(%s)\n", name, Socket_Error())) return -1; } set_timeout(); if(connect(fd_server, (struct sockaddr *) address, sizeof(struct sockaddr_in)) == SOCKET_ERROR) { clr_timeout(); ERR(("message_to_server(%s): connect() error(%s)\n", name, Socket_Error())) Socket_Close(fd_server); return 0; } clr_timeout(); // must encode any spaces in the message and add GET stuff // really encodes all non-printable characters to be safe if(http_server) { strcpy(buf, "GET /cgi-bin/vs_cgi?"); d = buf + strlen(buf); s = msg; while(c = (((int)(*s++)) & 0x000000ff)) { if((c <= ' ') || (c >= 0x7f) || (c == '\\')) { *d++ = '\\'; *d++ = hex_char(c >> 4); *d++ = hex_char(c ); } else { *d++ = c; } } strcpy(d, " HTTP/1.0\r\n\r\n"); // point to message to send and len, for http don't send the '\0' s = buf; n = strlen(s); } else { // set message to send and length, for non-http send the '\0; s = msg; n = strlen(s) + 1; } // send the message plus the trailing '\0' vblast_socket_writer("message_to_server", fd_server, s, n); // get a response, keep reading till get entire message from server, // should end with a '\0' have = 0; // TODO: ADD TIMEOUT TO ENTIRE LOOP while(1) { set_timeout(); n = Socket_Read(fd_server, buf + have, sizeof(buf) - have); clr_timeout(); if(n < 1) // should at least get something { ERR(("message_to_server(%s): bad response length(%d)\n", name, n)) Socket_Close(fd_server); return 0; } have += n; if(buf[have - 1] == '\0') { break; } // done when get trailing '\0' if(have >= sizeof(buf)) { ERR(("message_to_server(%s): response too long\n", name)) Socket_Close(fd_server); return 0; } } Socket_Close(fd_server); if(http_server) // skip over the HTTP header by looking for "\r\n\r\n" { s = buf; p = "\r\n\r\n"; d = p; while(*s != '\0') { if(*s++ == *d++) { if(*d == '\0') break; // found it } else { d = p; // back to start of the pattern } } if(*s == '\0') { ERR(("message_to_server(%s): can't parse http header\n", name)) return 0; } d = s; } else // non http { d = buf; } // TODO: ADD DECODE IF ENCODED SINCE BETTER FOR ALERTS, SHOULD BE // BACKWARD COMPATIBLE SINCE NO REASON FOR '\' IN RESPONSES MSG1(("RESPONSE(%s)\n", d)) var_parse(d); if((s = var_find("CFLAGS")) != NULL) { control_flags = atoi(s); } if((s = var_find("ALERT")) != NULL) { // TODO: DO A POPUP, BUT MAY BLOCK? MSG((s)) // host should format it so can be multiple lines } return 1; } static int _message_to_server_with_redirect(char *msg) { char current_name[128], new_name[128], *s; int r, do_update, do_redirect, trys; short current_port, new_port; struct sockaddr_in new_address; strcpy(current_name, phonebook_current->name); current_port = ntohs(phonebook_current->address.sin_port); if((r = _do_message_to_server(msg, current_name, &(phonebook_current->address))) <= 0) { return r; // not successfull } trys = MAX_REDIRECTS; // protect from looping redirects! while(1) { if((s = var_find("NSERVER")) != NULL) // update but use next time { do_update = 1; do_redirect = 0; } else if((s = var_find("RSERVER")) != NULL) // update and use now { do_update = 1; do_redirect = 1; } else if((s = var_find("TSERVER")) != NULL) // no update but use now { do_update = 0; do_redirect = 1; } else { break; // no SERVER-REDIRECT entry so done } strcpy(new_name, s); // save copy of whole name new_port = scan_address(s, current_port); if(resolve_address(s, new_port, &new_address) != 0) { MSG(("message_to_server(%s,%s): bad Redirect(%s)\n", phonebook_current->name, current_name, s)) free(new_name); return 0; // try the next server } if(do_update) { if((s = malloc(strlen(new_name) + 1)) == NULL) { ERR(("message_to_server(%s,%s): No Memory - new_name(%s)\n", phonebook_current->name, current_name, new_name)) return -1; // serious error } memcpy(&(phonebook_current->address), &new_address, sizeof(struct sockaddr_in)); free(phonebook_current->name); phonebook_current->name = s; } if(do_redirect == 0) { break; // all done } if(--trys <= 0) { return 0; // too many tries } if((r = _do_message_to_server(msg, new_name, &(new_address))) <= 0) { return r; // not successfull } strcpy(current_name, new_name); current_port = new_port; } return 1; // success } static int _message_to_server(char *msg) { int r; PHONEBOOK *phonebook_first; if(phonebook_current == NULL) { return 0; } // can not send the message phonebook_first = phonebook_current; // mark first one to try // try all the servers until can contact one while(1) { if((r = _message_to_server_with_redirect(msg)) < 0) { return 0; // bad enough error might as well give up } if(r > 0) { break; // found one we could talk to } // Try the next server if((phonebook_current = phonebook_current->next) == NULL) { phonebook_current = phonebook_head; } if(phonebook_current == phonebook_first) { return 0; // tried them all, can't talk to any of them } MSG(("message_to_server(%s): Switching Server\n", phonebook_current->name)) } return 1; // success } // --------------------------------------------------------------------------- // build a message body describing this node, can be sent either to // the server for registration or a peer on connection // --------------------------------------------------------------------------- static void _build_id_msg(VBLAST *v, char *msg, char *type, char *cmd, int registration_type) { char s_version[16]; sprintf(s_version, "%d.%d", VERSION_MAJOR, VERSION_MINOR); var_add_str(msg , type , serial_number_string ); var_add_str(NULL, "ip" , inet_ntoa(public_ip_and_port_tcp.sin_addr)); var_add_int(NULL, "port" , ntohs(public_ip_and_port_tcp.sin_port)); var_add_str(NULL, "name" , public_name ); var_add_str(NULL, "location", public_location ); var_add_str(NULL, "email" , public_email ); var_add_int(NULL, "flags" , (registration_type ? 0x80 : 0) | (v->status_headset == STATUS_HEADSET_IN ? 0x40 : 0) | can_accept_inbound); var_add_str(NULL, "version" , s_version ); var_add_str(NULL, "os" , os_name() ); var_add_i32(NULL, "rtime" , last_registration_time ); if(cmd != NULL) // pass cmd if given { var_add_str(NULL, "cmd", cmd); } if(port_listen_udp >= 0) // pass udp info if enabled { var_add_str(NULL,"udp_ip",inet_ntoa(public_ip_and_port_udp.sin_addr)); var_add_int(NULL,"udp_port", ntohs(public_ip_and_port_udp.sin_port)); } } // --------------------------------------------------------------------------- // send a message to a peer, usually via TCP, but use UDP for UDP probes // --------------------------------------------------------------------------- void send_msg(VBLAST *v, char *msg, struct sockaddr_in *udp_address) { int n; n = strlen(msg + MSG_HEADER_SIZE) + MSG_HEADER_SIZE + 1; // header + EOS msg[0] = MAGIC_CNTRL ; msg[1] = (n >> 8) & 0xff; msg[2] = (n ) & 0xff; if(udp_address) // only used for checking if udp works, ignore errors { sendto(v->fd_peer_udp, msg, n, 0, (struct sockaddr *) udp_address, sizeof(struct sockaddr_in)); } else // normally sent via TCP for reliability { if(vblast_socket_writer("send_msg", v->fd_peer_tcp, msg, n) <= 0) { Socket_Close(v->fd_peer_tcp); v->fd_peer_tcp = INVALID_SOCKET; } } } // --------------------------------------------------------------------------- // send id to peer on initial connection // --------------------------------------------------------------------------- void send_my_id(VBLAST *v, char *cmd) { u_char msg[TXT_BUF_SIZE]; _build_id_msg(v, msg + MSG_HEADER_SIZE, "id", cmd, 0); send_msg(v, msg, NULL); } // --------------------------------------------------------------------------- // send periodic registration messages to the phonebook server // called by most callback routines, and also useable as the default callback // --------------------------------------------------------------------------- int registration_update(VBLAST *v) { static time_t next_registration = ( 0); // when to do next one static int registration_type = ( 1); // is initial registration static int registration_timer = ( 600); // 10 minutes char *p, msg[TXT_BUF_SIZE]; int t; if((publish_to_server) && (RIGHT_NOW >= next_registration)) { _build_id_msg(v, msg, "register", NULL, registration_type); if(_message_to_server(msg) > 0) { if((p = var0_chk("rtimer")) != NULL) { if((t = atoi(p)) > 0) { registration_timer = t; last_registration_time = RIGHT_NOW; UPDATE_LOCAL(last_registration_time) } else { ERR(("Registration Response has bogus 'rtimer' (%d)\n", t)) } } else { ERR(("Registration Response missing 'rtimer'\n")) } } next_registration = RIGHT_NOW + registration_timer; registration_type = 0; // from now on will be updates } return 0; } // --------------------------------------------------------------------------- // query the phonebook server with the serial-number, and hopefully // get back an ip-address and port // --------------------------------------------------------------------------- char * server_query(char *destination, int *port) { static char buf[TXT_BUF_SIZE]; char *d_serial_number ; char *d_listen_ip ; char *d_listen_port ; char *d_remote_name ; char *d_remote_location; char *d_remote_email ; char *d_remote_flags ; char *d_remote_version ; char *d_remote_os ; char *d_created ; char *d_updated ; char *d_received ; MSG(("server_query(%s)\n", destination)) var_add_str(buf , "query" , serial_number_string); var_add_str(NULL, "lookup", destination ); if(_message_to_server(buf) <= 0) { return NULL; } if((d_serial_number = var0_chk("serial")) == NULL) { return NULL; } if(strcmp(d_serial_number, "UNKNOWN") == 0) { return NULL; } if((d_listen_ip = var_find("ip" )) == NULL) { return NULL; } if((d_listen_port = var_find("port" )) == NULL) { return NULL; } if((d_remote_name = var_find("name" )) == NULL) { return NULL; } if((d_remote_location = var_find("location")) == NULL) { return NULL; } if((d_remote_email = var_find("email" )) == NULL) { return NULL; } if((d_remote_flags = var_find("flags" )) == NULL) { return NULL; } if((d_remote_version = var_find("version" )) == NULL) { return NULL; } if((d_remote_os = var_find("os" )) == NULL) { return NULL; } if((d_created = var_find("ctime" )) == NULL) { return NULL; } if((d_updated = var_find("utime" )) == NULL) { return NULL; } if((d_received = var_find("rtime" )) == NULL) { return NULL; } MSG(("PHONEBOOK - Serial-Number: %s\n" " IP : %s\n" " Port : %s\n" " Name : %s\n" " Location : %s\n" " Email : %s\n" " Flags : %s\n" " Version : %s\n" " OS : %s\n" " Created : %s\n" " Changed : %s\n" " Received : %s\n", d_serial_number , d_listen_ip , d_listen_port , d_remote_name , d_remote_location, d_remote_email , d_remote_flags , d_remote_version , d_remote_os , time_string_str(d_created ), time_string_str(d_updated ), time_string_str(d_received ))) UPDATE_REMOTE(" (From Phonebook)") if(strcmp(serial_number_string, destination) == 0) { MSG(("Trying to call myself\n")) return NULL; } strcpy(buf, d_listen_ip); *port = atoi(d_listen_port); return buf; } // --------------------------------------------------------------------------- // let the server know about call status so can see how well things work // --------------------------------------------------------------------------- void send_stats(int net_sent_tcp, int net_sent_udp, int net_received_tcp, int net_received_udp, int net_dropped, int net_blocked, int max_queue, time_t duration, int called, char *peer_serial_number, int hangup_reason) { static char buf[TXT_BUF_SIZE]; if((query_server == 0) || // if no start record then no end record (control_flags & NO_STATS)) { return; } var_add_str(buf , "stats" , serial_number_string ); var_add_str(NULL, "dir" , called ? "from" : "to"); var_add_str(NULL, "peer" , peer_serial_number ); var_add_int(NULL, "tcp_tx", net_sent_tcp ); var_add_int(NULL, "udp_tx", net_sent_udp ); var_add_int(NULL, "tcp_rx", net_received_tcp ); var_add_int(NULL, "udp_rx", net_received_udp ); var_add_int(NULL, "drop" , net_dropped ); var_add_int(NULL, "block" , net_blocked ); var_add_int(NULL, "queue" , max_queue ); var_add_i32(NULL, "secs" , duration ); var_add_i32(NULL, "reason", hangup_reason ); _message_to_server(buf); } // --------------------------------------------------------------------------- // // The End! //