//////////// //SECURITY// //////////// //debugging, uncomment for viewing topic calls //#define TOPIC_DEBUGGING 1 #define TOPIC_SPAM_DELAY 2 //2 ticks is about 2/10ths of a second; it was 4 ticks, but that caused too many clicks to be lost due to lag #define UPLOAD_LIMIT 10485760 //Restricts client uploads to the server to 10MB //Boosted this thing. What's the worst that can happen? #define MIN_CLIENT_VERSION 513 // Minimum byond major version required to play. //I would just like the code ready should it ever need to be used. #define SUGGESTED_CLIENT_VERSION 514 // only integers (e.g: 513, 514) are useful here. This is the part BEFORE the ".", IE 513 out of 513.1542 #define SUGGESTED_CLIENT_BUILD 1566 // only integers (e.g: 1542, 1543) are useful here. This is the part AFTER the ".", IE 1542 out of 513.1542 #define SSD_WARNING_TIMER 30 // cycles, not seconds, so 30=60s #define LIMITER_SIZE 5 #define CURRENT_SECOND 1 #define SECOND_COUNT 2 #define CURRENT_MINUTE 3 #define MINUTE_COUNT 4 #define ADMINSWARNED_AT 5 /* When somebody clicks a link in game, this Topic is called first. It does the stuff in this proc and then is redirected to the Topic() proc for the src=[0xWhatever] (if specified in the link). ie locate(hsrc).Topic() Such links can be spoofed. Because of this certain things MUST be considered whenever adding a Topic() for something: - Can it be fed harmful values which could cause runtimes? - Is the Topic call an admin-only thing? - If so, does it have checks to see if the person who called it (usr.client) is an admin? - Are the processes being called by Topic() particularly laggy? - If so, is there any protection against somebody spam-clicking a link? If you have any questions about this stuff feel free to ask. ~Carn */ /client/Topic(href, href_list, hsrc) if(!usr || usr != mob) //stops us calling Topic for somebody else's client. Also helps prevent usr=null return // src should always be a UID; if it isn't, warn instead of failing entirely if(href_list["src"]) hsrc = locateUID(href_list["src"]) // If there's a ]_ in the src, it's a UID, so don't try to locate it if(!hsrc && !findtext(href_list["src"], "]_")) hsrc = locate(href_list["src"]) if(hsrc) var/hsrc_info = datum_info_line(hsrc) || "[hsrc]" stack_trace("Got \\ref-based src in topic from [src] for [hsrc_info], should be UID: [href]") // asset_cache var/asset_cache_job if(href_list["asset_cache_confirm_arrival"]) asset_cache_job = asset_cache_confirm_arrival(href_list["asset_cache_confirm_arrival"]) if(!asset_cache_job) return // Rate limiting var/mtl = 150 // 150 topics per minute if(!holder) // Admins are allowed to spam click, deal with it. var/minute = round(world.time, 600) if(!topiclimiter) topiclimiter = new(LIMITER_SIZE) if(minute != topiclimiter[CURRENT_MINUTE]) topiclimiter[CURRENT_MINUTE] = minute topiclimiter[MINUTE_COUNT] = 0 topiclimiter[MINUTE_COUNT] += 1 if(topiclimiter[MINUTE_COUNT] > mtl) var/msg = "Your previous action was ignored because you've done too many in a minute." if(minute != topiclimiter[ADMINSWARNED_AT]) //only one admin message per-minute. (if they spam the admins can just boot/ban them) topiclimiter[ADMINSWARNED_AT] = minute msg += " Administrators have been informed." log_game("[key_name(src)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute") message_admins("[ADMIN_LOOKUPFLW(usr)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute") to_chat(src, "[msg]") return var/stl = 10 // 10 topics a second if(!holder) // Admins are allowed to spam click, deal with it. var/second = round(world.time, 10) if(!topiclimiter) topiclimiter = new(LIMITER_SIZE) if(second != topiclimiter[CURRENT_SECOND]) topiclimiter[CURRENT_SECOND] = second topiclimiter[SECOND_COUNT] = 0 topiclimiter[SECOND_COUNT] += 1 if(topiclimiter[SECOND_COUNT] > stl) to_chat(src, "Your previous action was ignored because you've done too many in a second") return //search the href for script injection if(findtext(href, "", "border=0;titlebar=0;size=1x1") to_chat(src, "You will be automatically taken to the game, if not, click here to be taken manually. Except you can't, since the chat window doesn't exist yet.") /client/proc/is_afk(duration = 5 MINUTES) if(inactivity > duration) return inactivity return 0 //Send resources to the client. /client/proc/send_resources() // Change the way they should download resources. if(length(GLOB.configuration.url.rsc_urls)) preload_rsc = pick(GLOB.configuration.url.rsc_urls) else preload_rsc = 1 // If config.resource_urls is not set, preload like normal. spawn (10) //removing this spawn causes all clients to not get verbs. //load info on what assets the client has src << browse('code/modules/asset_cache/validate_assets.html', "window=asset_cache_browser") //Precache the client with all other assets slowly, so as to not block other browse() calls if(GLOB.configuration.asset_cache.asset_simple_preload) addtimer(CALLBACK(SSassets.transport, TYPE_PROC_REF(/datum/asset_transport, send_assets_slow), src, SSassets.transport.preload), 5 SECONDS) //For debugging purposes /client/proc/list_all_languages() for(var/L in GLOB.all_languages) var/datum/language/lang = GLOB.all_languages[L] var/message = "[lang.name] : [lang.type]" if(lang.flags & RESTRICTED) message += " (RESTRICTED)" to_chat(world, "[message]") /client/proc/colour_transition(list/colour_to = null, time = 10) //Call this with no parameters to reset to default. animate(src, color = colour_to, time = time, easing = SINE_EASING) /client/proc/on_varedit() var_edited = TRUE /client/proc/generate_clickcatcher() if(!void) void = new() screen += void /client/proc/apply_clickcatcher() generate_clickcatcher() var/list/actualview = getviewsize(view) void.UpdateGreed(actualview[1],actualview[2]) /client/proc/send_ssd_warning(mob/M) if(!GLOB.configuration.general.ssd_warning) return FALSE if(ssd_warning_acknowledged) return FALSE if(M?.player_logged < SSD_WARNING_TIMER) return FALSE to_chat(src, "Are you taking this person to cryo or giving them medical treatment? If you are, confirm that and proceed. Interacting with SSD players in other ways is against server rules unless you've ahelped first for permission.") return TRUE #undef SSD_WARNING_TIMER // /client/verb/resend_ui_resources() set name = "Reload UI Resources" set desc = "Reload your UI assets if they are not working" set category = "Special Verbs" if(last_ui_resource_send > world.time) to_chat(usr, "You requested your UI resource files too quickly. Please try again in [(last_ui_resource_send - world.time)/10] seconds.") return var/choice = alert(usr, "This will reload your TGUI resources. If you have any open UIs, they will be closed. Are you sure?", "Resource Reloading", "Yes", "No") if(choice == "Yes") // 600 deciseconds = 1 minute last_ui_resource_send = world.time + 60 SECONDS // Close their open UIs SStgui.close_user_uis(usr) // Clear the user's cache so they get resent. // This is not fully clearing their BYOND cache, just their assets sent from the server this round sent_assets = list() to_chat(usr, "UI resource files resent successfully. If you are still having issues, please try manually clearing your BYOND cache. This can be achieved by opening your BYOND launcher, pressing the cog in the top right, selecting preferences, going to the Games tab, and pressing 'Clear Cache'.") /** * Retrieves the BYOND accounts data from the BYOND servers * * Makes a web request to byond.com to retrieve the details for the BYOND account associated with the clients ckey. * Returns the data in a parsed, associative list */ /client/proc/retrieve_byondacc_data() // Do not refactor this to use SShttp, because that requires the subsystem to be firing for requests to be made, and this will be triggered before the MC has finished loading var/list/http[] = HTTPGet("http://www.byond.com/members/[ckey]?format=text") if(http) var/status = text2num(http["STATUS"]) if(status == 200) // This is wrapped in try/catch because lummox could change the format on any day without informing anyone try var/list/lines = splittext(http["CONTENT"], "\n") var/list/initial_data = list() var/current_index = "" for(var/L in lines) if(L == "") continue if(!findtext(L, "\t")) current_index = L initial_data[current_index] = list() continue initial_data[current_index] += replacetext(replacetext(L, "\t", ""), "\"", "") var/list/parsed_data = list() for(var/key in initial_data) var/inner_list = list() for(var/entry in initial_data[key]) var/list/split = splittext(entry, " = ") var/inner_key = split[1] var/inner_value = split[2] inner_list[inner_key] = inner_value parsed_data[key] = inner_list // Main return is here return parsed_data catch log_debug("Error parsing byond.com data for [ckey]. Please inform maintainers.") return null else log_debug("Error retrieving data from byond.com for [ckey]. Invalid status code (Expected: 200 | Got: [status]).") return null else log_debug("Failed to retrieve data from byond.com for [ckey]. Connection failed.") return null /** * Sets the clients BYOND date up properly * * If the client does not have a saved BYOND account creation date, retrieve it from the website * If they do have a saved date, use that from the DB, because this value will never change * Arguments: * * notify - Do we notify admins of this new accounts date */ /client/proc/get_byond_account_date(notify = FALSE) // First we see if the client has a saved date in the DB var/datum/db_query/query_date = SSdbcore.NewQuery("SELECT byond_date, DATEDIFF(Now(), byond_date) FROM player WHERE ckey=:ckey", list( "ckey" = ckey )) if(!query_date.warn_execute()) qdel(query_date) return while(query_date.NextRow()) byondacc_date = query_date.item[1] byondacc_age = max(text2num(query_date.item[2]), 0) // Ensure account isnt negative days old qdel(query_date) // They have a date already, lets bail if(byondacc_date) return // They dont have a date, lets grab one var/list/byond_data = retrieve_byondacc_data() if(isnull(byond_data) || !(byond_data["general"]["joined"])) log_debug("Failed to retrieve an account creation date for [ckey].") return byondacc_date = byond_data["general"]["joined"] // Now save it var/datum/db_query/query_update = SSdbcore.NewQuery("UPDATE player SET byond_date=:date WHERE ckey=:ckey", list( "date" = byondacc_date, "ckey" = ckey )) if(!query_update.warn_execute()) qdel(query_update) return qdel(query_update) // Now retrieve the age again because BYOND doesnt have native methods for this var/datum/db_query/query_age = SSdbcore.NewQuery("SELECT DATEDIFF(Now(), byond_date) FROM player WHERE ckey=:ckey", list( "ckey" = ckey )) if(!query_age.warn_execute()) qdel(query_age) return while(query_age.NextRow()) byondacc_age = max(text2num(query_age.item[1]), 0) // Ensure account isnt negative days old qdel(query_age) // Notify admins on new clients connecting, if the byond account age is less than a config value if(notify && (byondacc_age < GLOB.configuration.general.byond_account_age_threshold)) message_admins("[key] has just connected for the first time. BYOND account registered on [byondacc_date] ([byondacc_age] days old)") /client/proc/show_update_notice() to_chat(src, "Your BYOND client (v: [byond_version].[byond_build]) is out of date. This can cause glitches. We highly suggest you download the latest client from byond.com before playing. You can also update via the BYOND launcher application.") /client/proc/update_ambience_pref() if(prefs.sound & SOUND_AMBIENCE) if(SSambience.ambience_listening_clients[src] > world.time) return // If already properly set we don't want to reset the timer. SSambience.ambience_listening_clients[src] = world.time + 10 SECONDS //Just wait 10 seconds before the next one aight mate? cheers. else SSambience.ambience_listening_clients -= src /client/proc/try_localhost_autoadmin() if(GLOB.configuration.admin.enable_localhost_autoadmin && is_connecting_from_localhost()) return new /datum/admins("!LOCALHOST!", R_HOST, ckey) // Verb scoped to the client level so its ALWAYS available /client/verb/open_tos() set category = "OOC" set name = "Terms of Service" var/output = GLOB.join_tos output += "
By withdrawing your consent, you acknowledge that you will be instantaneously kicked from the server and will have to re-accept the Terms of Service. If you do not wish to withdraw your consent at this moment, feel free to close this window.
" output += "" var/datum/browser/popup = new(src, "privacy_consent", "