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