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