]> jfr.im git - irc/freenode/solanum.git/blob - modules/m_challenge.c
add ConfigFileEntry.oper_secure_only, to require TLS to oper up (#76)
[irc/freenode/solanum.git] / modules / m_challenge.c
1 /*
2 * ircd-ratbox: A slightly useful ircd.
3 * m_challenge.c: Allows an IRC Operator to securely authenticate.
4 *
5 * Copyright (C) 1990 Jarkko Oikarinen and University of Oulu, Co Center
6 * Copyright (C) 1996-2002 Hybrid Development Team
7 * Copyright (C) 2002-2005 ircd-ratbox development team
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
22 * USA
23 */
24
25 #include "stdinc.h"
26
27 #ifdef HAVE_LIBCRYPTO
28 #include <openssl/pem.h>
29 #include <openssl/rand.h>
30 #include <openssl/rsa.h>
31 #include <openssl/md5.h>
32 #include <openssl/bn.h>
33 #include <openssl/evp.h>
34 #include <openssl/err.h>
35 #endif
36
37 #include "client.h"
38 #include "ircd.h"
39 #include "modules.h"
40 #include "numeric.h"
41 #include "send.h"
42 #include "s_conf.h"
43 #include "msg.h"
44 #include "parse.h"
45 #include "match.h"
46 #include "logger.h"
47 #include "s_user.h"
48 #include "cache.h"
49 #include "s_newconf.h"
50
51 #define CHALLENGE_WIDTH BUFSIZE - (NICKLEN + HOSTLEN + 12)
52 #define CHALLENGE_EXPIRES 180 /* 180 seconds should be more than long enough */
53 #define CHALLENGE_SECRET_LENGTH 128 /* how long our challenge secret should be */
54
55 #ifndef HAVE_LIBCRYPTO
56
57 static const char challenge_desc[] = "Does nothing as OpenSSL was not enabled.";
58
59 /* Maybe this should be an error or something?-davidt */
60 /* now it is -larne */
61 static int challenge_load(void)
62 {
63 sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
64 "Challenge module not loaded because OpenSSL is not available.");
65 ilog(L_MAIN, "Challenge module not loaded because OpenSSL is not available.");
66 return -1;
67 }
68
69 DECLARE_MODULE_AV2(challenge, challenge_load, NULL, NULL, NULL, NULL, NULL, NULL, challenge_desc);
70 #else
71
72 static const char challenge_desc[] =
73 "Provides the challenge-response facility used for becoming an IRC operator";
74
75 static void m_challenge(struct MsgBuf *, struct Client *, struct Client *, int, const char **);
76
77 /* We have openssl support, so include /CHALLENGE */
78 struct Message challenge_msgtab = {
79 "CHALLENGE", 0, 0, 0, 0,
80 {mg_unreg, {m_challenge, 2}, mg_ignore, mg_ignore, mg_ignore, {m_challenge, 2}}
81 };
82
83 mapi_clist_av1 challenge_clist[] = { &challenge_msgtab, NULL };
84
85 DECLARE_MODULE_AV2(challenge, NULL, NULL, challenge_clist, NULL, NULL, NULL, NULL, challenge_desc);
86
87 static bool generate_challenge(char **r_challenge, char **r_response, RSA * key);
88
89 static void
90 cleanup_challenge(struct Client *target_p)
91 {
92 if(target_p->localClient == NULL)
93 return;
94
95 rb_free(target_p->localClient->challenge);
96 rb_free(target_p->user->opername);
97 target_p->localClient->challenge = NULL;
98 target_p->user->opername = NULL;
99 target_p->localClient->chal_time = 0;
100 }
101
102 /*
103 * m_challenge - generate RSA challenge for wouldbe oper
104 * parv[1] = operator to challenge for, or +response
105 */
106 static void
107 m_challenge(struct MsgBuf *msgbuf_p, struct Client *client_p, struct Client *source_p, int parc, const char *parv[])
108 {
109 struct oper_conf *oper_p;
110 char *challenge = NULL; /* to placate gcc */
111 char chal_line[CHALLENGE_WIDTH];
112 unsigned char *b_response;
113 size_t cnt;
114 int len = 0;
115
116 if (ConfigFileEntry.oper_secure_only && !IsSecureClient(source_p))
117 {
118 sendto_one_notice(source_p, ":You must be using a secure connection to /CHALLENGE on this server");
119 if (ConfigFileEntry.failed_oper_notice)
120 {
121 sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
122 "Failed CHALLENGE attempt - missing secure connection by %s (%s@%s)",
123 source_p->name, source_p->username, source_p->host);
124 }
125 return;
126 }
127
128 /* if theyre an oper, reprint oper motd and ignore */
129 if(IsOper(source_p))
130 {
131 sendto_one(source_p, form_str(RPL_YOUREOPER), me.name, source_p->name);
132 send_oper_motd(source_p);
133 return;
134 }
135
136 if(*parv[1] == '+')
137 {
138 /* Ignore it if we aren't expecting this... -A1kmm */
139 if(!source_p->localClient->challenge)
140 return;
141
142 if((rb_current_time() - source_p->localClient->chal_time) > CHALLENGE_EXPIRES)
143 {
144 sendto_one(source_p, form_str(ERR_PASSWDMISMATCH), me.name, source_p->name);
145 ilog(L_FOPER, "EXPIRED CHALLENGE (%s) by (%s!%s@%s) (%s)",
146 source_p->user->opername, source_p->name,
147 source_p->username, source_p->host, source_p->sockhost);
148
149 if(ConfigFileEntry.failed_oper_notice)
150 sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
151 "Expired CHALLENGE attempt by %s (%s@%s)",
152 source_p->name, source_p->username,
153 source_p->host);
154 cleanup_challenge(source_p);
155 return;
156 }
157
158 parv[1]++;
159 b_response = rb_base64_decode((const unsigned char *)parv[1], strlen(parv[1]), &len);
160
161 if(len != SHA_DIGEST_LENGTH ||
162 memcmp(source_p->localClient->challenge, b_response, SHA_DIGEST_LENGTH))
163 {
164 sendto_one(source_p, form_str(ERR_PASSWDMISMATCH), me.name, source_p->name);
165 ilog(L_FOPER, "FAILED CHALLENGE (%s) by (%s!%s@%s) (%s)",
166 source_p->user->opername, source_p->name,
167 source_p->username, source_p->host, source_p->sockhost);
168
169 if(ConfigFileEntry.failed_oper_notice)
170 sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
171 "Failed CHALLENGE attempt by %s (%s@%s)",
172 source_p->name, source_p->username,
173 source_p->host);
174
175 rb_free(b_response);
176 cleanup_challenge(source_p);
177 return;
178 }
179
180 rb_free(b_response);
181
182 oper_p = find_oper_conf(source_p->username, source_p->orighost,
183 source_p->sockhost,
184 source_p->user->opername);
185
186 if(oper_p == NULL)
187 {
188 sendto_one_numeric(source_p, ERR_NOOPERHOST, form_str(ERR_NOOPERHOST));
189 ilog(L_FOPER, "FAILED OPER (%s) by (%s!%s@%s) (%s)",
190 source_p->user->opername, source_p->name,
191 source_p->username, source_p->host,
192 source_p->sockhost);
193
194 if(ConfigFileEntry.failed_oper_notice)
195 sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
196 "Failed CHALLENGE attempt - host mismatch by %s (%s@%s)",
197 source_p->name, source_p->username,
198 source_p->host);
199 return;
200 }
201
202 cleanup_challenge(source_p);
203
204 oper_up(source_p, oper_p);
205
206 ilog(L_OPERED, "OPER %s by %s!%s@%s (%s)",
207 source_p->user->opername, source_p->name,
208 source_p->username, source_p->host, source_p->sockhost);
209 return;
210 }
211
212 cleanup_challenge(source_p);
213
214 oper_p = find_oper_conf(source_p->username, source_p->orighost,
215 source_p->sockhost, parv[1]);
216
217 if(oper_p == NULL)
218 {
219 sendto_one_numeric(source_p, ERR_NOOPERHOST, form_str(ERR_NOOPERHOST));
220 ilog(L_FOPER, "FAILED OPER (%s) by (%s!%s@%s) (%s)",
221 parv[1], source_p->name,
222 source_p->username, source_p->host, source_p->sockhost);
223
224 if(ConfigFileEntry.failed_oper_notice)
225 sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
226 "Failed CHALLENGE attempt - host mismatch by %s (%s@%s)",
227 source_p->name, source_p->username, source_p->host);
228 return;
229 }
230
231 if(!oper_p->rsa_pubkey)
232 {
233 sendto_one_notice(source_p, ":I'm sorry, PK authentication is not enabled for your oper{} block.");
234 return;
235 }
236
237 if(IsOperConfNeedSSL(oper_p) && !IsSecureClient(source_p))
238 {
239 sendto_one_numeric(source_p, ERR_NOOPERHOST, form_str(ERR_NOOPERHOST));
240 ilog(L_FOPER, "FAILED CHALLENGE (%s) by (%s!%s@%s) (%s) -- requires SSL/TLS",
241 parv[1], source_p->name, source_p->username, source_p->host,
242 source_p->sockhost);
243
244 if(ConfigFileEntry.failed_oper_notice)
245 {
246 sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
247 "Failed CHALLENGE attempt - missing SSL/TLS by %s (%s@%s)",
248 source_p->name, source_p->username, source_p->host);
249 }
250 return;
251 }
252
253 if (oper_p->certfp != NULL)
254 {
255 if (source_p->certfp == NULL || rb_strcasecmp(source_p->certfp, oper_p->certfp))
256 {
257 sendto_one_numeric(source_p, ERR_NOOPERHOST, form_str(ERR_NOOPERHOST));
258 ilog(L_FOPER, "FAILED OPER (%s) by (%s!%s@%s) (%s) -- client certificate fingerprint mismatch",
259 parv[1], source_p->name,
260 source_p->username, source_p->host, source_p->sockhost);
261
262 if(ConfigFileEntry.failed_oper_notice)
263 {
264 sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
265 "Failed OPER attempt - client certificate fingerprint mismatch by %s (%s@%s)",
266 source_p->name, source_p->username, source_p->host);
267 }
268 return;
269 }
270 }
271
272 if(generate_challenge(&challenge, &(source_p->localClient->challenge), oper_p->rsa_pubkey))
273 {
274 char *chal = challenge;
275 source_p->localClient->chal_time = rb_current_time();
276 for(;;)
277 {
278 cnt = rb_strlcpy(chal_line, chal, CHALLENGE_WIDTH);
279 sendto_one(source_p, form_str(RPL_RSACHALLENGE2), me.name, source_p->name, chal_line);
280 if(cnt > CHALLENGE_WIDTH)
281 chal += CHALLENGE_WIDTH - 1;
282 else
283 break;
284
285 }
286 sendto_one(source_p, form_str(RPL_ENDOFRSACHALLENGE2),
287 me.name, source_p->name);
288 rb_free(challenge);
289 source_p->user->opername = rb_strdup(oper_p->name);
290 }
291 else
292 sendto_one_notice(source_p, ":Failed to generate challenge.");
293 }
294
295 static bool
296 generate_challenge(char **r_challenge, char **r_response, RSA * rsa)
297 {
298 SHA_CTX ctx;
299 unsigned char secret[CHALLENGE_SECRET_LENGTH], *tmp;
300 unsigned long length;
301 unsigned long e = 0;
302 unsigned long cnt = 0;
303 int ret;
304
305 if(!rsa)
306 return false;
307 if(rb_get_random(secret, CHALLENGE_SECRET_LENGTH))
308 {
309 SHA1_Init(&ctx);
310 SHA1_Update(&ctx, (uint8_t *)secret, CHALLENGE_SECRET_LENGTH);
311 *r_response = malloc(SHA_DIGEST_LENGTH);
312 SHA1_Final((uint8_t *)*r_response, &ctx);
313
314 length = RSA_size(rsa);
315 tmp = rb_malloc(length);
316 ret = RSA_public_encrypt(CHALLENGE_SECRET_LENGTH, secret, tmp, rsa, RSA_PKCS1_OAEP_PADDING);
317
318 if(ret >= 0)
319 {
320 *r_challenge = (char *)rb_base64_encode(tmp, ret);
321 rb_free(tmp);
322 return true;
323 }
324
325 rb_free(tmp);
326 rb_free(*r_response);
327 *r_response = NULL;
328 }
329
330 ERR_load_crypto_strings();
331 while ((cnt < 100) && (e = ERR_get_error()))
332 {
333 ilog(L_MAIN, "SSL error: %s", ERR_error_string(e, 0));
334 cnt++;
335 }
336
337 return false;
338 }
339
340 #endif /* HAVE_LIBCRYPTO */