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