File: sasl.c

package info (click to toggle)
inn2 2.6.3-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 13,228 kB
  • sloc: ansic: 96,526; sh: 15,562; perl: 13,281; makefile: 3,700; yacc: 842; python: 309; lex: 262
file content (353 lines) | stat: -rw-r--r-- 11,253 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
/*
** AUTHINFO SASL functionality.
**
** $Id: sasl.c 10117 2016-11-06 14:23:27Z iulius $
*/

#include "config.h"
#include "clibrary.h"

#include "inn/messages.h"
#include "nnrpd.h"

/* Outside the ifdef so that make depend works even ifndef HAVE_OPENSSL. */
#include "inn/ov.h"
#include "tls.h"

#if defined(HAVE_OPENSSL)
extern int tls_cipher_usebits;
extern char *tls_peer_CN;
#endif /* HAVE_OPENSSL */
#if defined(HAVE_OPENSSL) || defined(HAVE_SASL)
extern bool encryption_layer_on;
#endif /* HAVE_OPENSSL || HAVE_SASL */

#ifdef HAVE_SASL
sasl_conn_t *sasl_conn = NULL;
int sasl_ssf = 0;
int sasl_maxout = NNTP_MAXLEN_COMMAND;

sasl_callback_t sasl_callbacks[] = {
    /* XXX Do we want a proxy callback? */
    /* XXX Add a getopt callback? */
    { SASL_CB_LIST_END, NULL, NULL }
};

#define BASE64_BUF_SIZE 21848   /* Per RFC 4422:  (floor(n/3) + 1) * 4
                                   where n = 16 kB = 16384 bytes. */

/*
**  Create a new SASL server authentication object.
*/
void
SASLnewserver(void)
{
    if (sasl_conn != NULL) {
        sasl_dispose(&sasl_conn);
        sasl_conn = NULL;
        sasl_ssf = 0;
        sasl_maxout = NNTP_MAXLEN_COMMAND;
    }
 
    if (sasl_server_new("nntp", NULL, NULL, NULL, NULL,
                        NULL, SASL_SUCCESS_DATA, &sasl_conn) != SASL_OK) {
        syslog(L_FATAL, "sasl_server_new() failed");
        Reply("%d SASL server unavailable.  Try later!\r\n", NNTP_FAIL_TERMINATING);
        ExitWithStats(1, true);
    } else {
        /* XXX Fill in SASL_IPLOCALPORT and SASL_IPREMOTEPORT. */
        sasl_security_properties_t secprops;

        memset(&secprops, 0, sizeof(secprops));
        secprops.security_flags = SASL_SEC_NOANONYMOUS;
        secprops.max_ssf = 256;
        secprops.maxbufsize = NNTP_MAXLEN_COMMAND;
        sasl_setprop(sasl_conn, SASL_SEC_PROPS, &secprops);
#ifdef HAVE_OPENSSL
        /* Tell SASL about the negotiated TLS layer. */
        if (encryption_layer_on) {
            if (sasl_setprop(sasl_conn, SASL_SSF_EXTERNAL,
                             (sasl_ssf_t *) &tls_cipher_usebits) != SASL_OK) {
                syslog(L_NOTICE, "sasl_setprop() failed:  TLS layer for SASL");
            }
            if (sasl_setprop(sasl_conn, SASL_AUTH_EXTERNAL, tls_peer_CN)
                != SASL_OK) {
                syslog(L_NOTICE, "sasl_setprop() failed:  TLS layer for SASL");
            }
        }
#endif
    }
}


void
SASLauth(int ac, char *av[])
{
    const char *mech;
    const char *clientin = NULL;
    unsigned int clientinlen = 0;
    size_t tclientinlen = 0;
    const char *serverout = NULL;
    unsigned int serveroutlen;
    char base64[BASE64_BUF_SIZE+1];
    const char *canon_user = NULL;
    const int *ssfp = NULL;
    const int *maxoutp;
    const void *property;
    int r = SASL_OK;
    int r1;
    bool base64error = false;

    if (ac < 3 || ac > 4) {
        /* In fact, ac > 4 here. */
        Reply("%d Too many arguments\r\n", NNTP_ERR_SYNTAX);
        return;
    }

    mech = av[2];

    if (!IsValidAlgorithm(mech)) {
        Reply("%d Syntax error in mechanism name\r\n", NNTP_ERR_SYNTAX);
        return;
    }

    /* 502 if authentication will fail. */
    if (!PERMcanauthenticate) {
        if (PERMauthorized && !PERMneedauth)
            Reply("%d Already authenticated\r\n", NNTP_ERR_ACCESS);
        else
            Reply("%d Authentication will fail\r\n", NNTP_ERR_ACCESS);
        return;
    }

#ifdef HAVE_OPENSSL
    /* Check whether STARTTLS must be used before trying to authenticate
     * with AUTHINFO SASL PLAIN, LOGIN or EXTERNAL. */
    if (PERMcanauthenticate && !PERMcanauthenticatewithoutSSL
        && !encryption_layer_on && ((strcasecmp(mech, "PLAIN") == 0
                                     || strcasecmp(mech, "LOGIN") == 0
                                     || strcasecmp(mech, "EXTERNAL") == 0))) {
        Reply("%d Encryption layer required\r\n", NNTP_FAIL_PRIVACY_NEEDED);
        return;
    }
#endif

    if (ac == 4) {
	/* Initial response. */
	clientin = av[3];

	if (strcmp(clientin, "=") == 0) {
	    /* Zero-length initial response. */
	    clientin = "";
            clientinlen = 0;
	} else {
	    /* Decode the response.  On error, SASL_CONTINUE should not be
             * given because we know for sure that we have already received
             * the whole challenge/response.  Use SASL_BADPROT instead,
             * in order to indicate a base64-encoding error. */
            r1 = sasl_decode64(clientin, strlen(clientin),
                               base64, BASE64_BUF_SIZE, &clientinlen);
            clientin = base64;
            r = (r1 == SASL_CONTINUE ? SASL_BADPROT : r1);
            base64error = (r == SASL_BADPROT);
	}
    }

    if (r == SASL_OK) {
	/* Start the exchange. */
	r = sasl_server_start(sasl_conn, mech, clientin, clientinlen,
			      &serverout, &serveroutlen);
    }

    while (r == SASL_CONTINUE || (r == SASL_OK && serveroutlen != 0)) {
	if (serveroutlen != 0) {
            /* Encode the server challenge.
             * In sasl_encode64() calls, the fourth argument is the length
             * of the third including the null terminator. */
            r1 = sasl_encode64(serverout, serveroutlen,
                               base64, BASE64_BUF_SIZE+1, NULL);
            if (r1 != SASL_OK)
                r = r1;
	}

	/* Check for failure or success. */
        if (r != SASL_CONTINUE)
            break;

	/* Send the challenge to the client. */
	Reply("%d %s\r\n", NNTP_CONT_SASL,
	      serveroutlen != 0 ? base64 : "=");
	fflush(stdout);

	/* Get the response from the client. */
	r1 = line_read(&NNTPline, PERMaccessconf->clienttimeout,
		      &clientin, &tclientinlen, NULL);
        clientinlen = tclientinlen;
        
        switch (r1) {
	case RTok:
            if (clientinlen <= BASE64_BUF_SIZE)
                break;
	    /* FALLTHROUGH */
	case RTlong:
	    warn("%s response too long in AUTHINFO SASL", Client.host);
            Reply("%d Too long response\r\n", NNTP_FAIL_TERMINATING);
	    ExitWithStats(1, false);
	    break;
	case RTtimeout:
	    warn("%s timeout in AUTHINFO SASL", Client.host);
            /* No answer. */
	    ExitWithStats(1, false);
	    break;
	case RTeof:
	    warn("%s EOF in AUTHINFO SASL", Client.host);
            Reply("%d EOF\r\n", NNTP_FAIL_TERMINATING);
	    ExitWithStats(1, false);
	    break;
	default:
	    warn("%s internal %d in AUTHINFO SASL", Client.host, r);
            Reply("%d Internal error\r\n", NNTP_FAIL_TERMINATING);
	    ExitWithStats(1, false);
	    break;
	}

	/* Check if client cancelled. */
	if (strcmp(clientin, "*") == 0) {
            /* Restart the SASL server in order to be able to reauthenticate.
             * Call that function before the reply because in case of failure,
             * 400 is sent. */
            SASLnewserver();
	    Reply("%d Client cancelled authentication\r\n", NNTP_FAIL_AUTHINFO_BAD);
	    return;
	}

        if (strcmp(clientin, "=") == 0) {
            /* Zero-length answer. */
            clientin = "";
            clientinlen = 0;
        } else {
            /* Decode the response.  On error, SASL_CONTINUE should not be
             * given because we know for sure that we have already received
             * the whole challenge/response.  Use SASL_BADPROT instead,
             * in order to indicate a base64-encoding error. */
            r1 = sasl_decode64(clientin, clientinlen,
                               base64, BASE64_BUF_SIZE, &clientinlen);
            clientin = base64;
            r = (r1 == SASL_CONTINUE ? SASL_BADPROT : r1);
            base64error = (r == SASL_BADPROT);
        }

        /* Do the next step. */
	if (r == SASL_OK) {
	    r = sasl_server_step(sasl_conn, clientin, clientinlen,
				 &serverout, &serveroutlen);
	}
    }

    /* Fetch the username (authorization ID). */
    if (r == SASL_OK) {
	r = sasl_getprop(sasl_conn, SASL_USERNAME, &property);
        canon_user = property;
    }

    /* Grab info about the negotiated layer. */
    if (r == SASL_OK) {
	r = sasl_getprop(sasl_conn, SASL_SSF, &property);
        ssfp = property;
    }

    if (r == SASL_OK) {
	r = sasl_getprop(sasl_conn, SASL_MAXOUTBUF, &property);
        maxoutp = property;
    }

    if (r == SASL_OK) {
        /* Success!
         * First, save info about the negotiated security layer
         * for I/O functions. */
        sasl_ssf = *ssfp;
        sasl_maxout = (*maxoutp == 0 || *maxoutp > NNTP_MAXLEN_COMMAND) ?
            NNTP_MAXLEN_COMMAND : *maxoutp;

        if (sasl_ssf > 1) {
            /* For the forthcoming check of the permissions the client now
             * has, tell the connection is encrypted, so that auth blocks
             * requiring the negotiation of a security layer in readers.conf
             * are properly taken into account.
             * When sasl_ssf equals 1, only data integrity is provided, without
             * any security. */
            encryption_layer_on = true;

            /* Close out any existing article, report group stats.
             * RFC 4643 requires the reset of any knowledge about the client. */
            if (GRPcur) {
                bool boolval;
                ARTclose();
                GRPreport();
                OVctl(OVCACHEFREE, &boolval);
                free(GRPcur);
                GRPcur = NULL;
                if (ARTcount) {
                    syslog(L_NOTICE,
                           "%s exit for AUTHINFO SASL articles %ld groups %ld",
                           Client.host, ARTcount, GRPcount);
                }
                GRPcount = 0;
                PERMgroupmadeinvalid = false;

                /* Reset our read buffer so as to prevent plaintext
                 * command injection. */
                line_reset(&NNTPline);
            }
        }

        PERMgetaccess(false);
        strlcpy(PERMuser, canon_user, sizeof(PERMuser));
        PERMgetpermissions();
        PERMneedauth = false;
        PERMauthorized = true;
        PERMcanauthenticate = false;

        syslog(L_NOTICE, "%s user %s", Client.host, PERMuser);

        if (serveroutlen) {
            Reply("%d %s\r\n", NNTP_OK_SASL, base64);
        } else {
            Reply("%d Authentication succeeded\r\n", NNTP_OK_AUTHINFO);
        }
    } else {
	/* Failure. */
	int resp_code;
	const char *errstring = sasl_errstring(r, NULL, NULL);

	syslog(L_NOTICE, "%s bad_auth", Client.host);

	switch (r) {
	case SASL_BADPROT:
            resp_code = (base64error ? NNTP_ERR_BASE64 : NNTP_FAIL_AUTHINFO_REJECT);
            break;
        case SASL_BADPARAM:
        case SASL_NOTDONE:
	    resp_code = NNTP_FAIL_AUTHINFO_REJECT;
	    break;
	case SASL_NOMECH:
            resp_code = NNTP_ERR_UNAVAILABLE;
            break;
	case SASL_ENCRYPT:
	    resp_code = NNTP_FAIL_PRIVACY_NEEDED;
	    break;
	default:
	    resp_code = NNTP_FAIL_AUTHINFO_BAD;
	    break;
	}

        /* Restart the SASL server in order to be able to reauthenticate.
         * Call that function before the reply because in case of failure,
         * 400 is sent. */
        SASLnewserver();
	Reply("%d %s\r\n",
	      resp_code, errstring ? errstring : "Authentication failed");
    }
}

#endif /* HAVE_SASL */