diff --git a/Makefile b/Makefile index 2f3c728f..1e30dac2 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,8 @@ OBJS = swad_account.o swad_action.o swad_agenda.o swad_alert.o \ swad_department.o swad_duplicate.o \ swad_enrolment.o swad_exam.o \ swad_figure.o swad_file.o swad_file_browser.o swad_file_extension.o \ - swad_file_MIME.o swad_follow.o swad_form.o swad_forum.o \ + swad_file_MIME.o swad_firewall.o swad_follow.o swad_form.o \ + swad_forum.o \ swad_game.o swad_global.o swad_group.o \ swad_help.o swad_hierarchy.o swad_holiday.o \ swad_icon.o swad_ID.o swad_image.o swad_indicator.o swad_info.o \ diff --git a/sql/cambios.sql b/sql/cambios.sql index 09e27ac7..ee89bb4e 100644 --- a/sql/cambios.sql +++ b/sql/cambios.sql @@ -12593,19 +12593,8 @@ ALTER TABLE ws_keys ENGINE=MyISAM; OPTIMIZE TABLE ws_keys; - - - - - - - - - - - - - - - SELECT Weekday,TIME_TO_SEC(StartTime) AS S,TIME_TO_SEC(Duration) AS D,Place,ClassType,GrpCod FROM timetable_crs WHERE CrsCod=19 ORDER BY Weekday,S,ClassType,GrpCod,Place,D DESC; + + +CREATE TABLE IF NOT EXISTS firewall (ClickTime DATETIME NOT NULL,IP CHAR(15) NOT NULL,INDEX(ClickTime),INDEX(IP)); + diff --git a/sql/swad.sql b/sql/swad.sql index 28c3a122..f091ad32 100644 --- a/sql/swad.sql +++ b/sql/swad.sql @@ -522,6 +522,14 @@ CREATE TABLE IF NOT EXISTS files ( INDEX(ZoneUsrCod), INDEX(PublisherUsrCod)); -- +-- Table firewall: stores the most recent IPs in order to mitigate denial of service attacks +-- +CREATE TABLE IF NOT EXISTS firewall ( + ClickTime DATETIME NOT NULL, + IP CHAR(15) NOT NULL, + INDEX(ClickTime), + INDEX(IP)); +-- -- Table forum_disabled_post: stores the forum post that have been disabled -- CREATE TABLE IF NOT EXISTS forum_disabled_post ( diff --git a/swad_agenda.c b/swad_agenda.c index 60a11657..0135a398 100644 --- a/swad_agenda.c +++ b/swad_agenda.c @@ -1125,7 +1125,7 @@ static void Agd_GetDataOfEventByCod (struct AgendaEvent *AgdEvent) "SELECT AgdCod,Public,Hidden," "UNIX_TIMESTAMP(StartTime)," "UNIX_TIMESTAMP(EndTime)," - "NOW()>EndTime," // Past event? + "NOW()>EndTime," // Past event? "NOW() DESCRIBE files; "INDEX(ZoneUsrCod)," "INDEX(PublisherUsrCod))"); + /***** Table firewall *****/ +/* +mysql> DESCRIBE firewall; ++-----------+----------+------+-----+---------+-------+ +| Field | Type | Null | Key | Default | Extra | ++-----------+----------+------+-----+---------+-------+ +| ClickTime | datetime | NO | MUL | NULL | | +| IP | char(15) | NO | MUL | NULL | | ++-----------+----------+------+-----+---------+-------+ +2 rows in set (0.00 sec) +*/ + DB_CreateTable ("CREATE TABLE IF NOT EXISTS firewall (" + "ClickTime DATETIME NOT NULL," + "IP CHAR(15) NOT NULL," // Cns_MAX_BYTES_IP + "INDEX(ClickTime)," + "INDEX(IP))"); + /***** Table forum_disabled_post *****/ /* mysql> DESCRIBE forum_disabled_post; @@ -1543,7 +1560,7 @@ mysql> DESCRIBE log_full; "ClickTime DATETIME NOT NULL," "TimeToGenerate INT NOT NULL," "TimeToSend INT NOT NULL," - "IP CHAR(15) NOT NULL," // Cns_MAX_CHARS_IP + "IP CHAR(15) NOT NULL," // Cns_MAX_BYTES_IP "UNIQUE INDEX(LogCod)," "INDEX(ActCod)," "INDEX(CtyCod)," @@ -1590,7 +1607,7 @@ mysql> DESCRIBE log_recent; "ClickTime DATETIME NOT NULL," "TimeToGenerate INT NOT NULL," "TimeToSend INT NOT NULL," - "IP CHAR(15) NOT NULL," // Cns_MAX_CHARS_IP + "IP CHAR(15) NOT NULL," // Cns_MAX_BYTES_IP "UNIQUE INDEX(LogCod)," "INDEX(ActCod)," "INDEX(CtyCod)," diff --git a/swad_enrolment.c b/swad_enrolment.c index 29b29e68..24205a07 100644 --- a/swad_enrolment.c +++ b/swad_enrolment.c @@ -862,7 +862,7 @@ void Enr_RemoveOldUsrs (void) "SELECT UsrCod FROM" "(" "SELECT UsrCod FROM usr_last WHERE" - " LastTime. +*/ +/*****************************************************************************/ +/********************************* Headers ***********************************/ +/*****************************************************************************/ + +#include "swad_database.h" +#include "swad_global.h" + +/*****************************************************************************/ +/************** External global variables from others modules ****************/ +/*****************************************************************************/ + +extern struct Globals Gbl; + +/*****************************************************************************/ +/***************************** Private constants *****************************/ +/*****************************************************************************/ + +#define Fw_CHECK_INTERVAL ((time_t)(10UL)) // Check clicks in the last 10 seconds +#define Fw_MAX_CLICKS_IN_INTERVAL 30 // Maximum of 30 clicks allowed in 10 seconds + +#define Fw_TIME_TO_DELETE_OLD_CLICKS Fw_CHECK_INTERVAL // Remove clicks older than these seconds + +/*****************************************************************************/ +/******************************* Private types *******************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/***************************** Internal prototypes ***************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/************************** Log access into firewall *************************/ +/*****************************************************************************/ + +void FW_LogAccess (void) + { + /***** Log access in firewall recent log *****/ + DB_QueryINSERT ("can not log access into firewall", + "INSERT INTO firewall (ClickTime,IP) VALUES (NOW(),'%s')", + Gbl.IP); + } + +/*****************************************************************************/ +/************************** Log access into firewall *************************/ +/*****************************************************************************/ + +void FW_CheckFirewallAndExitIfTooManyRequests (void) + { + unsigned long NumClicks; + + /***** Get number of clicks from database *****/ + NumClicks = DB_QueryCOUNT ("can not check firewall", + "SELECT COUNT(*) FROM firewall" + " WHERE IP='%s'" + " AND ClickTime>FROM_UNIXTIME(UNIX_TIMESTAMP()-%lu)", + Gbl.IP, + Fw_CHECK_INTERVAL); + + /***** Exit with status 429 if too many connections *****/ + /* RFC 6585 suggests "429 Too Many Requests", according to + https://stackoverflow.com/questions/46664695/whats-the-correct-http-response-code-to-return-for-denial-of-service-dos-atta + https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 */ + if (NumClicks > Fw_MAX_CLICKS_IN_INTERVAL) + { + /* Return status 429 Too Many Requests */ + fprintf (stdout,"Content-Type: text/html; charset=windows-1252\n" + "Retry-After: 3600\n" + "Status: 429\r\n\r\n" + "" + "" + "Too Many Requests" + "" + "" + "

Please stop that

" + "" + "\n"); + + /* Close database connection and exit */ + DB_CloseDBConnection (); + exit (0); + } + } + +/*****************************************************************************/ +/********************** Remove old clicks from firewall **********************/ +/*****************************************************************************/ + +void FW_PurgeFirewall (void) + { + /***** Remove old clicks *****/ + DB_QueryDELETE ("can not purge firewall", + "DELETE LOW_PRIORITY FROM firewall" + " WHERE ClickTime. +*/ +/*****************************************************************************/ +/********************************* Headers ***********************************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/************************** Public types and constants ***********************/ +/*****************************************************************************/ + +/*****************************************************************************/ +/***************************** Public prototypes *****************************/ +/*****************************************************************************/ + +void FW_LogAccess (void); +void FW_CheckFirewallAndExitIfTooManyRequests (void); +void FW_PurgeFirewall (void); + +#endif diff --git a/swad_forum.c b/swad_forum.c index 8f7c1ba4..cd7c8ba6 100644 --- a/swad_forum.c +++ b/swad_forum.c @@ -4557,7 +4557,7 @@ static void For_RemoveExpiredThrsClipboards (void) /***** Remove all expired clipboards *****/ DB_QueryDELETE ("can not remove old threads from clipboards", "DELETE LOW_PRIORITY FROM forum_thr_clip" - " WHERE TimeInsert 0; // Right column visible && There is a course selected - // Sometimes, someone must do this work, so who best than processes that refresh via AJAX? + /***** Sometimes, someone must do this work, + so who best than processes that refresh via AJAX? *****/ if (!(Gbl.PID % 11)) // Do this only one of 11 times ( 11 is prime) Ntf_SendPendingNotifByEMailToAllUsrs (); // Send pending notifications by email + else if (!(Gbl.PID % 19)) // Do this only one of 19 times ( 19 is prime) + FW_PurgeFirewall (); else if (!(Gbl.PID % 1013)) // Do this only one of 1013 times (1013 is prime) Brw_RemoveExpiredExpandedFolders (); // Remove old expanded folders (from all users) else if (!(Gbl.PID % 1019)) // Do this only one of 1019 times (1019 is prime) @@ -1415,7 +1419,7 @@ void Lay_RefreshNotifsAndConnected (void) else if (!(Gbl.PID % 1021)) // Do this only one of 1021 times (1021 is prime) Sta_RemoveOldEntriesRecentLog (); // Remove old entries in recent log table, it's a slow query - // Send, before the HTML, the refresh time + /***** Send, before the HTML, the refresh time *****/ fprintf (Gbl.F.Out,"%lu|",Gbl.Usrs.Connected.TimeToRefreshInMs); if (Gbl.Usrs.Me.Logged) Ntf_WriteNumberOfNewNtfs (); diff --git a/swad_mail.c b/swad_mail.c index 0cc477e3..e2c38452 100644 --- a/swad_mail.c +++ b/swad_mail.c @@ -1774,8 +1774,8 @@ static void Mai_InsertMailKey (const char Email[Cns_MAX_BYTES_EMAIL_ADDRESS + 1] { /***** Remove expired pending emails from database *****/ DB_QueryDELETE ("can not remove old pending mail keys", - "DELETE FROM pending_emails" - " WHERE DateAndTime0" " AND (Status & %u)=0" " AND (Status & %u)=0", @@ -1578,7 +1578,7 @@ void Ntf_SendPendingNotifByEMailToAllUsrs (void) /***** Delete old notifications ******/ DB_QueryDELETE ("can not remove old notifications", "DELETE LOW_PRIORITY FROM notif" - " WHERE TimeNotifLastTime+INTERVAL 1 SECOND" " AND" - " LastRefresh // For log10, floor, ceil, modf, sqrt... +#include // For getenv, malloc #include // For string functions #include "swad_box.h" @@ -274,7 +275,7 @@ void Sta_RemoveOldEntriesRecentLog (void) /***** Remove all expired clipboards *****/ DB_QueryDELETE ("can not remove old entries from recent log", "DELETE LOW_PRIORITY FROM log_recent" - " WHERE ClickTime