Atrinik Server 2.5
socket/loop.c
Go to the documentation of this file.
00001 /************************************************************************
00002 *            Atrinik, a Multiplayer Online Role Playing Game            *
00003 *                                                                       *
00004 *    Copyright (C) 2009-2011 Alex Tokar and Atrinik Development Team    *
00005 *                                                                       *
00006 * Fork from Daimonin (Massive Multiplayer Online Role Playing Game)     *
00007 * and Crossfire (Multiplayer game for X-windows).                       *
00008 *                                                                       *
00009 * This program is free software; you can redistribute it and/or modify  *
00010 * it under the terms of the GNU General Public License as published by  *
00011 * the Free Software Foundation; either version 2 of the License, or     *
00012 * (at your option) any later version.                                   *
00013 *                                                                       *
00014 * This program is distributed in the hope that it will be useful,       *
00015 * but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00017 * GNU General Public License for more details.                          *
00018 *                                                                       *
00019 * You should have received a copy of the GNU General Public License     *
00020 * along with this program; if not, write to the Free Software           *
00021 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.             *
00022 *                                                                       *
00023 * The author can be reached at admin@atrinik.org                        *
00024 ************************************************************************/
00025 
00031 #include <global.h>
00032 
00033 static fd_set tmp_read, tmp_exceptions, tmp_write;
00034 
00036 typedef void (*func_uint8_int_ns)(char *, int, socket_struct *);
00037 
00039 struct client_cmd_mapping
00040 {
00042     char *cmdname;
00043 
00045     func_uint8_int_ns cmdproc;
00046 };
00047 
00049 typedef void (*func_uint8_int_pl)(char *, int, player *);
00050 
00052 struct player_cmd_mapping
00053 {
00055     char *cmdname;
00056 
00058     func_uint8_int_pl cmdproc;
00059 
00061     int flags;
00062 };
00063 
00065 static const struct player_cmd_mapping player_commands[] =
00066 {
00067     {"ex",          ExamineCmd, 0},
00068     {"ap",          ApplyCmd, 0},
00069     {"mv",          MoveCmd, 0},
00070     {"reply",       ReplyCmd, 0},
00071     {"cm",          PlayerCmd, 0},
00072     {"lock",        (func_uint8_int_pl) LockItem, 0},
00073     {"mark",        (func_uint8_int_pl) MarkItem, 0},
00074     {"/fire", command_fire_old, 0},
00075     {"fire", (func_uint8_int_pl) command_fire, 0},
00076     {"nc",          command_new_char, 0},
00077     {"qs",          (func_uint8_int_pl) QuickSlotCmd, 0},
00078     {"shop",        ShopCmd, 0},
00079     {"qlist",       QuestListCmd, 0},
00080     {"mp", (func_uint8_int_pl) command_move_path, 0},
00081     {"rd", (func_uint8_int_pl) cmd_ready, 0},
00082     {"pc", (func_uint8_int_pl) cmd_password_change, 0},
00083     {NULL, NULL, 0}
00084 };
00085 
00087 static const struct client_cmd_mapping client_commands[] =
00088 {
00089     {"addme",       AddMeCmd},
00090     {"askface",     SendFaceCmd},
00091     {"setup",       SetUp},
00092     {"version",     VersionCmd},
00093     {"rf",          RequestFileCmd},
00094     {"clr", command_clear_cmds},
00095     {"setsound", SetSound},
00096     {"upf", cmd_request_update},
00097     {"ka", cmd_keepalive},
00098     {NULL, NULL}
00099 };
00100 
00105 static int check_client_command(socket_struct *ns)
00106 {
00107     unsigned char *data;
00108     int i, data_len;
00109 
00110     for (i = 0; client_commands[i].cmdname; i++)
00111     {
00112         if ((int) strlen(client_commands[i].cmdname) <= ns->inbuf.len - 2 && !strncmp((char *) ns->inbuf.buf + 2, client_commands[i].cmdname, strlen(client_commands[i].cmdname)))
00113         {
00114             /* Pre-process the command. */
00115             data = (unsigned char *) strchr((char *) ns->inbuf.buf + 2, ' ');
00116 
00117             if (data)
00118             {
00119                 *data = '\0';
00120                 data++;
00121                 data_len = ns->inbuf.len - (data - ns->inbuf.buf);
00122             }
00123             else
00124             {
00125                 data_len = 0;
00126             }
00127 
00128             client_commands[i].cmdproc((char *) data, data_len, ns);
00129 
00130             /* We have successfully added this client. */
00131             if (ns->addme)
00132             {
00133                 ns->addme = 0;
00134             }
00135 
00136             return 1;
00137         }
00138     }
00139 
00140     return 0;
00141 }
00142 
00146 static void fill_command_buffer(socket_struct *ns)
00147 {
00148     int rr;
00149 
00150     do
00151     {
00152         if ((rr = SockList_ReadCommand(&ns->readbuf, &ns->inbuf)))
00153         {
00154             /* Terminate buffer - useful for string data. */
00155             ns->inbuf.buf[ns->inbuf.len] = '\0';
00156 
00157             if (check_client_command(ns))
00158             {
00159                 if (ns->status == Ns_Dead)
00160                 {
00161                     return;
00162                 }
00163             }
00164             else
00165             {
00166                 memcpy(ns->cmdbuf.buf + ns->cmdbuf.len, ns->inbuf.buf, rr);
00167                 ns->cmdbuf.len += rr;
00168             }
00169         }
00170     }
00171     while (rr);
00172 }
00173 
00179 static int check_command(socket_struct *ns, player *pl)
00180 {
00181     int i, len = 0;
00182     unsigned char *data;
00183 
00184     /* Terminate buffer - useful for string data */
00185     ns->inbuf.buf[ns->inbuf.len] = '\0';
00186 
00187     /* First, break out beginning word. There are at least
00188      * a few commands that do not have any parameters. If
00189      * we get such a command, don't worry about trying
00190      * to break it up. */
00191     data = (unsigned char *) strchr((char *) ns->inbuf.buf + 2, ' ');
00192 
00193     if (data)
00194     {
00195         *data = '\0';
00196         data++;
00197         len = ns->inbuf.len - (data - ns->inbuf.buf);
00198     }
00199     else
00200     {
00201         len = 0;
00202     }
00203 
00204     for (i = 0; client_commands[i].cmdname; i++)
00205     {
00206         if (strcmp((char *) ns->inbuf.buf + 2, client_commands[i].cmdname) == 0)
00207         {
00208             client_commands[i].cmdproc((char *) data, len, ns);
00209             ns->inbuf.len = 0;
00210 
00211             /* We have successfully added this connect! */
00212             if (ns->addme)
00213             {
00214                 ns->addme = 0;
00215             }
00216 
00217             return 1;
00218         }
00219     }
00220 
00221     /* Only valid players can use these commands */
00222     if (pl)
00223     {
00224         for (i = 0; player_commands[i].cmdname; i++)
00225         {
00226             if (strcmp((char *) ns->inbuf.buf + 2, player_commands[i].cmdname) == 0)
00227             {
00228                 player_commands[i].cmdproc((char *) data, len, pl);
00229                 ns->inbuf.len = 0;
00230                 return 1;
00231             }
00232         }
00233     }
00234 
00235     LOG(llevSystem, "Bad command from client ('%s') (%s)\n", ns->inbuf.buf + 2, STRING_SAFE((char *) data));
00236     return 0;
00237 }
00238 
00247 void handle_client(socket_struct *ns, player *pl)
00248 {
00249     int cmd_count = 0;
00250 
00251     /* Loop through this - maybe we have several complete packets here. */
00252     while (1)
00253     {
00254         /* If it is a player, and they don't have any speed left, we
00255          * return, and will parse the data when they do have time. */
00256         if (ns->status == Ns_Zombie || ns->status == Ns_Dead || (pl && pl->state == ST_PLAYING && pl->ob != NULL && pl->ob->speed_left < 0))
00257         {
00258             return;
00259         }
00260 
00261         if (!SockList_ReadCommand(&ns->cmdbuf, &ns->inbuf))
00262         {
00263             return;
00264         }
00265 
00266         /* Reset idle counter. */
00267         if (pl && pl->state == ST_PLAYING)
00268         {
00269             ns->login_count = 0;
00270             ns->keepalive = 0;
00271         }
00272 
00273         if (check_command(ns, pl))
00274         {
00275             if (cmd_count++ <= 8 && ns->status != Ns_Dead)
00276             {
00277                 continue;
00278             }
00279         }
00280 
00281         return;
00282     }
00283 }
00284 
00289 void watchdog()
00290 {
00291     static int fd = -1;
00292     static struct sockaddr_in insock;
00293 
00294     if (fd == -1)
00295     {
00296         struct protoent *protoent;
00297 
00298         if ((protoent = getprotobyname("udp")) == NULL || (fd = socket(PF_INET, SOCK_DGRAM, protoent->p_proto)) == -1)
00299         {
00300             return;
00301         }
00302 
00303         insock.sin_family = AF_INET;
00304         insock.sin_port = htons((unsigned short) 13325);
00305         insock.sin_addr.s_addr = inet_addr("127.0.0.1");
00306     }
00307 
00308     sendto(fd, (void *) &fd, 1, 0, (struct sockaddr *) &insock, sizeof(insock));
00309 }
00310 
00315 void remove_ns_dead_player(player *pl)
00316 {
00317     if (pl == NULL || pl->ob->type == DEAD_OBJECT)
00318     {
00319         return;
00320     }
00321 
00322     if (pl->state == ST_PLAYING)
00323     {
00324         /* Trigger the global LOGOUT event */
00325         trigger_global_event(GEVENT_LOGOUT, pl->ob, pl->socket.host);
00326         statistics_player_logout(pl);
00327 
00328         if (!pl->dm_stealth)
00329         {
00330             new_draw_info_format(NDI_ALL, COLOR_DK_ORANGE, NULL, "%s left the game.", query_name(pl->ob, NULL));
00331         }
00332 
00333         /* If this player is in a party, leave the party */
00334         if (pl->party)
00335         {
00336             command_party(pl->ob, "leave");
00337         }
00338 
00339         strncpy(pl->killer, "left", MAX_BUF - 1);
00340         hiscore_check(pl->ob, 1);
00341 
00342         /* Be sure we have closed container when we leave */
00343         container_unlink(pl, NULL);
00344 
00345         save_player(pl->ob, 0);
00346 
00347         if (!QUERY_FLAG(pl->ob, FLAG_REMOVED))
00348         {
00349             leave_map(pl->ob);
00350         }
00351 
00352         if (pl->ob->map)
00353         {
00354             if (pl->ob->map->in_memory == MAP_IN_MEMORY)
00355             {
00356                 pl->ob->map->timeout = MAP_TIMEOUT(pl->ob->map);
00357             }
00358 
00359             pl->ob->map = NULL;
00360         }
00361     }
00362 
00363     LOG(llevInfo, "Logout %s from IP %s\n", pl->ob->name, pl->socket.host);
00364 
00365     /* To avoid problems with inventory window */
00366     pl->ob->type = DEAD_OBJECT;
00367     free_player(pl);
00368 }
00369 
00374 static int is_fd_valid(int fd)
00375 {
00376 #ifndef WIN32
00377     return fcntl(fd, F_GETFL) != -1 || errno != EBADF;
00378 #else
00379     return 1;
00380 #endif
00381 }
00382 
00386 #define FREE_SOCKET(i) \
00387     free_newsocket(&init_sockets[(i)]); \
00388     init_sockets[(i)].status = Ns_Avail; \
00389     socket_info.nconns--;
00390 
00396 void doeric_server()
00397 {
00398     int i, pollret, rr;
00399     struct sockaddr_in addr;
00400     socklen_t addrlen = sizeof(addr);
00401     player *pl, *next;
00402 
00403 #if CS_LOGSTATS
00404     if ((time(NULL) - cst_lst.time_start) >= CS_LOGTIME)
00405     {
00406         write_cs_stats();
00407     }
00408 #endif
00409 
00410     FD_ZERO(&tmp_read);
00411     FD_ZERO(&tmp_write);
00412     FD_ZERO(&tmp_exceptions);
00413 
00414     for (i = 0; i < socket_info.allocated_sockets; i++)
00415     {
00416         if (init_sockets[i].status == Ns_Add && !is_fd_valid(init_sockets[i].fd))
00417         {
00418             LOG(llevDebug, "doeric_server(): Invalid waiting fd %d\n", i);
00419             init_sockets[i].status = Ns_Dead;
00420         }
00421 
00422         if (init_sockets[i].status == Ns_Dead)
00423         {
00424             FREE_SOCKET(i);
00425         }
00426         else if (init_sockets[i].status == Ns_Zombie)
00427         {
00428             if (init_sockets[i].login_count++ >= 1000000 / MAX_TIME)
00429             {
00430                 init_sockets[i].status = Ns_Dead;
00431             }
00432         }
00433         else if (init_sockets[i].status != Ns_Avail)
00434         {
00435             if (init_sockets[i].status > Ns_Wait)
00436             {
00437                 if (init_sockets[i].keepalive++ >= (uint32) SOCKET_KEEPALIVE_TIMEOUT * (1000000 / max_time))
00438                 {
00439                     LOG(llevInfo, "Keepalive: disconnecting %s: %d\n", init_sockets[i].host ? init_sockets[i].host : "(unknown ip?)", init_sockets[i].fd);
00440                     FREE_SOCKET(i);
00441                     continue;
00442                 }
00443             }
00444 
00445             FD_SET((uint32) init_sockets[i].fd, &tmp_read);
00446             FD_SET((uint32) init_sockets[i].fd, &tmp_write);
00447             FD_SET((uint32) init_sockets[i].fd, &tmp_exceptions);
00448         }
00449     }
00450 
00451     /* Go through the players. Let the loop set the next pl value, since
00452      * we may remove some. */
00453     for (pl = first_player; pl != NULL; )
00454     {
00455         if (pl->socket.status != Ns_Dead && !is_fd_valid(pl->socket.fd))
00456         {
00457             LOG(llevDebug, "doeric_server(): Invalid file descriptor for player %s [%s]: %d\n", (pl->ob && pl->ob->name) ? pl->ob->name : "(unnamed player?)", (pl->socket.host) ? pl->socket.host : "(unknown ip?)", pl->socket.fd);
00458             pl->socket.status = Ns_Dead;
00459         }
00460 
00461         if (pl->socket.status != Ns_Dead && pl->socket.socket_version >= 1052 && pl->socket.keepalive++ >= (uint32) SOCKET_KEEPALIVE_TIMEOUT * (1000000 / max_time))
00462         {
00463             LOG(llevInfo, "Keepalive: disconnecting %s [%s]: %d\n", (pl->ob && pl->ob->name) ? pl->ob->name : "(unnamed player?)", (pl->socket.host) ? pl->socket.host : "(unknown ip?)", pl->socket.fd);
00464             pl->socket.status = Ns_Dead;
00465         }
00466 
00467         if (pl->socket.status == Ns_Dead)
00468         {
00469             player *npl = pl->next;
00470 
00471             remove_ns_dead_player(pl);
00472             pl = npl;
00473         }
00474         else if (pl->socket.status == Ns_Zombie)
00475         {
00476             if (pl->socket.login_count++ >= 1000000 / MAX_TIME)
00477             {
00478                 pl->socket.status = Ns_Dead;
00479             }
00480         }
00481         else
00482         {
00483             FD_SET((uint32) pl->socket.fd, &tmp_read);
00484             FD_SET((uint32) pl->socket.fd, &tmp_write);
00485             FD_SET((uint32) pl->socket.fd, &tmp_exceptions);
00486             pl = pl->next;
00487         }
00488     }
00489 
00490     socket_info.timeout.tv_sec = 0;
00491     socket_info.timeout.tv_usec = 0;
00492 
00493     pollret = select(socket_info.max_filedescriptor, &tmp_read, &tmp_write, &tmp_exceptions, &socket_info.timeout);
00494 
00495     if (pollret == -1)
00496     {
00497         LOG(llevDebug, "doeric_server(): select failed: %s\n", strerror_local(errno));
00498         return;
00499     }
00500 
00501     /* Following adds a new connection */
00502     if (pollret && FD_ISSET(init_sockets[0].fd, &tmp_read))
00503     {
00504         int newsocknum = 0;
00505 
00506         /* If this is the case, all sockets are currently in use */
00507         if (socket_info.allocated_sockets <= socket_info.nconns)
00508         {
00509             init_sockets = realloc(init_sockets, sizeof(socket_struct) * (socket_info.nconns + 1));
00510 
00511             if (!init_sockets)
00512             {
00513                 LOG(llevError, "doeric_server(): Out of memory\n");
00514             }
00515 
00516             newsocknum = socket_info.allocated_sockets;
00517             socket_info.allocated_sockets++;
00518             init_sockets[newsocknum].status = Ns_Avail;
00519         }
00520         else
00521         {
00522             int j;
00523 
00524             for (j = 1; j < socket_info.allocated_sockets; j++)
00525             {
00526                 if (init_sockets[j].status == Ns_Avail)
00527                 {
00528                     newsocknum = j;
00529                     break;
00530                 }
00531             }
00532         }
00533 
00534         init_sockets[newsocknum].fd = accept(init_sockets[0].fd, (struct sockaddr *) &addr, &addrlen);
00535 
00536         if (init_sockets[newsocknum].fd == -1)
00537         {
00538             LOG(llevDebug, "doeric_server(): accept failed: %s\n", strerror_local(errno));
00539         }
00540         else
00541         {
00542             char buf[MAX_BUF];
00543             long ip = ntohl(addr.sin_addr.s_addr);
00544 
00545             snprintf(buf, sizeof(buf), "%ld.%ld.%ld.%ld", (ip >> 24) & 255, (ip >> 16) & 255, (ip >> 8) & 255, ip & 255);
00546 
00547             if (checkbanned(NULL, buf))
00548             {
00549                 LOG(llevSystem, "Ban: Banned IP tried to connect: %s\n", buf);
00550 #ifndef WIN32
00551                 close(init_sockets[newsocknum].fd);
00552 #else
00553                 shutdown(init_sockets[newsocknum].fd, SD_BOTH);
00554                 closesocket(init_sockets[newsocknum].fd);
00555 #endif
00556                 init_sockets[newsocknum].fd = -1;
00557             }
00558             else
00559             {
00560                 init_connection(&init_sockets[newsocknum], buf);
00561                 socket_info.nconns++;
00562             }
00563         }
00564     }
00565 
00566     /* Check for any exceptions/input on the sockets */
00567     if (pollret)
00568     {
00569         for (i = 1; i < socket_info.allocated_sockets; i++)
00570         {
00571             if (init_sockets[i].status == Ns_Avail)
00572             {
00573                 continue;
00574             }
00575 
00576             if (FD_ISSET(init_sockets[i].fd, &tmp_exceptions))
00577             {
00578                 FREE_SOCKET(i);
00579                 continue;
00580             }
00581 
00582             if (FD_ISSET(init_sockets[i].fd, &tmp_read))
00583             {
00584                 rr = SockList_ReadPacket(&init_sockets[i], MAXSOCKBUF_IN - 1);
00585 
00586                 if (rr < 0)
00587                 {
00588                     LOG(llevInfo, "Drop connection: %s\n", STRING_SAFE(init_sockets[i].host));
00589                     init_sockets[i].status = Ns_Dead;
00590                 }
00591                 else
00592                 {
00593                     fill_command_buffer(&init_sockets[i]);
00594                 }
00595             }
00596 
00597             if (init_sockets[i].status == Ns_Avail)
00598             {
00599                 continue;
00600             }
00601 
00602             if (init_sockets[i].status == Ns_Dead)
00603             {
00604                 FREE_SOCKET(i);
00605                 continue;
00606             }
00607 
00608             if (FD_ISSET(init_sockets[i].fd, &tmp_write))
00609             {
00610                 socket_buffer_write(&init_sockets[i]);
00611             }
00612 
00613             if (init_sockets[i].status == Ns_Dead)
00614             {
00615                 FREE_SOCKET(i);
00616             }
00617         }
00618     }
00619 
00620     /* This does roughly the same thing, but for the players now */
00621     for (pl = first_player; pl; pl = next)
00622     {
00623         next = pl->next;
00624 
00625         /* Kill players if we have problems */
00626         if (pl->socket.status == Ns_Dead || FD_ISSET(pl->socket.fd, &tmp_exceptions))
00627         {
00628             remove_ns_dead_player(pl);
00629             continue;
00630         }
00631 
00632         if (FD_ISSET(pl->socket.fd, &tmp_read))
00633         {
00634             rr = SockList_ReadPacket(&pl->socket, MAXSOCKBUF_IN - 1);
00635 
00636             if (rr < 0)
00637             {
00638                 LOG(llevInfo, "Drop connection: %s (%s)\n", STRING_OBJ_NAME(pl->ob), STRING_SAFE(pl->socket.host));
00639                 pl->socket.status = Ns_Dead;
00640             }
00641             else
00642             {
00643                 fill_command_buffer(&pl->socket);
00644             }
00645         }
00646 
00647         /* Perhaps something was bad in handle_client(), or player has
00648          * left the game. */
00649         if (pl->socket.status == Ns_Dead)
00650         {
00651             remove_ns_dead_player(pl);
00652             continue;
00653         }
00654 
00655         if (FD_ISSET(pl->socket.fd, &tmp_write))
00656         {
00657             socket_buffer_write(&pl->socket);
00658         }
00659     }
00660 }
00661 
00664 void doeric_server_write()
00665 {
00666     player *pl, *next;
00667     uint32 update_below;
00668 
00669     /* This does roughly the same thing, but for the players now */
00670     for (pl = first_player; pl; pl = next)
00671     {
00672         next = pl->next;
00673 
00674         if (pl->socket.status == Ns_Dead || FD_ISSET(pl->socket.fd, &tmp_exceptions))
00675         {
00676             remove_ns_dead_player(pl);
00677             continue;
00678         }
00679 
00680         esrv_update_stats(pl);
00681         party_update_who(pl);
00682 
00683         if (pl->update_skills)
00684         {
00685             esrv_update_skills(pl);
00686             pl->update_skills = 0;
00687         }
00688 
00689         draw_client_map(pl->ob);
00690 
00691         if (pl->ob->map && (update_below = GET_MAP_UPDATE_COUNTER(pl->ob->map, pl->ob->x, pl->ob->y)) != pl->socket.update_tile)
00692         {
00693             esrv_draw_look(pl->ob);
00694             pl->socket.update_tile = update_below;
00695         }
00696 
00697         if (FD_ISSET(pl->socket.fd, &tmp_write))
00698         {
00699             socket_buffer_write(&pl->socket);
00700         }
00701     }
00702 }