]> jfr.im git - irc/rqf/shadowircd.git/blob - modules/m_challenge.c
[svn] - the new plan:
[irc/rqf/shadowircd.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 * $Id: m_challenge.c 1483 2006-05-27 18:58:12Z jilles $
25 */
26
27 #include "stdinc.h"
28
29 #ifdef HAVE_LIBCRYPTO
30 #include <openssl/pem.h>
31 #include <openssl/rand.h>
32 #include <openssl/rsa.h>
33 #include <openssl/md5.h>
34 #include <openssl/bn.h>
35 #include <openssl/evp.h>
36 #include <openssl/err.h>
37 #endif
38
39 #include "memory.h"
40 #include "client.h"
41 #include "ircd.h"
42 #include "modules.h"
43 #include "numeric.h"
44 #include "send.h"
45 #include "s_conf.h"
46 #include "msg.h"
47 #include "parse.h"
48 #include "irc_string.h"
49 #include "s_log.h"
50 #include "s_user.h"
51 #include "cache.h"
52 #include "s_newconf.h"
53
54 #define CHALLENGE_WIDTH BUFSIZE - (NICKLEN + HOSTLEN + 12)
55 #define CHALLENGE_EXPIRES 180 /* 180 seconds should be more than long enough */
56 #define CHALLENGE_SECRET_LENGTH 128 /* how long our challenge secret should be */
57
58 #ifndef HAVE_LIBCRYPTO
59 /* Maybe this should be an error or something?-davidt */
60 /* now it is -larne */
61 static int challenge_load(void)
62 {
63 #ifndef STATIC_MODULES
64 sendto_realops_snomask(SNO_GENERAL, L_ALL,
65 "Challenge module not loaded because OpenSSL is not available.");
66 ilog(L_MAIN, "Challenge module not loaded because OpenSSL is not available.");
67 return -1;
68 #else
69 return 0;
70 #endif
71 }
72
73 DECLARE_MODULE_AV1(challenge, challenge_load, NULL, NULL, NULL, NULL, "$Revision: 1483 $");
74 #else
75
76 static int m_challenge(struct Client *, struct Client *, int, const char **);
77
78 /* We have openssl support, so include /CHALLENGE */
79 struct Message challenge_msgtab = {
80 "CHALLENGE", 0, 0, 0, MFLG_SLOW,
81 {mg_unreg, {m_challenge, 2}, mg_ignore, mg_ignore, mg_ignore, {m_challenge, 2}}
82 };
83
84 mapi_clist_av1 challenge_clist[] = { &challenge_msgtab, NULL };
85 DECLARE_MODULE_AV1(challenge, NULL, NULL, challenge_clist, NULL, NULL, "$Revision: 1483 $");
86
87 static int 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 MyFree(target_p->localClient->challenge);
96 MyFree(target_p->localClient->opername);
97 target_p->localClient->challenge = NULL;
98 target_p->localClient->opername = NULL;
99 target_p->localClient->chal_time = 0;
100 }
101
102 /*
103 * m_challenge - generate RSA challenge for wouldbe oper
104 * parv[0] = sender prefix
105 * parv[1] = operator to challenge for, or +response
106 *
107 */
108 static int
109 m_challenge(struct Client *client_p, struct Client *source_p, int parc, const char *parv[])
110 {
111 struct oper_conf *oper_p;
112 char *challenge = NULL; /* to placate gcc */
113 char chal_line[CHALLENGE_WIDTH];
114 unsigned char *b_response;
115 size_t cnt;
116 int len = 0;
117
118 /* if theyre an oper, reprint oper motd and ignore */
119 if(IsOper(source_p))
120 {
121 sendto_one(source_p, form_str(RPL_YOUREOPER), me.name, source_p->name);
122 send_oper_motd(source_p);
123 return 0;
124 }
125
126 if(*parv[1] == '+')
127 {
128 /* Ignore it if we aren't expecting this... -A1kmm */
129 if(!source_p->localClient->challenge)
130 return 0;
131
132 if((CurrentTime - source_p->localClient->chal_time) > CHALLENGE_EXPIRES)
133 {
134 sendto_one(source_p, form_str(ERR_PASSWDMISMATCH), me.name, source_p->name);
135 ilog(L_FOPER, "EXPIRED CHALLENGE (%s) by (%s!%s@%s) (%s)",
136 source_p->localClient->opername, source_p->name,
137 source_p->username, source_p->host, source_p->sockhost);
138
139 if(ConfigFileEntry.failed_oper_notice)
140 sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
141 "Expired CHALLENGE attempt by %s (%s@%s)",
142 source_p->name, source_p->username,
143 source_p->host);
144 cleanup_challenge(source_p);
145 return 0;
146 }
147
148 b_response = ircd_base64_decode((const unsigned char *)++parv[1], strlen(parv[1]), &len);
149
150 if(len != SHA_DIGEST_LENGTH ||
151 memcmp(source_p->localClient->challenge, b_response, SHA_DIGEST_LENGTH))
152 {
153 sendto_one(source_p, form_str(ERR_PASSWDMISMATCH), me.name, source_p->name);
154 ilog(L_FOPER, "FAILED CHALLENGE (%s) by (%s!%s@%s) (%s)",
155 source_p->localClient->opername, source_p->name,
156 source_p->username, source_p->host, source_p->sockhost);
157
158 if(ConfigFileEntry.failed_oper_notice)
159 sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
160 "Failed CHALLENGE attempt by %s (%s@%s)",
161 source_p->name, source_p->username,
162 source_p->host);
163
164 MyFree(b_response);
165 cleanup_challenge(source_p);
166 return 0;
167 }
168
169 MyFree(b_response);
170
171 oper_p = find_oper_conf(source_p->username, source_p->orighost,
172 source_p->sockhost,
173 source_p->localClient->opername);
174
175 if(oper_p == NULL)
176 {
177 sendto_one(source_p, form_str(ERR_NOOPERHOST),
178 me.name, source_p->name);
179 ilog(L_FOPER, "FAILED OPER (%s) by (%s!%s@%s) (%s)",
180 source_p->localClient->opername, source_p->name,
181 source_p->username, source_p->host,
182 source_p->sockhost);
183
184 if(ConfigFileEntry.failed_oper_notice)
185 sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
186 "Failed CHALLENGE attempt - host mismatch by %s (%s@%s)",
187 source_p->name, source_p->username,
188 source_p->host);
189 return 0;
190 }
191
192 cleanup_challenge(source_p);
193
194 oper_up(source_p, oper_p);
195
196 ilog(L_OPERED, "OPER %s by %s!%s@%s (%s)",
197 source_p->localClient->opername, source_p->name,
198 source_p->username, source_p->host, source_p->sockhost);
199 return 0;
200 }
201
202 cleanup_challenge(source_p);
203
204 oper_p = find_oper_conf(source_p->username, source_p->orighost,
205 source_p->sockhost, parv[1]);
206
207 if(oper_p == NULL)
208 {
209 sendto_one(source_p, form_str(ERR_NOOPERHOST), me.name, source_p->name);
210 ilog(L_FOPER, "FAILED OPER (%s) by (%s!%s@%s) (%s)",
211 parv[1], source_p->name,
212 source_p->username, source_p->host, source_p->sockhost);
213
214 if(ConfigFileEntry.failed_oper_notice)
215 sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
216 "Failed CHALLENGE attempt - host mismatch by %s (%s@%s)",
217 source_p->name, source_p->username, source_p->host);
218 return 0;
219 }
220
221 if(!oper_p->rsa_pubkey)
222 {
223 sendto_one(source_p, ":%s NOTICE %s :I'm sorry, PK authentication "
224 "is not enabled for your oper{} block.", me.name, parv[0]);
225 return 0;
226 }
227
228 if(!generate_challenge(&challenge, &(source_p->localClient->challenge), oper_p->rsa_pubkey))
229 {
230 char *chal = challenge;
231 source_p->localClient->chal_time = CurrentTime;
232 for(;;)
233 {
234 cnt = strlcpy(chal_line, chal, CHALLENGE_WIDTH);
235 sendto_one(source_p, form_str(RPL_RSACHALLENGE2), me.name, source_p->name, chal_line);
236 if(cnt > CHALLENGE_WIDTH)
237 chal += CHALLENGE_WIDTH - 1;
238 else
239 break;
240
241 }
242 sendto_one(source_p, form_str(RPL_ENDOFRSACHALLENGE2),
243 me.name, source_p->name);
244 MyFree(challenge);
245 DupString(source_p->localClient->opername, oper_p->name);
246 }
247 else
248 sendto_one_notice(source_p, ":Failed to generate challenge.");
249
250 return 0;
251 }
252
253 static int
254 get_randomness(unsigned char *buf, int length)
255 {
256 /* Seed OpenSSL PRNG with EGD enthropy pool -kre */
257 if(ConfigFileEntry.use_egd && (ConfigFileEntry.egdpool_path != NULL))
258 {
259 if(RAND_egd(ConfigFileEntry.egdpool_path) == -1)
260 return -1;
261 }
262
263 if(RAND_status())
264 {
265 if(RAND_bytes(buf, length) > 0)
266 return 1;
267 }
268 else {
269 if(RAND_pseudo_bytes(buf, length) >= 0)
270 return 1;
271 }
272 return 0;
273 }
274
275 static int
276 generate_challenge(char **r_challenge, char **r_response, RSA * rsa)
277 {
278 SHA_CTX ctx;
279 unsigned char secret[CHALLENGE_SECRET_LENGTH], *tmp;
280 unsigned long length;
281 unsigned long e = 0;
282 unsigned long cnt = 0;
283 int ret;
284
285 if(!rsa)
286 return -1;
287 if(get_randomness(secret, CHALLENGE_SECRET_LENGTH))
288 {
289 SHA1_Init(&ctx);
290 SHA1_Update(&ctx, (u_int8_t *)secret, CHALLENGE_SECRET_LENGTH);
291 *r_response = MyMalloc(SHA_DIGEST_LENGTH);
292 SHA1_Final((u_int8_t *)*r_response, &ctx);
293
294 length = RSA_size(rsa);
295 tmp = MyMalloc(length);
296 ret = RSA_public_encrypt(CHALLENGE_SECRET_LENGTH, secret, tmp, rsa, RSA_PKCS1_OAEP_PADDING);
297
298 if (ret >= 0)
299 {
300 *r_challenge = (char *)ircd_base64_encode(tmp, ret);
301 MyFree(tmp);
302 return 0;
303 }
304 MyFree(tmp);
305 MyFree(*r_response);
306 *r_response = NULL;
307 }
308
309 ERR_load_crypto_strings();
310 while ((cnt < 100) && (e = ERR_get_error()))
311 {
312 ilog(L_MAIN, "SSL error: %s", ERR_error_string(e, 0));
313 cnt++;
314 }
315
316 return (-1);
317 }
318
319 #endif /* HAVE_LIBCRYPTO */