1 /*
2 ** AUTHINFO SASL functionality.
3 */
4
5 #include "portable/system.h"
6
7 #include "inn/messages.h"
8 #include "nnrpd.h"
9
10 /* Outside the ifdef so that make depend works even ifndef HAVE_OPENSSL. */
11 #include "inn/ov.h"
12 #include "tls.h"
13
14
15 #ifdef HAVE_SASL
16 sasl_conn_t *sasl_conn = NULL;
17 int sasl_ssf = 0;
18 int sasl_maxout = NNTP_MAXLEN_COMMAND;
19
20 sasl_callback_t sasl_callbacks[] = {
21 /* XXX Do we want a proxy callback? */
22 /* XXX Add a getopt callback? */
23 {SASL_CB_LIST_END, NULL, NULL}};
24
25 # define BASE64_BUF_SIZE \
26 21848 /* Per RFC 4422: (floor(n/3) + 1) * 4 \
27 where n = 16 kB = 16384 bytes. */
28
29 /*
30 ** Create a new SASL server authentication object.
31 */
32 void
SASLnewserver(void)33 SASLnewserver(void)
34 {
35 if (sasl_conn != NULL) {
36 sasl_dispose(&sasl_conn);
37 sasl_conn = NULL;
38 sasl_ssf = 0;
39 sasl_maxout = NNTP_MAXLEN_COMMAND;
40 }
41
42 if (sasl_server_new("nntp", NULL, NULL, NULL, NULL, NULL,
43 SASL_SUCCESS_DATA, &sasl_conn)
44 != SASL_OK) {
45 syslog(L_FATAL, "sasl_server_new() failed");
46 Reply("%d SASL server unavailable. Try later!\r\n",
47 NNTP_FAIL_TERMINATING);
48 ExitWithStats(1, true);
49 } else {
50 /* XXX Fill in SASL_IPLOCALPORT and SASL_IPREMOTEPORT. */
51 sasl_security_properties_t secprops;
52
53 memset(&secprops, 0, sizeof(secprops));
54 secprops.security_flags = SASL_SEC_NOANONYMOUS;
55 secprops.max_ssf = 256;
56 secprops.maxbufsize = NNTP_MAXLEN_COMMAND;
57 sasl_setprop(sasl_conn, SASL_SEC_PROPS, &secprops);
58 # ifdef HAVE_OPENSSL
59 /* Tell SASL about the negotiated TLS layer. */
60 if (encryption_layer_on) {
61 if (sasl_setprop(sasl_conn, SASL_SSF_EXTERNAL,
62 (sasl_ssf_t *) &tls_cipher_usebits)
63 != SASL_OK) {
64 syslog(L_NOTICE, "sasl_setprop() failed: TLS layer for SASL");
65 }
66 if (sasl_setprop(sasl_conn, SASL_AUTH_EXTERNAL, tls_peer_CN)
67 != SASL_OK) {
68 syslog(L_NOTICE, "sasl_setprop() failed: TLS layer for SASL");
69 }
70 }
71 # endif
72 }
73 }
74
75
76 void
SASLauth(int ac,char * av[])77 SASLauth(int ac, char *av[])
78 {
79 const char *mech;
80 const char *clientin = NULL;
81 unsigned int clientinlen = 0;
82 size_t tclientinlen = 0;
83 const char *serverout = NULL;
84 unsigned int serveroutlen = 0;
85 char base64[BASE64_BUF_SIZE + 1];
86 const char *canon_user = NULL;
87 const int *ssfp = NULL;
88 const int *maxoutp = NULL;
89 const void *property;
90 int r = SASL_OK;
91 int r1;
92 bool base64error = false;
93
94 if (ac < 3 || ac > 4) {
95 /* In fact, ac > 4 here. */
96 Reply("%d Too many arguments\r\n", NNTP_ERR_SYNTAX);
97 return;
98 }
99
100 mech = av[2];
101
102 if (!IsValidAlgorithm(mech)) {
103 Reply("%d Syntax error in mechanism name\r\n", NNTP_ERR_SYNTAX);
104 return;
105 }
106
107 /* 502 if authentication will fail. */
108 if (!PERMcanauthenticate) {
109 if (PERMauthorized && !PERMneedauth)
110 Reply("%d Already authenticated\r\n", NNTP_ERR_ACCESS);
111 else
112 Reply("%d Authentication will fail\r\n", NNTP_ERR_ACCESS);
113 return;
114 }
115
116 # ifdef HAVE_OPENSSL
117 /* Check whether STARTTLS must be used before trying to authenticate
118 * with AUTHINFO SASL PLAIN, LOGIN or EXTERNAL. */
119 if (PERMcanauthenticate && !PERMcanauthenticatewithoutSSL
120 && !encryption_layer_on
121 && ((strcasecmp(mech, "PLAIN") == 0 || strcasecmp(mech, "LOGIN") == 0
122 || strcasecmp(mech, "EXTERNAL") == 0))) {
123 Reply("%d Encryption layer required\r\n", NNTP_FAIL_PRIVACY_NEEDED);
124 return;
125 }
126 # endif
127
128 if (ac == 4) {
129 /* Initial response. */
130 clientin = av[3];
131
132 if (strcmp(clientin, "=") == 0) {
133 /* Zero-length initial response. */
134 clientin = "";
135 clientinlen = 0;
136 } else {
137 /* Decode the response. On error, SASL_CONTINUE should not be
138 * given because we know for sure that we have already received
139 * the whole challenge/response. Use SASL_BADPROT instead,
140 * in order to indicate a base64-encoding error. */
141 r1 = sasl_decode64(clientin, strlen(clientin), base64,
142 BASE64_BUF_SIZE, &clientinlen);
143 clientin = base64;
144 r = (r1 == SASL_CONTINUE ? SASL_BADPROT : r1);
145 base64error = (r == SASL_BADPROT);
146 }
147 }
148
149 if (r == SASL_OK) {
150 /* Start the exchange. */
151 r = sasl_server_start(sasl_conn, mech, clientin, clientinlen,
152 &serverout, &serveroutlen);
153 }
154
155 while (r == SASL_CONTINUE || (r == SASL_OK && serveroutlen != 0)) {
156 if (serveroutlen != 0) {
157 /* Encode the server challenge.
158 * In sasl_encode64() calls, the fourth argument is the length
159 * of the third including the null terminator. */
160 r1 = sasl_encode64(serverout, serveroutlen, base64,
161 BASE64_BUF_SIZE + 1, NULL);
162 if (r1 != SASL_OK)
163 r = r1;
164 }
165
166 /* Check for failure or success. */
167 if (r != SASL_CONTINUE)
168 break;
169
170 /* Send the challenge to the client. */
171 Reply("%d %s\r\n", NNTP_CONT_SASL, serveroutlen != 0 ? base64 : "=");
172 fflush(stdout);
173
174 /* Get the response from the client. */
175 r1 = line_read(&NNTPline, PERMaccessconf->clienttimeout, &clientin,
176 &tclientinlen, NULL);
177 clientinlen = tclientinlen;
178
179 switch (r1) {
180 case RTok:
181 if (clientinlen <= BASE64_BUF_SIZE)
182 break;
183 goto fallthroughRTlong;
184 case RTlong:
185 fallthroughRTlong:
186 warn("%s response too long in AUTHINFO SASL", Client.host);
187 Reply("%d Too long response\r\n", NNTP_FAIL_TERMINATING);
188 ExitWithStats(1, false);
189 /* NOTREACHED */
190 case RTtimeout:
191 warn("%s timeout in AUTHINFO SASL", Client.host);
192 /* No answer. */
193 ExitWithStats(1, false);
194 /* NOTREACHED */
195 case RTeof:
196 warn("%s EOF in AUTHINFO SASL", Client.host);
197 Reply("%d EOF\r\n", NNTP_FAIL_TERMINATING);
198 ExitWithStats(1, false);
199 /* NOTREACHED */
200 default:
201 warn("%s internal %d in AUTHINFO SASL", Client.host, r);
202 Reply("%d Internal error\r\n", NNTP_FAIL_TERMINATING);
203 ExitWithStats(1, false);
204 }
205
206 /* Check if client cancelled. */
207 if (strcmp(clientin, "*") == 0) {
208 /* Restart the SASL server in order to be able to reauthenticate.
209 * Call that function before the reply because in case of failure,
210 * 400 is sent. */
211 SASLnewserver();
212 Reply("%d Client cancelled authentication\r\n",
213 NNTP_FAIL_AUTHINFO_BAD);
214 return;
215 }
216
217 if (strcmp(clientin, "=") == 0) {
218 /* Zero-length answer. */
219 clientin = "";
220 clientinlen = 0;
221 } else {
222 /* Decode the response. On error, SASL_CONTINUE should not be
223 * given because we know for sure that we have already received
224 * the whole challenge/response. Use SASL_BADPROT instead,
225 * in order to indicate a base64-encoding error. */
226 r1 = sasl_decode64(clientin, clientinlen, base64, BASE64_BUF_SIZE,
227 &clientinlen);
228 clientin = base64;
229 r = (r1 == SASL_CONTINUE ? SASL_BADPROT : r1);
230 base64error = (r == SASL_BADPROT);
231 }
232
233 /* Do the next step. */
234 if (r == SASL_OK) {
235 r = sasl_server_step(sasl_conn, clientin, clientinlen, &serverout,
236 &serveroutlen);
237 }
238 }
239
240 /* Fetch the username (authorization ID). */
241 if (r == SASL_OK) {
242 r = sasl_getprop(sasl_conn, SASL_USERNAME, &property);
243 canon_user = property;
244 }
245
246 /* Grab info about the negotiated layer. */
247 if (r == SASL_OK) {
248 r = sasl_getprop(sasl_conn, SASL_SSF, &property);
249 ssfp = property;
250 }
251
252 if (r == SASL_OK) {
253 r = sasl_getprop(sasl_conn, SASL_MAXOUTBUF, &property);
254 maxoutp = property;
255 }
256
257 if (r == SASL_OK) {
258 /* Success!
259 * First, save info about the negotiated security layer
260 * for I/O functions. */
261 sasl_ssf = *ssfp;
262 sasl_maxout = (*maxoutp == 0 || *maxoutp > NNTP_MAXLEN_COMMAND)
263 ? NNTP_MAXLEN_COMMAND
264 : *maxoutp;
265
266 if (sasl_ssf > 1) {
267 /* For the forthcoming check of the permissions the client now
268 * has, tell the connection is encrypted, so that auth blocks
269 * requiring the negotiation of a security layer in readers.conf
270 * are properly taken into account.
271 * When sasl_ssf equals 1, only data integrity is provided, without
272 * any security. */
273 encryption_layer_on = true;
274
275 /* Close out any existing article, report group stats.
276 * RFC 4643 requires the reset of any knowledge about the client.
277 */
278 if (GRPcur) {
279 bool boolval;
280 ARTclose();
281 GRPreport();
282 OVctl(OVCACHEFREE, &boolval);
283 free(GRPcur);
284 GRPcur = NULL;
285 if (ARTcount) {
286 syslog(L_NOTICE,
287 "%s exit for AUTHINFO SASL articles %ld groups %ld",
288 Client.host, ARTcount, GRPcount);
289 }
290 GRPcount = 0;
291 PERMgroupmadeinvalid = false;
292
293 /* Reset our read buffer so as to prevent plaintext
294 * command injection. */
295 line_reset(&NNTPline);
296 }
297 }
298
299 PERMgetaccess(false);
300 strlcpy(PERMuser, canon_user, sizeof(PERMuser));
301 PERMgetpermissions();
302 PERMneedauth = false;
303 PERMauthorized = true;
304 PERMcanauthenticate = false;
305
306 syslog(L_NOTICE, "%s user %s", Client.host, PERMuser);
307
308 if (serveroutlen) {
309 Reply("%d %s\r\n", NNTP_OK_SASL, base64);
310 } else {
311 Reply("%d Authentication succeeded\r\n", NNTP_OK_AUTHINFO);
312 }
313 } else {
314 /* Failure. */
315 int resp_code;
316 const char *errstring = sasl_errstring(r, NULL, NULL);
317
318 syslog(L_NOTICE, "%s bad_auth", Client.host);
319
320 switch (r) {
321 case SASL_BADPROT:
322 resp_code =
323 (base64error ? NNTP_ERR_BASE64 : NNTP_FAIL_AUTHINFO_REJECT);
324 break;
325 case SASL_BADPARAM:
326 case SASL_NOTDONE:
327 resp_code = NNTP_FAIL_AUTHINFO_REJECT;
328 break;
329 case SASL_NOMECH:
330 resp_code = NNTP_ERR_UNAVAILABLE;
331 break;
332 case SASL_ENCRYPT:
333 resp_code = NNTP_FAIL_PRIVACY_NEEDED;
334 break;
335 default:
336 resp_code = NNTP_FAIL_AUTHINFO_BAD;
337 break;
338 }
339
340 /* Restart the SASL server in order to be able to reauthenticate.
341 * Call that function before the reply because in case of failure,
342 * 400 is sent. */
343 SASLnewserver();
344 Reply("%d %s\r\n", resp_code,
345 errstring ? errstring : "Authentication failed");
346 }
347 }
348
349 #endif /* HAVE_SASL */
350