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