author | unC0Rr |
Sat, 28 Sep 2024 22:27:13 +0200 | |
changeset 16037 | 2b4f361e3891 |
parent 15937 | e514ceb5e7d6 |
permissions | -rw-r--r-- |
15833 | 1 |
use mysql_async::{self, from_row_opt, params, prelude::*, Pool}; |
2 |
use sha1::{Digest, Sha1}; |
|
15937 | 3 |
use tokio::sync::mpsc::{channel, Receiver, Sender}; |
14779
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
4 |
|
15075 | 5 |
use crate::handlers::{AccountInfo, Sha1Digest}; |
14456 | 6 |
|
15103
823052e66611
check for account existence before asking passwords
alfadur
parents:
15075
diff
changeset
|
7 |
const CHECK_ACCOUNT_EXISTS_QUERY: &str = |
823052e66611
check for account existence before asking passwords
alfadur
parents:
15075
diff
changeset
|
8 |
r"SELECT 1 FROM users WHERE users.name = :username LIMIT 1"; |
823052e66611
check for account existence before asking passwords
alfadur
parents:
15075
diff
changeset
|
9 |
|
15517 | 10 |
const GET_ACCOUNT_QUERY: &str = r"SELECT CASE WHEN users.status = 1 THEN users.pass ELSE '' END, |
14779
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
11 |
(SELECT COUNT(users_roles.rid) FROM users_roles WHERE users.uid = users_roles.uid AND users_roles.rid = 3), |
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
12 |
(SELECT COUNT(users_roles.rid) FROM users_roles WHERE users.uid = users_roles.uid AND users_roles.rid = 13) |
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
13 |
FROM users WHERE users.name = :username"; |
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
14 |
|
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
15 |
const STORE_STATS_QUERY: &str = r"INSERT INTO gameserver_stats |
15103
823052e66611
check for account existence before asking passwords
alfadur
parents:
15075
diff
changeset
|
16 |
(players, rooms, last_update) |
823052e66611
check for account existence before asking passwords
alfadur
parents:
15075
diff
changeset
|
17 |
VALUES |
823052e66611
check for account existence before asking passwords
alfadur
parents:
15075
diff
changeset
|
18 |
(:players, :rooms, UNIX_TIMESTAMP())"; |
14456 | 19 |
|
14785
a1077e8d26f4
implement watch message apart from replay deserializing
alfadur
parents:
14779
diff
changeset
|
20 |
const GET_REPLAY_NAME_QUERY: &str = r"SELECT filename FROM achievements WHERE id = :id"; |
a1077e8d26f4
implement watch message apart from replay deserializing
alfadur
parents:
14779
diff
changeset
|
21 |
|
15121 | 22 |
pub struct ServerStatistics { |
14456 | 23 |
rooms: u32, |
24 |
players: u32, |
|
25 |
} |
|
26 |
||
15121 | 27 |
pub struct Achievements {} |
14456 | 28 |
|
15937 | 29 |
pub enum DatabaseQuery { |
30 |
CheckRegistered { |
|
31 |
nick: String, |
|
32 |
}, |
|
33 |
GetAccount { |
|
34 |
nick: String, |
|
35 |
protocol: u16, |
|
36 |
password_hash: String, |
|
37 |
client_salt: String, |
|
38 |
server_salt: String, |
|
39 |
}, |
|
40 |
GetCheckerAccount { |
|
41 |
nick: String, |
|
42 |
password: String, |
|
43 |
}, |
|
44 |
GetReplayFilename { |
|
45 |
id: u32, |
|
46 |
}, |
|
47 |
} |
|
48 |
||
49 |
pub enum DatabaseResponse { |
|
50 |
AccountRegistered(bool), |
|
51 |
Account(Option<AccountInfo>), |
|
52 |
CheckerAccount { is_registered: bool }, |
|
53 |
} |
|
54 |
||
14779
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
55 |
pub struct Database { |
15833 | 56 |
pool: Pool, |
15937 | 57 |
query_rx: Receiver<DatabaseQuery>, |
58 |
response_tx: Sender<DatabaseResponse>, |
|
14456 | 59 |
} |
60 |
||
61 |
impl Database { |
|
15833 | 62 |
pub fn new(url: &str) -> Self { |
15937 | 63 |
let (query_tx, query_rx) = channel(32); |
64 |
let (response_tx, response_rx) = channel(32); |
|
15833 | 65 |
Self { |
66 |
pool: Pool::new(url), |
|
15937 | 67 |
query_rx, |
68 |
response_tx, |
|
69 |
} |
|
70 |
} |
|
71 |
||
72 |
pub async fn run(&mut self) { |
|
73 |
use DatabaseResponse::*; |
|
74 |
loop { |
|
75 |
let query = self.query_rx.recv().await; |
|
76 |
if let Some(query) = query { |
|
77 |
match query { |
|
78 |
DatabaseQuery::CheckRegistered { nick } => { |
|
79 |
let is_registered = self.get_is_registered(&nick).await.unwrap_or(false); |
|
80 |
self.response_tx |
|
81 |
.send(AccountRegistered(is_registered)) |
|
82 |
.await; |
|
83 |
} |
|
84 |
DatabaseQuery::GetAccount { |
|
85 |
nick, |
|
86 |
protocol, |
|
87 |
password_hash, |
|
88 |
client_salt, |
|
89 |
server_salt, |
|
90 |
} => { |
|
91 |
let account = self |
|
92 |
.get_account( |
|
93 |
&nick, |
|
94 |
protocol, |
|
95 |
&password_hash, |
|
96 |
&client_salt, |
|
97 |
&server_salt, |
|
98 |
) |
|
99 |
.await |
|
100 |
.unwrap_or(None); |
|
101 |
self.response_tx.send(Account(account)).await; |
|
102 |
} |
|
103 |
DatabaseQuery::GetCheckerAccount { nick, password } => { |
|
104 |
let is_registered = self |
|
105 |
.get_checker_account(&nick, &password) |
|
106 |
.await |
|
107 |
.unwrap_or(false); |
|
108 |
self.response_tx |
|
109 |
.send(CheckerAccount { is_registered }) |
|
110 |
.await; |
|
111 |
} |
|
112 |
DatabaseQuery::GetReplayFilename { id } => { |
|
113 |
let filename = self.get_replay_name(id).await; |
|
114 |
} |
|
115 |
}; |
|
116 |
} else { |
|
117 |
break; |
|
118 |
} |
|
15103
823052e66611
check for account existence before asking passwords
alfadur
parents:
15075
diff
changeset
|
119 |
} |
823052e66611
check for account existence before asking passwords
alfadur
parents:
15075
diff
changeset
|
120 |
} |
823052e66611
check for account existence before asking passwords
alfadur
parents:
15075
diff
changeset
|
121 |
|
15833 | 122 |
pub async fn get_is_registered(&mut self, nick: &str) -> mysql_async::Result<bool> { |
123 |
let mut connection = self.pool.get_conn().await?; |
|
124 |
let result = CHECK_ACCOUNT_EXISTS_QUERY |
|
125 |
.with(params! { "username" => nick }) |
|
15937 | 126 |
.first::<u32, _>(&mut connection) |
15833 | 127 |
.await?; |
15937 | 128 |
Ok(!result.is_some()) |
15833 | 129 |
} |
130 |
||
131 |
pub async fn get_account( |
|
14779
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
132 |
&mut self, |
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
133 |
nick: &str, |
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
134 |
protocol: u16, |
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
135 |
password_hash: &str, |
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
136 |
client_salt: &str, |
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
137 |
server_salt: &str, |
15833 | 138 |
) -> mysql_async::Result<Option<AccountInfo>> { |
139 |
let mut connection = self.pool.get_conn().await?; |
|
140 |
if let Some((mut password, is_admin, is_contributor)) = GET_ACCOUNT_QUERY |
|
141 |
.with(params! { "username" => nick }) |
|
142 |
.first::<(String, i32, i32), _>(&mut connection) |
|
143 |
.await? |
|
144 |
{ |
|
145 |
let client_hash = get_hash(protocol, &password, &client_salt, &server_salt); |
|
146 |
let server_hash = get_hash(protocol, &password, &server_salt, &client_salt); |
|
147 |
password.replace_range(.., "🦔🦔🦔🦔🦔🦔🦔🦔"); |
|
14779
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
148 |
|
15833 | 149 |
if client_hash == password_hash { |
150 |
Ok(Some(AccountInfo { |
|
151 |
is_registered: true, |
|
152 |
is_admin: is_admin == 1, |
|
153 |
is_contributor: is_contributor == 1, |
|
154 |
server_hash, |
|
155 |
})) |
|
14779
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
156 |
} else { |
15110 | 157 |
Ok(None) |
14779
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
158 |
} |
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
159 |
} else { |
15833 | 160 |
Ok(None) |
14456 | 161 |
} |
162 |
} |
|
163 |
||
15833 | 164 |
pub async fn get_checker_account( |
15532 | 165 |
&mut self, |
166 |
nick: &str, |
|
167 |
checker_password: &str, |
|
15833 | 168 |
) -> mysql_async::Result<bool> { |
169 |
let mut connection = self.pool.get_conn().await?; |
|
170 |
if let Some((password, _, _)) = GET_ACCOUNT_QUERY |
|
171 |
.with(params! { "username" => nick }) |
|
172 |
.first::<(String, i32, i32), _>(&mut connection) |
|
173 |
.await? |
|
174 |
{ |
|
175 |
Ok(checker_password == password) |
|
15532 | 176 |
} else { |
15833 | 177 |
Ok(false) |
15532 | 178 |
} |
179 |
} |
|
180 |
||
15833 | 181 |
pub async fn store_stats(&mut self, stats: &ServerStatistics) -> mysql_async::Result<()> { |
182 |
let mut connection = self.pool.get_conn().await?; |
|
183 |
STORE_STATS_QUERY |
|
184 |
.with(params! { |
|
185 |
"players" => stats.players, |
|
186 |
"rooms" => stats.rooms, |
|
187 |
}) |
|
188 |
.ignore(&mut connection) |
|
189 |
.await |
|
14456 | 190 |
} |
191 |
||
15848 | 192 |
pub async fn store_achievements( |
193 |
&mut self, |
|
194 |
achievements: &Achievements, |
|
195 |
) -> mysql_async::Result<()> { |
|
14456 | 196 |
Ok(()) |
197 |
} |
|
198 |
||
15833 | 199 |
pub async fn get_replay_name(&mut self, replay_id: u32) -> mysql_async::Result<Option<String>> { |
200 |
let mut connection = self.pool.get_conn().await?; |
|
201 |
GET_REPLAY_NAME_QUERY |
|
202 |
.with(params! { "id" => replay_id }) |
|
203 |
.first::<String, _>(&mut connection) |
|
204 |
.await |
|
14456 | 205 |
} |
206 |
} |
|
14779
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
207 |
|
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
208 |
fn get_hash(protocol_number: u16, web_password: &str, salt1: &str, salt2: &str) -> Sha1Digest { |
15833 | 209 |
let data = format!( |
14779
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
210 |
"{}{}{}{}{}", |
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
211 |
salt1, salt2, web_password, protocol_number, "!hedgewars" |
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
212 |
); |
15833 | 213 |
|
214 |
let mut sha1 = Sha1::new(); |
|
215 |
sha1.update(&data); |
|
216 |
Sha1Digest::new(sha1.finalize().try_into().unwrap()) |
|
14779
f43ab2bd76ae
add a thread for internal server IO and implement account checking with it
alfadur
parents:
14457
diff
changeset
|
217 |
} |