// socket_driver.cpp #include "socket_driver.h" #include "thread_functions.h" SocketDriver::SocketDriver() { stat_read = 0; stat_write = 0; } SocketDriver::~SocketDriver() { if(clientlist.size() > 0) { for(INT i = 0; i < clientlist.size() - 1; i++) { if(clientlist[i].get_conn_state() > Closed) close_connection(clientlist[i].get_fd()); } } } void SocketDriver::shutdown_connection(const INT& index) { STRING exiter(CLIENT_EXIT); INT place = exiter.find("#n"); exiter.replace(place, 2, clientlist[index].get_name()); exiter.append(END); broadcast(exiter, index, "", false); close_connection(clientlist[index].get_fd()); // remove the socket from the vector std::vector::iterator cli = clientlist.begin() + index; clientlist.erase(cli); } void SocketDriver::new_connection() { INT i = 0, argp = 1, new_fd = 0; struct sockaddr_in sock; socklen_t socklen; pthread_attr_t attr; pthread_t thread_lookup; // go back if our new socket fails to be created new_fd = open_connection(&sock); if(new_fd == -1) return; clientlist.push_back(); i = clientlist.size() - 1; // let the client socket know its descriptor clientlist[i].set_fd(new_fd); sil_log::log("SocketDriver::new_connection(): new conn on descriptor %d", new_fd); // make the socket non-blocking ioctl(new_fd, FIONBIO, &argp); socklen = sizeof(sock); if(getpeername(new_fd, (struct sockaddr *)&sock, &socklen) < 0) { sil_log::log("SocketDriver::new_connection(): getpeername lookup failed"); clientlist[i].set_hostname("unknown"); } else { // use IP as temporary hostname clientlist[i].set_hostname(inet_ntoa(sock.sin_addr)); if(clientlist[i].hasPublicIP()) { // initialize and create lookup thread pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); pthread_create(&thread_lookup, &attr, &lookup_address, (void *)&clientlist[i]); } else { sil_log::log("Client descriptor %d has a private IP", i); } } // send greeting and allow login BUFFER greeting; if(mudhelp::get_greeting(greeting)) { write_to(i, greeting); write_to(i, END); } else { sil_log::log("failed to fetch greeting"); } clientlist[i].set_conn_state(LoginName); write_to(i, LOGIN_MSG); } void SocketDriver::write_to(const INT& index, STRING msg, BOOL flushSocket) { colorparse(msg, clientlist[index].is_colorblind()); clientlist[index].to_client(msg); // add to our statistics stat_write += msg.length(); if(flushSocket) flush_output(index); } void SocketDriver::colorparse(STRING &text, BOOL colorblind) { // colors work like this: // $ // specials are: n=normal, b=bold // colors are: k=black, r=red, g=green, y=yellow, b=blue, // m=magenta, c=cyan, w=white // also use $rev for reverse video, $res to reset colors // note: not case sensitive, use $$ to escape CHAR ansi_start[3] = { 27, '[', 0 }; std::vector tag; INT i = text.find('$'); // search for our color delimiter tag while(i 0) { // fix from end of string first so our vector of $'s is valid through all substitutions for(i=tag.size() - 1; i > -1; i--) { STRING swap = ansi_start; // if our $ has another $ before it, skip over it if(text[tag[i]-1] == '$') continue; switch(text[tag[i]+1]) { case '$': // get rid of the extra '$' and continue text.replace(tag[i], 1, ""); continue; break; case 'n': case 'N': swap += "0;"; break; case 'b': case 'B': swap += "1;"; break; case '`': swap += "5;"; break; default: // solve for $res and $rev cases if( (text[tag[i]+1] == 'r' || text[tag[i]+1] == 'R') && (text[tag[i]+2] == 'e' || text[tag[i]+2] == 'E') ) { switch(text[tag[i]+3]) { case 'v': case 'V': // reverse video code swap += "7m"; break; case 's': case 'S': // reset color code swap += "0m"; break; default: continue; } text.replace(tag[i], 4, colorblind ? "" : swap); } continue; } // foreground (text) color codes switch(text[tag[i]+2]) { case 'k': case 'K': swap += "30;"; break; case 'r': case 'R': swap += "31;"; break; case 'g': case 'G': swap += "32;"; break; case 'y': case 'Y': swap += "33;"; break; case 'b': case 'B': swap += "34;"; break; case 'm': case 'M': swap += "35;"; break; case 'c': case 'C': swap += "36;"; break; case 'w': case 'W': swap += "37;"; break; default: // screwed up color code, just dump it out to the client continue; } // background color codes and terminator switch(text[tag[i]+3]) { case 'k': case 'K': swap += "40m"; break; case 'r': case 'R': swap += "41m"; break; case 'g': case 'G': swap += "42m"; break; case 'y': case 'Y': swap += "43m"; break; case 'b': case 'B': swap += "44m"; break; case 'm': case 'M': swap += "45m"; break; case 'c': case 'C': swap += "46m"; break; case 'w': case 'W': swap += "47m"; break; default: // screwed up color code, just dump it out to the client continue; } text.replace(tag[i], 4, colorblind ? "" : swap); } // throw a reset tag on to the end just to be sure if(!colorblind) { text += ansi_start; text += "0m"; } } } ClientSocket *SocketDriver::get_client_ptr(const INT& index) { if(index < clientlist.size()) return &clientlist[index]; else return NULL; } void SocketDriver::parse_in_buffer(const INT& index) { STRING command, buffer; INT i = 0; command = clientlist[index].get_next_command(); sil_log::log("parse in buffer: next command is '%s'", command.c_str()); // return if we already have a command waiting if(!command.empty()) { sil_log::log("command is not empty, returning"); return; } clientlist[index].get_in_buffer(buffer); // return if the client hasn't sent anything if(buffer.empty()) { sil_log::log("buffer is empty, returning"); return; } // add to our statistics stat_read += buffer.length(); // debug sil_log::log("socketdriver read: %s", buffer.c_str()); // clear out the old buffer clientlist[index].clear_in_buffer(); // get rid of \r's i = buffer.find('\r'); while(i > 0 && i < buffer.length()) { buffer.replace(i, 1, ""); i = buffer.find('\r'); } // reset our counter variable i = 0; // find the end of this command i = buffer.find('\n'); // only process full commands if(i > 0 && i < buffer.length()) { sil_log::log("got a full command from index %d", index); // get rid of the newline buffer.replace(i, 1, ""); command.assign(buffer.begin(), buffer.begin() + (i)); clientlist[index].set_next_command(command); // note last command received time for client clientlist[index].set_last_command_time(time(NULL)); // clear this command from the front of the buffer buffer.replace(0, i, ""); } else { // if we didn't get a full command, push the text back on the in_buffer clientlist[index].append_in_buffer(buffer); return; } // if we have another command in the buffer, put it back in the client's in_buffer if(buffer.length() > 0) clientlist[index].from_client(buffer); } BOOL SocketDriver::flush_output(const INT& index) { return(clientlist[index].write_socket()); } void SocketDriver::process_login(const INT& index) { STRING name; BOOL problemFlag = false; switch(clientlist[index].get_conn_state()) { case LoginName: name = clientlist[index].get_next_command(); // check for short name if(name.length() < 2) { clientlist[index].to_client("That is too short for a name. Please pick another."); clientlist[index].to_client(END); problemFlag = true; } // check for duplicate names for(INT i = 0; i < get_number_of_connections(); i++) { if(clientlist[i].get_name() == name) { clientlist[index].to_client("That name is already in use. Please pick another."); clientlist[index].to_client(END); problemFlag = true; } } if(!problemFlag) { clientlist[index].set_name(name); // prompt user for password clientlist[index].to_client(END); clientlist[index].to_client("What is your password?"); clientlist[index].set_conn_state(LoginPassword); } break; case LoginPassword: clientlist[index].set_password(clientlist[index].get_next_command()); // for now, let user in clientlist[index].set_conn_state(Playing); // note client's logon time clientlist[index].set_logon_time(time(NULL)); // send newline for after password entry clientlist[index].to_client(END); // broadcast entrance message STRING enterer(CLIENT_ENTRANCE); INT place = enterer.find("#n"); enterer.replace(place, 2, clientlist[index].get_name()); enterer.append(END); broadcast(enterer, index, "You join the virtual SIL universe. See `help` for available commands."); break; } } void SocketDriver::broadcast(const STRING& mmsg, const INT& index, const STRING& omsg, BOOL includeIndex) { for(INT i = 0; iget_start_time(); s << "Read: "; if(stat_read > 1048576) // bytes in a meg s << stat_read / 1048576 << "Mb, Average: "; else s << stat_read / 1024 << "Kb, Average: "; s << (stat_read / up) / 60 << " bytes per hour;" << END; s << "Wrote: "; if(stat_write > 1048576) s << stat_write / 1048576 << "Mb, Average: "; else s << stat_write / 1024 << "Kb, Average: "; s << (stat_write / up) / 60 << " bytes per hour."; return s.str(); } const STRING SocketDriver::get_client_idle_time(const INT& index) { std::stringstream s; time_t idle = time(NULL) - clientlist[index].get_last_command_time(); s << clientlist[index].get_name() << " has been idle " << convert_time(idle) << END; return s.str(); } const STRING SocketDriver::get_client_connected_time(const INT& index) { std::stringstream s; time_t played = time(NULL) - clientlist[index].get_logon_time(); s << clientlist[index].get_name() << " has been on for " << convert_time(played) << END; return s.str(); }