]> jfr.im git - irc/freenode/syn.git/blob - util.c
add licence info
[irc/freenode/syn.git] / util.c
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
20 #include "atheme.h"
21 #include "syn.h"
22
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
31 const char *_decode_hex_ip(const char *hex)
32 {
33 static char buf[16];
34 unsigned int ip = 0;
35
36 buf[0] = '\0';
37
38 if (strlen(hex) != 8)
39 return NULL;
40
41 char *endptr;
42 ip = strtoul(hex, &endptr, 16);
43 if (*endptr)
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
50 const char *_get_random_host_part(user_t *u)
51 {
52 // UID, user, host, '!', '@', NUL
53 static char user_buf[9 + USERLEN + HOSTLEN + 3];
54 static char buf[PRF_OUT_LEN + 3];
55
56 strcpy(buf, "x-");
57 snprintf(user_buf, sizeof user_buf, "%s!%s@%s", u->uid, u->user, u->host);
58
59 if (!prf_ready)
60 {
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 }
66 }
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];
73 siphash((unsigned char*)user_buf, strlen(user_buf), prf_key, out, PRF_OUT_LEN);
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;
82 return buf;
83 }
84
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
103 const char *_encode_ident_for_host(const char *str)
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
141 time_t _syn_parse_duration(const char *s)
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
164 const char *_syn_format_expiry(time_t t)
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 }
178
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
209 static void mod_init(module_t *m)
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
217 static void mod_deinit(module_unload_intent_t unused)
218 {
219 del_conf_item("PRF_KEY", &syn->conf_table);
220 hook_del_config_ready(syn_util_config_ready);
221 }
222
223 DECLARE_MODULE_V1
224 (
225 "syn/util", false, mod_init, mod_deinit,
226 "$Revision$",
227 "Stephen Bennett <stephen -at- freenode.net>"
228 );