]>
Commit | Line | Data |
---|---|---|
0a30e865 SB |
1 | /* |
2 | * syn: a utility bot to manage IRC network access | |
3 | * Copyright (C) 2009-2016 Stephen Bennett | |
4 | * | |
5 | * This program is free software: you can redistribute it and/or modify | |
6 | * it under the terms of the GNU Affero General Public License as | |
7 | * published by the Free Software Foundation, either version 3 of the | |
8 | * License, or (at your option) any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU Affero General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU Affero General Public License | |
16 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | |
17 | */ | |
18 | ||
19 | ||
345beb92 | 20 | #include "atheme.h" |
2497348d | 21 | #include "syn.h" |
345beb92 | 22 | |
dc11be33 JK |
23 | #define PRF_KEY_LEN 16 |
24 | #define PRF_KEY_HEX_LEN (PRF_KEY_LEN * 2) | |
25 | #define PRF_OUT_LEN 16 | |
26 | ||
27 | char *prf_key_hex = NULL; | |
28 | uint8_t prf_key[PRF_KEY_LEN]; | |
29 | bool prf_ready = false; | |
30 | ||
2497348d | 31 | const char *_decode_hex_ip(const char *hex) |
345beb92 SB |
32 | { |
33 | static char buf[16]; | |
34 | unsigned int ip = 0; | |
35 | ||
36 | buf[0] = '\0'; | |
37 | ||
a7578b74 SB |
38 | if (strlen(hex) != 8) |
39 | return NULL; | |
345beb92 | 40 | |
a7578b74 SB |
41 | char *endptr; |
42 | ip = strtoul(hex, &endptr, 16); | |
43 | if (*endptr) | |
345beb92 SB |
44 | return NULL; |
45 | ||
46 | sprintf(buf, "%hhu.%hhu.%hhu.%hhu", (ip >> 24) & 0xff, (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff); | |
47 | return buf; | |
48 | } | |
49 | ||
dc11be33 | 50 | const char *_get_random_host_part(user_t *u) |
345beb92 | 51 | { |
71842e71 JK |
52 | // UID, user, host, '!', '@', NUL |
53 | static char user_buf[9 + USERLEN + HOSTLEN + 3]; | |
dc11be33 | 54 | static char buf[PRF_OUT_LEN + 3]; |
345beb92 SB |
55 | |
56 | strcpy(buf, "x-"); | |
71842e71 | 57 | snprintf(user_buf, sizeof user_buf, "%s!%s@%s", u->uid, u->user, u->host); |
345beb92 | 58 | |
dc11be33 | 59 | if (!prf_ready) |
345beb92 | 60 | { |
dc11be33 JK |
61 | syn_debug(2, "PRF key not configured, falling back to random cloaking"); |
62 | for (size_t i = 0; i < PRF_OUT_LEN; ++i) | |
63 | { | |
64 | buf[i + 2] = 'a' + rand() % 26; | |
65 | } | |
345beb92 | 66 | } |
dc11be33 JK |
67 | else |
68 | { | |
69 | int siphash(const uint8_t *in, const size_t inlen, const uint8_t *k, | |
70 | uint8_t *out, const size_t outlen); | |
71 | ||
72 | uint8_t out[PRF_OUT_LEN]; | |
71842e71 | 73 | siphash((unsigned char*)user_buf, strlen(user_buf), prf_key, out, PRF_OUT_LEN); |
dc11be33 JK |
74 | |
75 | for (size_t i=0; i < PRF_OUT_LEN; ++i) | |
76 | { | |
77 | buf[i + 2] = 'a' + out[i] % 26; | |
78 | } | |
79 | } | |
80 | ||
81 | buf[PRF_OUT_LEN + 2] = 0; | |
345beb92 SB |
82 | return buf; |
83 | } | |
84 | ||
d0f72f63 JK |
85 | // Taken from ircd-seven extensions/sasl_usercloak.c, modified for const correctness |
86 | static unsigned int fnv_hash_string(const char *str) | |
87 | { | |
88 | unsigned int hash = 0x811c9dc5; // Magic value for 32-bit fnv1 hash initialisation. | |
89 | unsigned const char *p = (unsigned const char *)str; | |
90 | while (*p) | |
91 | { | |
92 | hash += (hash<<1) + (hash<<4) + (hash<<7) + (hash<<8) + (hash<<24); | |
93 | hash ^= *p++; | |
94 | } | |
95 | return hash; | |
96 | } | |
97 | ||
98 | // Make sure to keep these in agreement. | |
99 | #define SUFFIX_HASH_LENGTH 8 | |
100 | #define SUFFIX_HASH_FMT "%08ud" | |
101 | #define SUFFIX_HASH_MODULUS 100000000 | |
102 | ||
2497348d | 103 | const char *_encode_ident_for_host(const char *str) |
d0f72f63 JK |
104 | { |
105 | // ident + /x- + SUFFIX_HASH_LENGTH, and nul terminator | |
106 | static char buf[USERLEN + SUFFIX_HASH_LENGTH + 3 + 1]; | |
107 | bool needhash = false; | |
108 | ||
109 | char *dst = buf; | |
110 | for (const char *src = str; *src; src++) | |
111 | { | |
112 | if (str - src > USERLEN) | |
113 | { | |
114 | slog(LG_ERROR, "encode_ident_for_host(): tried to encode %s which is too long", str); | |
115 | return NULL; | |
116 | } | |
117 | ||
118 | // For now, consider alphanumerics valid, as well as - | |
119 | // . is technically possible in ident, but might be confused for cloak formatting | |
120 | // Digits are not allowed unless there was another character successfully reproduced | |
121 | // since this could otherwise produce output that looks like a CIDR mask, | |
122 | // which messes with bans and is generally not done. | |
123 | if (IsAlpha(*src) || (IsDigit(*src) && dst != buf) || *src == '-') | |
124 | *dst++ = *src; | |
125 | else | |
126 | needhash = true; | |
127 | } | |
128 | ||
129 | *dst = '\0'; | |
130 | ||
131 | if (needhash) | |
132 | { | |
133 | unsigned int hashval = fnv_hash_string(str); | |
134 | hashval %= SUFFIX_HASH_MODULUS; | |
135 | snprintf(dst, 3 + SUFFIX_HASH_LENGTH + 1, "/x-" SUFFIX_HASH_FMT, hashval); | |
136 | } | |
137 | ||
138 | return buf; | |
139 | } | |
140 | ||
2497348d | 141 | time_t _syn_parse_duration(const char *s) |
b45a5ca4 SB |
142 | { |
143 | time_t duration = atol(s); | |
144 | while (isdigit(*s)) | |
145 | s++; | |
146 | switch (*s) | |
147 | { | |
148 | case 'H': | |
149 | case 'h': | |
150 | duration *= 60; | |
151 | break; | |
152 | case 'D': | |
153 | case 'd': | |
154 | duration *= 1440; | |
155 | break; | |
156 | case 'W': | |
157 | case 'w': | |
158 | duration *= 10080; | |
159 | break; | |
160 | } | |
161 | return duration; | |
162 | } | |
163 | ||
2497348d | 164 | const char *_syn_format_expiry(time_t t) |
b45a5ca4 SB |
165 | { |
166 | static char expirybuf[BUFSIZE]; | |
167 | if (t > 0) | |
168 | { | |
169 | strftime(expirybuf, BUFSIZE, "%d/%m/%Y %H:%M:%S", gmtime(&t)); | |
170 | } | |
171 | else | |
172 | { | |
173 | strcpy(expirybuf, "never"); | |
174 | } | |
175 | ||
176 | return expirybuf; | |
177 | } | |
345beb92 | 178 | |
dc11be33 JK |
179 | static void syn_util_config_ready(void *unused) |
180 | { | |
181 | if (prf_key_hex == NULL) | |
182 | { | |
183 | slog(LG_ERROR, "syn/util: could not find 'prf_key' configuration entry"); | |
184 | prf_ready = false; | |
185 | return; | |
186 | } | |
187 | ||
188 | if (strlen(prf_key_hex) != PRF_KEY_HEX_LEN) | |
189 | { | |
190 | slog(LG_ERROR, "syn/util: prf_key must be exactly %d hex digits", PRF_KEY_HEX_LEN); | |
191 | prf_ready = false; | |
192 | return; | |
193 | } | |
194 | ||
195 | // This could be done in a single big sscanf, but let's not do that | |
196 | for (size_t i = 0; i < PRF_KEY_LEN; i++) | |
197 | { | |
198 | if (sscanf(prf_key_hex + (i * 2), "%2" SCNx8, &prf_key[i]) != 1) | |
199 | { | |
200 | slog(LG_ERROR, "syn/util: failed to parse prf_key - must be string of hex digits"); | |
201 | prf_ready = false; | |
202 | return; | |
203 | } | |
204 | } | |
205 | ||
206 | prf_ready = true; | |
207 | } | |
208 | ||
060f0d15 | 209 | static void mod_init(module_t *m) |
dc11be33 JK |
210 | { |
211 | use_syn_main_symbols(m); | |
212 | ||
213 | add_dupstr_conf_item("PRF_KEY", &syn->conf_table, 0, &prf_key_hex, NULL); | |
214 | hook_add_config_ready(syn_util_config_ready); | |
215 | } | |
216 | ||
060f0d15 | 217 | static void mod_deinit(module_unload_intent_t unused) |
dc11be33 JK |
218 | { |
219 | del_conf_item("PRF_KEY", &syn->conf_table); | |
220 | hook_del_config_ready(syn_util_config_ready); | |
221 | } | |
222 | ||
492a4dc0 JK |
223 | DECLARE_MODULE_V1 |
224 | ( | |
dc11be33 | 225 | "syn/util", false, mod_init, mod_deinit, |
492a4dc0 JK |
226 | "$Revision$", |
227 | "Stephen Bennett <stephen -at- freenode.net>" | |
228 | ); |