webperimental: killstack decides stack protects.
[freeciv.git] / server / connecthand.c
blob5b0940f7bf922e4628191cea891b1cffb48b58fd
1 /***********************************************************************
2 Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
14 #ifdef HAVE_CONFIG_H
15 #include <fc_config.h>
16 #endif
18 #include <string.h>
20 /* utility */
21 #include "capability.h"
22 #include "fcintl.h"
23 #include "log.h"
24 #include "mem.h"
25 #include "support.h"
27 /* common */
28 #include "capstr.h"
29 #include "events.h"
30 #include "game.h"
31 #include "packets.h"
32 #include "player.h"
33 #include "version.h"
35 /* server */
36 #include "aiiface.h"
37 #include "auth.h"
38 #include "diplhand.h"
39 #include "edithand.h"
40 #include "gamehand.h"
41 #include "maphand.h"
42 #include "meta.h"
43 #include "notify.h"
44 #include "plrhand.h"
45 #include "report.h"
46 #include "ruleset.h"
47 #include "sernet.h"
48 #include "settings.h"
49 #include "srv_main.h"
50 #include "stdinhand.h"
51 #include "voting.h"
53 #include "connecthand.h"
56 static bool connection_attach_real(struct connection *pconn,
57 struct player *pplayer,
58 bool observing, bool connecting);
60 /**************************************************************************
61 Set the access level of a connection, and re-send some needed info. If
62 granted is TRUE, then it will overwrite the granted_access_level too.
63 Else, it will affect only the current access level.
65 NB: This function does not send updated connection information to other
66 clients, you need to do that yourself afterwards.
67 **************************************************************************/
68 void conn_set_access(struct connection *pconn, enum cmdlevel new_level,
69 bool granted)
71 enum cmdlevel old_level = conn_get_access(pconn);
73 pconn->access_level = new_level;
74 if (granted) {
75 pconn->server.granted_access_level = new_level;
78 if (old_level != new_level) {
79 send_server_access_level_settings(pconn->self, old_level, new_level);
83 /**************************************************************************
84 Restore access level for the given connection (user). Used when taking
85 a player, observing, or detaching.
87 NB: This function does not send updated connection information to other
88 clients, you need to do that yourself afterwards.
89 **************************************************************************/
90 static void restore_access_level(struct connection *pconn)
92 /* Restore previous privileges. */
93 enum cmdlevel level = pconn->server.granted_access_level;
95 /* Detached connections must have at most the same privileges
96 * as observers, unless they were granted something higher than
97 * ALLOW_BASIC in the first place. */
98 if ((pconn->observer || !pconn->playing) && level == ALLOW_BASIC) {
99 level = ALLOW_INFO;
102 conn_set_access(pconn, level, FALSE);
105 /**************************************************************************
106 This is used when a new player joins a server, before the game
107 has started. If pconn is NULL, is an AI, else a client.
109 N.B. this only attachs a connection to a player if
110 pconn->username == player->username
112 Here we send initial packets:
113 - ruleset datas.
114 - server settings.
115 - scenario info.
116 - game info.
117 - players infos (note it's resent in srv_main.c::send_all_info(),
118 see comment there).
119 - connections infos.
120 - running vote infos.
121 ... and additionnal packets if the game already started.
122 **************************************************************************/
123 void establish_new_connection(struct connection *pconn)
125 struct conn_list *dest = pconn->self;
126 struct player *pplayer;
127 struct packet_server_join_reply packet;
128 struct packet_chat_msg connect_info;
129 char hostname[512];
130 bool delegation_error = FALSE;
131 struct packet_set_topology topo_packet;
133 /* zero out the password */
134 memset(pconn->server.password, 0, sizeof(pconn->server.password));
136 /* send join_reply packet */
137 packet.you_can_join = TRUE;
138 sz_strlcpy(packet.capability, our_capability);
139 fc_snprintf(packet.message, sizeof(packet.message), _("%s Welcome"),
140 pconn->username);
141 sz_strlcpy(packet.challenge_file, new_challenge_filename(pconn));
142 packet.conn_id = pconn->id;
143 send_packet_server_join_reply(pconn, &packet);
145 /* "establish" the connection */
146 pconn->established = TRUE;
147 pconn->server.status = AS_ESTABLISHED;
149 pconn->server.delegation.status = FALSE;
150 pconn->server.delegation.playing = NULL;
151 pconn->server.delegation.observer = FALSE;
153 conn_list_append(game.est_connections, pconn);
154 if (conn_list_size(game.est_connections) == 1) {
155 /* First connection
156 * Replace "restarting in x seconds" meta message */
157 maybe_automatic_meta_message(default_meta_message_string());
158 (void) send_server_info_to_metaserver(META_INFO);
161 /* introduce the server to the connection */
162 if (fc_gethostname(hostname, sizeof(hostname)) == 0) {
163 notify_conn(dest, NULL, E_CONNECTION, ftc_any,
164 _("Welcome to the %s Server running at %s port %d."),
165 freeciv_name_version(), hostname, srvarg.port);
166 } else {
167 notify_conn(dest, NULL, E_CONNECTION, ftc_any,
168 _("Welcome to the %s Server at port %d."),
169 freeciv_name_version(), srvarg.port);
172 /* FIXME: this (getting messages about others logging on) should be a
173 * message option for the client with event */
175 /* Notify the console that you're here. */
176 log_normal(_("%s has connected from %s."), pconn->username, pconn->addr);
178 conn_compression_freeze(pconn);
179 send_rulesets(dest);
180 send_server_setting_control(pconn);
181 send_server_settings(dest);
182 send_scenario_info(dest);
183 send_scenario_description(dest);
184 send_game_info(dest);
185 topo_packet.topology_id = wld.map.topology_id;
186 send_packet_set_topology(pconn, &topo_packet);
188 /* Do we have a player that a delegate is currently controlling? */
189 if ((pplayer = player_by_user_delegated(pconn->username))) {
190 /* Reassert our control over the player. */
191 struct connection *pdelegate;
192 fc_assert_ret(player_delegation_get(pplayer) != NULL);
193 pdelegate = conn_by_user(player_delegation_get(pplayer));
195 if (pdelegate && connection_delegate_restore(pdelegate)) {
196 /* Delegate now detached from our player. We will restore control
197 * over them as normal below. */
198 notify_conn(pconn->self, NULL, E_CONNECTION, ftc_server,
199 _("Your delegate %s was controlling your player '%s'; "
200 "now detached."), pdelegate->username,
201 player_name(pplayer));
202 notify_conn(pdelegate->self, NULL, E_CONNECTION, ftc_server,
203 _("%s reconnected, ending your delegated control of "
204 "player '%s'."), pconn->username, player_name(pplayer));
205 } else {
206 fc_assert(pdelegate);
207 /* This really shouldn't happen. */
208 log_error("Failed to revoke delegate %s's control of %s, so owner %s "
209 "can't regain control.", pdelegate->username,
210 player_name(pplayer), pconn->username);
211 notify_conn(dest, NULL, E_CONNECTION, ftc_server,
212 _("Couldn't get control of '%s' from delegation to %s."),
213 player_name(pplayer), pdelegate->username);
214 delegation_error = TRUE;
215 pplayer = NULL;
219 if (!delegation_error) {
220 if ((pplayer = player_by_user(pconn->username))
221 && connection_attach_real(pconn, pplayer, FALSE, TRUE)) {
222 /* a player has already been created for this user, reconnect */
224 if (S_S_INITIAL == server_state()) {
225 send_player_info_c(NULL, dest);
227 } else {
228 if (!game_was_started()) {
229 if (connection_attach_real(pconn, NULL, FALSE, TRUE)) {
230 pplayer = conn_get_player(pconn);
231 fc_assert(pplayer != NULL);
232 } else {
233 notify_conn(dest, NULL, E_CONNECTION, ftc_server,
234 _("Couldn't attach your connection to new player."));
235 log_verbose("%s is not attached to a player", pconn->username);
238 send_player_info_c(NULL, dest);
242 send_conn_info(game.est_connections, dest);
244 if (NULL == pplayer) {
245 /* Else this has already been done in connection_attach_real(). */
246 send_pending_events(pconn, TRUE);
247 send_running_votes(pconn, FALSE);
248 restore_access_level(pconn);
249 send_conn_info(dest, game.est_connections);
251 notify_conn(dest, NULL, E_CONNECTION, ftc_server,
252 _("You are logged in as '%s' connected to no player."),
253 pconn->username);
254 } else {
255 notify_conn(dest, NULL, E_CONNECTION, ftc_server,
256 _("You are logged in as '%s' connected to %s."),
257 pconn->username,
258 player_name(pconn->playing));
261 /* Send information about delegation(s). */
262 send_delegation_info(pconn);
264 /* Notify the *other* established connections that you are connected, and
265 * add the info for all in event cache. Note we must to do it after we
266 * sent the pending events to pconn (from this function and also
267 * connection_attach()), otherwise pconn will receive it too. */
268 if (conn_controls_player(pconn)) {
269 package_event(&connect_info, NULL, E_CONNECTION, ftc_server,
270 _("%s has connected from %s (player %s)."),
271 pconn->username, pconn->addr,
272 player_name(conn_get_player(pconn)));
273 } else {
274 package_event(&connect_info, NULL, E_CONNECTION, ftc_server,
275 _("%s has connected from %s."),
276 pconn->username, pconn->addr);
278 conn_list_iterate(game.est_connections, aconn) {
279 if (aconn != pconn) {
280 send_packet_chat_msg(aconn, &connect_info);
282 } conn_list_iterate_end;
283 event_cache_add_for_all(&connect_info);
285 /* if need be, tell who we're waiting on to end the game.info.turn */
286 if (S_S_RUNNING == server_state() && game.server.turnblock) {
287 players_iterate_alive(cplayer) {
288 if (is_human(cplayer)
289 && !cplayer->phase_done
290 && cplayer != pconn->playing) { /* skip current player */
291 notify_conn(dest, NULL, E_CONNECTION, ftc_any,
292 _("Turn-blocking game play: "
293 "waiting on %s to finish turn..."),
294 player_name(cplayer));
296 } players_iterate_alive_end;
299 if (game.info.is_edit_mode) {
300 notify_conn(dest, NULL, E_SETTING, ftc_editor,
301 _(" *** Server is in edit mode. *** "));
304 if (NULL != pplayer) {
305 /* Else, no need to do anything. */
306 reset_all_start_commands(TRUE);
307 (void) send_server_info_to_metaserver(META_INFO);
310 send_current_history_report(pconn->self);
312 conn_compression_thaw(pconn);
315 /**************************************************************************
316 send the rejection packet to the client.
317 **************************************************************************/
318 void reject_new_connection(const char *msg, struct connection *pconn)
320 struct packet_server_join_reply packet;
322 /* zero out the password */
323 memset(pconn->server.password, 0, sizeof(pconn->server.password));
325 packet.you_can_join = FALSE;
326 sz_strlcpy(packet.capability, our_capability);
327 sz_strlcpy(packet.message, msg);
328 packet.challenge_file[0] = '\0';
329 packet.conn_id = -1;
330 send_packet_server_join_reply(pconn, &packet);
331 log_normal(_("Client rejected: %s."), conn_description(pconn));
332 flush_connection_send_buffer_all(pconn);
335 /**************************************************************************
336 Returns FALSE if the clients gets rejected and the connection should be
337 closed. Returns TRUE if the client get accepted.
338 **************************************************************************/
339 bool handle_login_request(struct connection *pconn,
340 struct packet_server_join_req *req)
342 char msg[MAX_LEN_MSG];
343 int kick_time_remaining;
345 if (pconn->established || pconn->server.status != AS_NOT_ESTABLISHED) {
346 /* We read the PACKET_SERVER_JOIN_REQ twice from this connection,
347 * this is probably not a Freeciv client. */
348 return FALSE;
351 log_normal(_("Connection request from %s from %s"),
352 req->username, pconn->addr);
354 /* print server and client capabilities to console */
355 log_normal(_("%s has client version %d.%d.%d%s"),
356 pconn->username, req->major_version, req->minor_version,
357 req->patch_version, req->version_label);
358 log_verbose("Client caps: %s", req->capability);
359 log_verbose("Server caps: %s", our_capability);
360 conn_set_capability(pconn, req->capability);
362 /* Make sure the server has every capability the client needs */
363 if (!has_capabilities(our_capability, req->capability)) {
364 fc_snprintf(msg, sizeof(msg),
365 _("The client is missing a capability that this server needs.\n"
366 "Server version: %d.%d.%d%s Client version: %d.%d.%d%s."
367 " Upgrading may help!"),
368 MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION, VERSION_LABEL,
369 req->major_version, req->minor_version,
370 req->patch_version, req->version_label);
371 reject_new_connection(msg, pconn);
372 log_normal(_("%s was rejected: Mismatched capabilities."),
373 req->username);
374 return FALSE;
377 /* Make sure the client has every capability the server needs */
378 if (!has_capabilities(req->capability, our_capability)) {
379 fc_snprintf(msg, sizeof(msg),
380 _("The server is missing a capability that the client needs.\n"
381 "Server version: %d.%d.%d%s Client version: %d.%d.%d%s."
382 " Upgrading may help!"),
383 MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION, VERSION_LABEL,
384 req->major_version, req->minor_version,
385 req->patch_version, req->version_label);
386 reject_new_connection(msg, pconn);
387 log_normal(_("%s was rejected: Mismatched capabilities."),
388 req->username);
389 return FALSE;
392 remove_leading_trailing_spaces(req->username);
394 /* Name-sanity check: could add more checks? */
395 if (!is_valid_username(req->username)) {
396 fc_snprintf(msg, sizeof(msg), _("Invalid username '%s'"), req->username);
397 reject_new_connection(msg, pconn);
398 log_normal(_("%s was rejected: Invalid name [%s]."),
399 req->username, pconn->addr);
400 return FALSE;
403 if (conn_is_kicked(pconn, &kick_time_remaining)) {
404 fc_snprintf(msg, sizeof(msg), _("You have been kicked from this server "
405 "and cannot reconnect for %d seconds."),
406 kick_time_remaining);
407 reject_new_connection(msg, pconn);
408 log_normal(_("%s was rejected: Connection kicked "
409 "(%d seconds remaining)."),
410 req->username, kick_time_remaining);
411 return FALSE;
414 /* don't allow duplicate logins */
415 conn_list_iterate(game.all_connections, aconn) {
416 if (fc_strcasecmp(req->username, aconn->username) == 0) {
417 fc_snprintf(msg, sizeof(msg), _("'%s' already connected."),
418 req->username);
419 reject_new_connection(msg, pconn);
420 log_normal(_("%s was rejected: Duplicate login name [%s]."),
421 req->username, pconn->addr);
422 return FALSE;
424 } conn_list_iterate_end;
426 /* Remove the ping timeout given in sernet.c:server_make_connection(). */
427 fc_assert_msg(1 == timer_list_size(pconn->server.ping_timers),
428 "Ping timer list size %d, should be 1. Have we sent "
429 "a ping to unestablished connection %s?",
430 timer_list_size(pconn->server.ping_timers),
431 conn_description(pconn));
432 timer_list_pop_front(pconn->server.ping_timers);
434 if (game.server.connectmsg[0] != '\0') {
435 log_debug("Sending connectmsg: %s", game.server.connectmsg);
436 dsend_packet_connect_msg(pconn, game.server.connectmsg);
439 if (srvarg.auth_enabled) {
440 return auth_user(pconn, req->username);
441 } else {
442 sz_strlcpy(pconn->username, req->username);
443 establish_new_connection(pconn);
444 return TRUE;
448 /****************************************************************************
449 High-level server stuff when connection to client is closed or lost.
450 Reports loss to log, and to other players if the connection was a
451 player. Also removes player in pregame, applies auto_toggle, and
452 does check for turn done (since can depend on connection/ai status).
453 Note you shouldn't this function directly. You should use
454 server_break_connection() if you want to close the connection.
455 ****************************************************************************/
456 void lost_connection_to_client(struct connection *pconn)
458 const char *desc = conn_description(pconn);
460 fc_assert_ret(TRUE == pconn->server.is_closing);
462 log_normal(_("Lost connection: %s."), desc);
464 /* Special color (white on black) for player loss */
465 notify_conn(game.est_connections, NULL, E_CONNECTION,
466 conn_controls_player(pconn) ? ftc_player_lost : ftc_server,
467 _("Lost connection: %s."), desc);
469 connection_detach(pconn, TRUE);
470 send_conn_info_remove(pconn->self, game.est_connections);
471 notify_if_first_access_level_is_available();
473 check_for_full_turn_done();
476 /**************************************************************************
477 Fill in packet_conn_info from full connection struct.
478 **************************************************************************/
479 static void package_conn_info(struct connection *pconn,
480 struct packet_conn_info *packet)
482 packet->id = pconn->id;
483 packet->used = pconn->used;
484 packet->established = pconn->established;
485 packet->player_num = (NULL != pconn->playing)
486 ? player_number(pconn->playing)
487 : player_slot_count();
488 packet->observer = pconn->observer;
489 packet->access_level = pconn->access_level;
491 sz_strlcpy(packet->username, pconn->username);
492 sz_strlcpy(packet->addr, pconn->addr);
493 sz_strlcpy(packet->capability, pconn->capability);
496 /**************************************************************************
497 Handle both send_conn_info() and send_conn_info_removed(), depending
498 on 'remove' arg. Sends conn_info packets for 'src' to 'dest', turning
499 off 'used' if 'remove' is specified.
500 **************************************************************************/
501 static void send_conn_info_arg(struct conn_list *src,
502 struct conn_list *dest, bool remove_conn)
504 struct packet_conn_info packet;
506 if (!dest) {
507 dest = game.est_connections;
510 conn_list_iterate(src, psrc) {
511 package_conn_info(psrc, &packet);
512 if (remove_conn) {
513 packet.used = FALSE;
515 lsend_packet_conn_info(dest, &packet);
516 } conn_list_iterate_end;
519 /**************************************************************************
520 Send conn_info packets to tell 'dest' connections all about
521 'src' connections.
522 **************************************************************************/
523 void send_conn_info(struct conn_list *src, struct conn_list *dest)
525 send_conn_info_arg(src, dest, FALSE);
528 /**************************************************************************
529 Like send_conn_info(), but turn off the 'used' bits to tell clients
530 to remove info about these connections instead of adding it.
531 **************************************************************************/
532 void send_conn_info_remove(struct conn_list *src, struct conn_list *dest)
534 send_conn_info_arg(src, dest, TRUE);
537 /**************************************************************************
538 Search for first uncontrolled player
539 **************************************************************************/
540 struct player *find_uncontrolled_player(void)
542 players_iterate(played) {
543 if (!played->is_connected && !played->was_created) {
544 return played;
546 } players_iterate_end;
548 return NULL;
551 /****************************************************************************
552 Setup pconn as a client connected to pplayer or observer:
553 Updates pconn->playing, pplayer->connections, pplayer->is_connected
554 and pconn->observer.
556 - If pplayer is NULL and observing is FALSE: take the next available
557 player that is not connected.
558 - If pplayer is NULL and observing is TRUE: attach this connection to
559 the game as global observer.
560 - If pplayer is not NULL and observing is FALSE: take this player.
561 - If pplayer is not NULL and observing is TRUE: observe this player.
563 Note take_command() needs to know if this function will success before
564 it's time to call this. Keep take_command() checks in sync when
565 modifying this.
566 ****************************************************************************/
567 static bool connection_attach_real(struct connection *pconn,
568 struct player *pplayer,
569 bool observing, bool connecting)
571 fc_assert_ret_val(pconn != NULL, FALSE);
572 fc_assert_ret_val_msg(!pconn->observer && pconn->playing == NULL, FALSE,
573 "connections must be detached with "
574 "connection_detach() before calling this!");
576 if (!observing) {
577 if (NULL == pplayer) {
578 /* search for uncontrolled player */
579 pplayer = find_uncontrolled_player();
581 if (NULL == pplayer) {
582 /* no uncontrolled player found */
583 if (player_count() >= game.server.max_players
584 || normal_player_count() >= server.playable_nations) {
585 return FALSE;
587 /* add new player, or not */
588 /* Should only be called in such a way as to create a new player
589 * in the pregame */
590 fc_assert_ret_val(!game_was_started(), FALSE);
591 pplayer = server_create_player(-1, default_ai_type_name(),
592 NULL, FALSE);
593 /* Pregame => no need to assign_player_colors() */
594 if (!pplayer) {
595 return FALSE;
597 } else {
598 team_remove_player(pplayer);
600 server_player_init(pplayer, FALSE, TRUE);
602 /* Make it human! */
603 set_as_human(pplayer);
606 sz_strlcpy(pplayer->username, pconn->username);
607 pplayer->unassigned_user = FALSE;
608 pplayer->user_turns = 0; /* reset for a new user */
609 pplayer->is_connected = TRUE;
611 if (!game_was_started()) {
612 if (!pplayer->was_created && NULL == pplayer->nation) {
613 /* Temporarily set player_name() to username. */
614 server_player_set_name(pplayer, pconn->username);
616 (void) aifill(game.info.aifill);
619 if (game.server.auto_ai_toggle && !is_human(pplayer)) {
620 toggle_ai_player_direct(NULL, pplayer);
623 send_player_info_c(pplayer, game.est_connections);
625 /* Remove from global observers list, if was there */
626 conn_list_remove(game.glob_observers, pconn);
627 } else if (pplayer == NULL) {
628 /* Global observer */
629 bool already = FALSE;
631 fc_assert(observing);
633 conn_list_iterate(game.glob_observers, pconn2) {
634 if (pconn2 == pconn) {
635 already = TRUE;
636 break;
638 } conn_list_iterate_end;
640 if (!already) {
641 conn_list_append(game.glob_observers, pconn);
645 /* We don't want the connection's username on another player. */
646 players_iterate(aplayer) {
647 if (aplayer != pplayer
648 && 0 == strncmp(aplayer->username, pconn->username, MAX_LEN_NAME)) {
649 sz_strlcpy(aplayer->username, _(ANON_USER_NAME));
650 aplayer->unassigned_user = TRUE;
651 send_player_info_c(aplayer, NULL);
653 } players_iterate_end;
655 pconn->observer = observing;
656 pconn->playing = pplayer;
657 if (pplayer) {
658 conn_list_append(pplayer->connections, pconn);
661 restore_access_level(pconn);
663 /* Reset the delta-state. */
664 send_conn_info(pconn->self, game.est_connections); /* Client side. */
665 conn_reset_delta_state(pconn); /* Server side. */
667 /* Initial packets don't need to be resent. See comment for
668 * connecthand.c::establish_new_connection(). */
669 switch (server_state()) {
670 case S_S_INITIAL:
671 send_pending_events(pconn, connecting);
672 send_running_votes(pconn, !connecting);
673 break;
675 case S_S_RUNNING:
676 conn_compression_freeze(pconn);
677 send_all_info(pconn->self);
678 if (game.info.is_edit_mode && can_conn_edit(pconn)) {
679 edithand_send_initial_packets(pconn->self);
681 conn_compression_thaw(pconn);
682 /* Enter C_S_RUNNING client state. */
683 dsend_packet_start_phase(pconn, game.info.phase);
684 /* Must be after C_S_RUNNING client state to be effective. */
685 send_diplomatic_meetings(pconn);
686 send_pending_events(pconn, connecting);
687 send_running_votes(pconn, !connecting);
688 break;
690 case S_S_OVER:
691 conn_compression_freeze(pconn);
692 send_all_info(pconn->self);
693 if (game.info.is_edit_mode && can_conn_edit(pconn)) {
694 edithand_send_initial_packets(pconn->self);
696 conn_compression_thaw(pconn);
697 report_final_scores(pconn->self);
698 send_pending_events(pconn, connecting);
699 send_running_votes(pconn, !connecting);
700 if (!connecting) {
701 /* Send information about delegation(s). */
702 send_delegation_info(pconn);
704 break;
707 send_updated_vote_totals(NULL);
709 return TRUE;
712 /****************************************************************************
713 Setup pconn as a client connected to pplayer or observer.
714 ****************************************************************************/
715 bool connection_attach(struct connection *pconn, struct player *pplayer,
716 bool observing)
718 return connection_attach_real(pconn, pplayer, observing, FALSE);
721 /****************************************************************************
722 Remove pconn as a client connected to pplayer:
723 Updates pconn->playing, pconn->playing->connections,
724 pconn->playing->is_connected and pconn->observer.
726 pconn remains a member of game.est_connections.
728 If remove_unused_player is TRUE, may remove a player left with no
729 controlling connection (only in pregame, and not if explicitly /created).
730 ****************************************************************************/
731 void connection_detach(struct connection *pconn, bool remove_unused_player)
733 struct player *pplayer;
735 fc_assert_ret(pconn != NULL);
737 if (NULL != (pplayer = pconn->playing)) {
738 bool was_connected = pplayer->is_connected;
740 send_remove_team_votes(pconn);
741 conn_list_remove(pplayer->connections, pconn);
742 pconn->playing = NULL;
743 pconn->observer = FALSE;
744 restore_access_level(pconn);
745 cancel_connection_votes(pconn);
746 send_updated_vote_totals(NULL);
747 send_conn_info(pconn->self, game.est_connections);
749 /* If any other (non-observing) conn is attached to this player, the
750 * player is still connected. */
751 pplayer->is_connected = FALSE;
752 conn_list_iterate(pplayer->connections, aconn) {
753 if (!aconn->observer) {
754 pplayer->is_connected = TRUE;
755 break;
757 } conn_list_iterate_end;
759 if (was_connected && !pplayer->is_connected) {
760 /* Player just lost its controlling connection. */
761 if (remove_unused_player &&
762 !pplayer->was_created && !game_was_started()) {
763 /* Remove player. */
764 conn_list_iterate(pplayer->connections, aconn) {
765 /* Detach all. */
766 fc_assert_action(aconn != pconn, continue);
767 notify_conn(aconn->self, NULL, E_CONNECTION, ftc_server,
768 _("Detaching from %s."), player_name(pplayer));
769 /* Recursive... but shouldn't be a problem, as this can only
770 * be a non-controlling connection so can't get back here. */
771 connection_detach(aconn, TRUE);
772 } conn_list_iterate_end;
774 /* Actually do the removal. */
775 server_remove_player(pplayer);
776 (void) aifill(game.info.aifill);
777 reset_all_start_commands(TRUE);
778 } else {
779 /* Aitoggle the player if no longer connected. */
780 if (game.server.auto_ai_toggle && is_human(pplayer)) {
781 toggle_ai_player_direct(NULL, pplayer);
782 /* send_player_info_c() was formerly updated by
783 * toggle_ai_player_direct(), so it must be safe to send here now?
785 * At other times, data from send_conn_info() is used by the
786 * client to display player information.
787 * See establish_new_connection().
789 log_verbose("connection_detach() calls send_player_info_c()");
790 send_player_info_c(pplayer, NULL);
792 reset_all_start_commands(TRUE);
796 } else {
797 pconn->observer = FALSE;
798 restore_access_level(pconn);
799 send_conn_info(pconn->self, game.est_connections);
803 /*****************************************************************************
804 Use a delegation to get control over another player.
805 *****************************************************************************/
806 bool connection_delegate_take(struct connection *pconn,
807 struct player *dplayer)
809 fc_assert_ret_val(pconn->server.delegation.status == FALSE, FALSE);
811 /* Save the original player of this connection and the original username of
812 * the player. */
813 pconn->server.delegation.status = TRUE;
814 pconn->server.delegation.playing = conn_get_player(pconn);
815 pconn->server.delegation.observer = pconn->observer;
816 if (conn_controls_player(pconn)) {
817 /* Setting orig_username in the player we're about to put aside is
818 * a flag that no-one should be allowed to mess with it (e.g. /take). */
819 struct player *oplayer = conn_get_player(pconn);
821 fc_assert_ret_val(oplayer != dplayer, FALSE);
822 fc_assert_ret_val(strlen(oplayer->server.orig_username) == 0, FALSE);
823 sz_strlcpy(oplayer->server.orig_username, oplayer->username);
825 fc_assert_ret_val(strlen(dplayer->server.orig_username) == 0, FALSE);
826 sz_strlcpy(dplayer->server.orig_username, dplayer->username);
828 /* Detach the current connection. */
829 if (NULL != pconn->playing || pconn->observer) {
830 connection_detach(pconn, FALSE);
833 /* Try to attach to the new player */
834 if (!connection_attach(pconn, dplayer, FALSE)) {
836 /* Restore original connection. */
837 bool success = connection_attach(pconn,
838 pconn->server.delegation.playing,
839 pconn->server.delegation.observer);
840 fc_assert_ret_val(success, FALSE);
842 /* Reset all changes done above. */
843 pconn->server.delegation.status = FALSE;
844 pconn->server.delegation.playing = NULL;
845 pconn->server.delegation.observer = FALSE;
846 if (conn_controls_player(pconn)) {
847 struct player *oplayer = conn_get_player(pconn);
848 oplayer->server.orig_username[0] = '\0';
850 dplayer->server.orig_username[0] = '\0';
852 return FALSE;
855 return TRUE;
858 /*****************************************************************************
859 Restore the original status of a delegate connection pconn after potentially
860 using a delegation. pconn is detached from the delegated player, and
861 reattached to its previous view (e.g. observer), if any.
862 (Reattaching the original user to the delegated player is not handled here.)
863 *****************************************************************************/
864 bool connection_delegate_restore(struct connection *pconn)
866 struct player *dplayer;
868 if (!pconn->server.delegation.status) {
869 return FALSE;
872 if (pconn->server.delegation.playing
873 && !pconn->server.delegation.observer) {
874 /* If restoring to controlling another player, and we're not the
875 * original controller of that player, something's gone wrong. */
876 fc_assert_ret_val(
877 strcmp(pconn->server.delegation.playing->server.orig_username,
878 pconn->username) == 0, FALSE);
881 /* Save the current (delegated) player. */
882 dplayer = conn_get_player(pconn);
884 /* There should be a delegated player connected to pconn. */
885 fc_assert_ret_val(dplayer, FALSE);
887 /* Detach the current (delegate) connection from the delegated player. */
888 if (NULL != pconn->playing || pconn->observer) {
889 connection_detach(pconn, FALSE);
892 /* Try to attach to the delegate's original player */
893 if ((NULL != pconn->server.delegation.playing
894 || pconn->server.delegation.observer)
895 && !connection_attach(pconn, pconn->server.delegation.playing,
896 pconn->server.delegation.observer)) {
897 return FALSE;
900 /* Reset data. */
901 pconn->server.delegation.status = FALSE;
902 pconn->server.delegation.playing = NULL;
903 pconn->server.delegation.observer = FALSE;
904 if (conn_controls_player(pconn) && conn_get_player(pconn) != NULL) {
905 /* Remove flag that we had 'put aside' our original player. */
906 struct player *oplayer = conn_get_player(pconn);
907 fc_assert_ret_val(oplayer != dplayer, FALSE);
908 oplayer->server.orig_username[0] = '\0';
911 /* Restore the username of the original controller in the previously-
912 * delegated player. */
913 sz_strlcpy(dplayer->username, dplayer->server.orig_username);
914 dplayer->server.orig_username[0] = '\0';
915 /* Send updated username to all connections. */
916 send_player_info_c(dplayer, NULL);
918 return TRUE;
921 /*****************************************************************************
922 Close a connection. Use this in the server to take care of delegation stuff
923 (reset the username of the controlled connection).
924 *****************************************************************************/
925 void connection_close_server(struct connection *pconn, const char *reason)
927 /* Restore possible delegations before the connection is closed. */
928 connection_delegate_restore(pconn);
929 connection_close(pconn, reason);