1 /*
2 * ProFTPD: mod_auth_otp
3 * Copyright (c) 2015-2017 TJ Saunders
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (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 General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18 *
19 * As a special exemption, TJ Saunders and other respective copyright holders
20 * give permission to link this program with OpenSSL, and distribute the
21 * resulting executable, without including the source code for OpenSSL in the
22 * source distribution.
23 *
24 * -----DO NOT EDIT BELOW THIS LINE-----
25 * $Archive: mod_auth_otp.a $
26 * $Libraries: -lcrypto$
27 */
28
29 #include "mod_auth_otp.h"
30 #if defined(HAVE_SFTP)
31 # include "mod_sftp.h"
32 #endif /* HAVE_SFTP */
33 #include "db.h"
34 #include "otp.h"
35
36 /* mod_auth_otp option flags */
37 #define AUTH_OTP_OPT_STANDARD_RESPONSE 0x001
38 #define AUTH_OTP_OPT_REQUIRE_TABLE_ENTRY 0x002
39 #define AUTH_OTP_OPT_DISPLAY_VERIFICATION_CODE 0x004
40
41 #define AUTH_OTP_VERIFICATION_CODE_PROMPT "Verification code: "
42
43 /* From src/response.c */
44 extern pr_response_t *resp_list;
45
46 pool *auth_otp_pool = NULL;
47 int auth_otp_logfd = -1;
48 unsigned long auth_otp_opts = 0UL;
49 module auth_otp_module;
50
51 static authtable auth_otp_authtab[3];
52 static int auth_otp_engine = FALSE;
53 static unsigned int auth_otp_algo = AUTH_OTP_ALGO_TOTP_SHA1;
54 static struct auth_otp_db *dbh = NULL;
55 static config_rec *auth_otp_db_config = NULL;
56 static int auth_otp_auth_code = PR_AUTH_BADPWD;
57
58 /* Necessary prototypes */
59 static int auth_otp_sess_init(void);
60 static int handle_user_otp(pool *p, const char *user, const char *user_otp,
61 int authoritative);
62
63 #if defined(HAVE_SFTP)
64 /* mod_sftp support */
65 static int auth_otp_using_sftp = FALSE;
66 static sftp_kbdint_driver_t auth_otp_kbdint_driver;
67 #endif /* HAVE_SFTP */
68
69 static const char *trace_channel = "auth_otp";
70
71 #if defined(HAVE_SFTP)
auth_otp_kbdint_open(sftp_kbdint_driver_t * driver,const char * user)72 static int auth_otp_kbdint_open(sftp_kbdint_driver_t *driver,
73 const char *user) {
74 const char *tabinfo;
75 int xerrno;
76
77 tabinfo = auth_otp_db_config->argv[0];
78
79 PRIVS_ROOT
80 dbh = auth_otp_db_open(driver->driver_pool, tabinfo);
81 xerrno = errno;
82 PRIVS_RELINQUISH
83
84 if (dbh == NULL) {
85 pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
86 "unable to open AuthOTPTable: %s", strerror(xerrno));
87 errno = xerrno;
88 return -1;
89 }
90
91 driver->driver_pool = make_sub_pool(auth_otp_pool);
92 pr_pool_tag(driver->driver_pool, "AuthOTP keyboard-interactive driver pool");
93
94 return 0;
95 }
96
auth_otp_kbdint_authenticate(sftp_kbdint_driver_t * driver,const char * user)97 static int auth_otp_kbdint_authenticate(sftp_kbdint_driver_t *driver,
98 const char *user) {
99 int authoritative = FALSE, res, xerrno;
100 sftp_kbdint_challenge_t *challenge;
101 unsigned int recvd_count = 0;
102 const char **recvd_responses = NULL, *user_otp = NULL;
103
104 if (auth_otp_authtab[0].auth_flags & PR_AUTH_FL_REQUIRED) {
105 authoritative = TRUE;
106 }
107
108 /* Check first to see if we even have information for this user, for to
109 * use when verifying them. If not, then don't prompt the user for info
110 * that we know, a priori, we cannot verify.
111 */
112 res = auth_otp_db_rlock(dbh);
113 if (res < 0) {
114 (void) pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
115 "failed to read-lock AuthOTPTable: %s", strerror(errno));
116 }
117
118 res = auth_otp_db_have_user_info(driver->driver_pool, dbh, user);
119 xerrno = errno;
120
121 if (auth_otp_db_unlock(dbh) < 0) {
122 (void) pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
123 "failed to unlock AuthOTPTable: %s", strerror(errno));
124 }
125
126 if (res < 0) {
127 (void) pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
128 "no info for user '%s' found in AuthOTPTable, skipping "
129 "SSH2 keyboard-interactive challenge", user);
130 errno = xerrno;
131 return -1;
132 }
133
134 challenge = pcalloc(driver->driver_pool, sizeof(sftp_kbdint_challenge_t));
135 challenge->challenge = pstrdup(driver->driver_pool,
136 AUTH_OTP_VERIFICATION_CODE_PROMPT);
137 challenge->display_response = FALSE;
138
139 if (auth_otp_opts & AUTH_OTP_OPT_DISPLAY_VERIFICATION_CODE) {
140 challenge->display_response = TRUE;
141 }
142
143 if (sftp_kbdint_send_challenge(NULL, NULL, 1, challenge) < 0) {
144 xerrno = errno;
145
146 pr_trace_msg(trace_channel, 3,
147 "error sending keyboard-interactive challenges: %s", strerror(xerrno));
148
149 errno = xerrno;
150 return -1;
151 }
152
153 if (sftp_kbdint_recv_response(driver->driver_pool, 1,
154 &recvd_count, &recvd_responses) < 0) {
155 xerrno = errno;
156
157 pr_trace_msg(trace_channel, 3,
158 "error receiving keyboard-interactive responses: %s", strerror(xerrno));
159
160 errno = xerrno;
161 return -1;
162 }
163
164 user_otp = recvd_responses[0];
165 res = handle_user_otp(driver->driver_pool, user, user_otp, authoritative);
166 if (res == 1) {
167 return 0;
168 }
169
170 errno = EPERM;
171 return -1;
172 }
173
auth_otp_kbdint_close(sftp_kbdint_driver_t * driver)174 static int auth_otp_kbdint_close(sftp_kbdint_driver_t *driver) {
175 if (dbh != NULL) {
176 if (auth_otp_db_close(dbh) < 0) {
177 (void) pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
178 "error closing AuthOTPTable: %s", strerror(errno));
179 }
180
181 dbh = NULL;
182 }
183
184 if (driver->driver_pool) {
185 destroy_pool(driver->driver_pool);
186 driver->driver_pool = NULL;
187 }
188
189 return 0;
190 }
191 #endif /* HAVE_SFTP */
192
check_otp_code(pool * p,const char * user,const char * user_otp,const unsigned char * secret,size_t secret_len,unsigned long counter)193 static int check_otp_code(pool *p, const char *user, const char *user_otp,
194 const unsigned char *secret, size_t secret_len, unsigned long counter) {
195 int res;
196 char code_str[9];
197 unsigned int code;
198
199 switch (auth_otp_algo) {
200 case AUTH_OTP_ALGO_TOTP_SHA1:
201 case AUTH_OTP_ALGO_TOTP_SHA256:
202 case AUTH_OTP_ALGO_TOTP_SHA512:
203 res = auth_otp_totp(p, secret, secret_len, counter, auth_otp_algo, &code);
204 if (res < 0) {
205 pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
206 "error generating TOTP code for user '%s': %s", user,
207 strerror(errno));
208 }
209 break;
210
211 case AUTH_OTP_ALGO_HOTP:
212 res = auth_otp_hotp(p, secret, secret_len, counter, &code);
213 if (res < 0) {
214 pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
215 "error generating HOTP code for user '%s': %s", user,
216 strerror(errno));
217 }
218 break;
219
220 default:
221 errno = EINVAL;
222 res = -1;
223 break;
224 }
225
226 if (res < 0) {
227 return -1;
228 }
229
230 memset(code_str, '\0', sizeof(code_str));
231
232 /* Note: If/when more than 6 digits are needed, the following format string
233 * would need to change to match.
234 */
235 pr_snprintf(code_str, sizeof(code_str)-1, "%06u", code);
236
237 pr_trace_msg(trace_channel, 13,
238 "computed code '%s', client sent code '%s'", code_str, user_otp);
239
240 res = pr_auth_check(p, code_str, user, user_otp);
241 if (res == PR_AUTH_OK ||
242 res == PR_AUTH_RFC2228_OK) {
243 return 0;
244 }
245
246 return -1;
247 }
248
update_otp_counter(pool * p,const char * user,unsigned long next_counter)249 static int update_otp_counter(pool *p, const char *user,
250 unsigned long next_counter) {
251 int res = 0;
252
253 if (auth_otp_algo == AUTH_OTP_ALGO_HOTP) {
254 int lock;
255
256 lock = auth_otp_db_wlock(dbh);
257 if (lock < 0) {
258 (void) pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
259 "failed to write-lock AuthOTPTable: %s", strerror(errno));
260 }
261
262 res = auth_otp_db_update_counter(dbh, user, next_counter);
263 if (res < 0) {
264 (void) pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
265 "error updating AuthOTPTable for HOTP counter for user '%s': %s",
266 user, strerror(errno));
267 }
268
269 lock = auth_otp_db_unlock(dbh);
270 if (lock < 0) {
271 (void) pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
272 "failed to unlock AuthOTPTable: %s", strerror(errno));
273 }
274 }
275
276 return res;
277 }
278
279 /* Sets the auth_otp_auth_code variable upon failure. */
handle_user_otp(pool * p,const char * user,const char * user_otp,int authoritative)280 static int handle_user_otp(pool *p, const char *user, const char *user_otp,
281 int authoritative) {
282 int res = 0, xerrno = 0;
283 const unsigned char *secret = NULL;
284 size_t secret_len = 0;
285 unsigned long counter = 0, *counter_ptr = NULL, next_counter = 0;
286
287 if (user_otp == NULL ||
288 (strlen(user_otp) == 0)) {
289 pr_trace_msg(trace_channel, 1,
290 "no OTP code provided by user, rejecting");
291 (void) pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
292 "FAILED: user '%s' provided invalid OTP code", user);
293 auth_otp_auth_code = PR_AUTH_BADPWD;
294 return -1;
295 }
296
297 switch (auth_otp_algo) {
298 case AUTH_OTP_ALGO_TOTP_SHA1:
299 case AUTH_OTP_ALGO_TOTP_SHA256:
300 case AUTH_OTP_ALGO_TOTP_SHA512: {
301 counter = (unsigned long) time(NULL);
302 break;
303 }
304
305 case AUTH_OTP_ALGO_HOTP:
306 counter_ptr = &counter;
307 break;
308
309 default:
310 pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
311 "unsupported AuthOTPAlgorithm configured");
312 return 0;
313 }
314
315 res = auth_otp_db_rlock(dbh);
316 if (res < 0) {
317 (void) pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
318 "failed to read-lock AuthOTPTable: %s", strerror(errno));
319 }
320
321 res = auth_otp_db_get_user_info(p, dbh, user, &secret, &secret_len,
322 counter_ptr);
323 xerrno = errno;
324
325 if (auth_otp_db_unlock(dbh) < 0) {
326 (void) pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
327 "failed to unlock AuthOTPTable: %s", strerror(errno));
328 }
329
330 if (res < 0) {
331 if (xerrno == ENOENT) {
332 pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
333 "user '%s' has no OTP info in AuthOTPTable", user);
334
335 } else {
336 pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
337 "unable to retrieve OTP info for user '%s': %s", user,
338 strerror(xerrno));
339 }
340
341 /* If there was no entry found in the table (errno = ENOENT), should we
342 * returned ERROR or DECLINED?
343 *
344 * If we are not authoritative, then we returned DECLINED, regardless of
345 * the errno value, in order to allow other modules a chance at handling
346 * the authentication. This module can only be "authoritative" about
347 * OTP codes it can generate, and if there is no entry in the table,
348 * REQUIRE_TABLE_ENTRY option or not, we cannot generate a code for
349 * comparisons.
350 *
351 * If we ARE authoritative, and the REQUIRE_TABLE_ENTRY option is in
352 * effect, then we return ERROR -- this is how we require OTP codes for
353 * ALL users. Otherwise we return DECLINED, despite being authoritative,
354 * because again, we don't have the necessary data for computing the code.
355 */
356
357 if (authoritative) {
358 if (auth_otp_opts & AUTH_OTP_OPT_REQUIRE_TABLE_ENTRY) {
359 (void) pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
360 "FAILED: user '%s' does not have entry in OTP tables", user);
361 auth_otp_auth_code = PR_AUTH_BADPWD;
362 return -1;
363 }
364 }
365
366 return 0;
367 }
368
369 res = check_otp_code(p, user, user_otp, secret, secret_len, counter);
370 if (res == 0) {
371 pr_memscrub((char *) secret, secret_len);
372
373 (void) pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
374 "SUCCESS: user '%s' provided valid OTP code", user);
375
376 /* XXX Update state with details about the expected OTP found,
377 * e.g. for clock drift.
378 */
379 update_otp_counter(p, user, counter + 1);
380 return 1;
381 }
382
383 /* We SHOULD be allowing for clock skew/counter drift here. We
384 * currently check one window ahead AND behind; is this policy too lenient?
385 * RFC 6238, Section 5.2 recommends one window for network transmission
386 * delay (which assumes that the client's OTP is always "behind" the
387 * server's OTP).
388 *
389 * By checking one window ahead/behind, we allow for clock skew in either
390 * direction: server ahead of client (most likely), client ahead of server.
391 */
392 pr_trace_msg(trace_channel, 3,
393 "current counter check failed, checking one window behind");
394
395 switch (auth_otp_algo) {
396 case AUTH_OTP_ALGO_TOTP_SHA1:
397 case AUTH_OTP_ALGO_TOTP_SHA256:
398 case AUTH_OTP_ALGO_TOTP_SHA512:
399 next_counter = counter - AUTH_OTP_TOTP_TIMESTEP_SECS;
400 break;
401
402 case AUTH_OTP_ALGO_HOTP:
403 next_counter = counter - 1;
404 break;
405 }
406
407 res = check_otp_code(p, user, user_otp, secret, secret_len, next_counter);
408 if (res == 0) {
409 pr_memscrub((char *) secret, secret_len);
410
411 pr_trace_msg(trace_channel, 3,
412 "counter check SUCCEEDED for one counter window behind; client is "
413 "out-of-sync");
414
415 (void) pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
416 "SUCCESS: user '%s' provided valid OTP code", user);
417
418 /* XXX Update state with details about the expected OTP found,
419 * e.g. for clock drift, event counter increment, etc.
420 *
421 * Note that here, the client is "behind". Expected for TOTP, but not
422 * for HOTP. Hmm.
423 */
424 update_otp_counter(p, user, counter + 1);
425 return 1;
426 }
427
428 pr_trace_msg(trace_channel, 3,
429 "counter one window ahead check failed, checking one window ahead");
430
431 switch (auth_otp_algo) {
432 case AUTH_OTP_ALGO_TOTP_SHA1:
433 case AUTH_OTP_ALGO_TOTP_SHA256:
434 case AUTH_OTP_ALGO_TOTP_SHA512:
435 next_counter = counter + AUTH_OTP_TOTP_TIMESTEP_SECS;
436 break;
437
438 case AUTH_OTP_ALGO_HOTP:
439 next_counter = counter + 1;
440 break;
441 }
442
443 res = check_otp_code(p, user, user_otp, secret, secret_len, next_counter);
444 if (res == 0) {
445 pr_memscrub((char *) secret, secret_len);
446
447 pr_trace_msg(trace_channel, 3,
448 "counter check SUCCEEDED for one counter window ahead; client is "
449 "out-of-sync");
450
451 (void) pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
452 "SUCCESS: user '%s' provided valid OTP code", user);
453
454 /* XXX Update state with details about the expected OTP found,
455 * e.g. for clock drift, event counter increment, etc.
456 *
457 * Note that here, the client is "ahead". NOT expected for TOTP, but is
458 * for HOTP. Hmm.
459 */
460 update_otp_counter(p, user, counter + 1);
461 return 1;
462 }
463
464 pr_memscrub((char *) secret, secret_len);
465
466 if (authoritative) {
467 (void) pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
468 "FAILED: user '%s' provided invalid OTP code", user);
469 auth_otp_auth_code = PR_AUTH_BADPWD;
470 return -1;
471 }
472
473 return 0;
474 }
475
476 /* Authentication handlers
477 */
478
auth_otp_auth(cmd_rec * cmd)479 MODRET auth_otp_auth(cmd_rec *cmd) {
480 int authoritative = FALSE, res = 0;
481 char *user = NULL, *user_otp = NULL;
482
483 if (auth_otp_engine == FALSE ||
484 dbh == NULL) {
485 return PR_DECLINED(cmd);
486 }
487
488 user = cmd->argv[0];
489 user_otp = cmd->argv[1];
490
491 /* Figure out our default return style: whether or not we should allow
492 * other auth modules a shot at this user or not is controlled by adding
493 * '*' to a module name in the AuthOrder directive. By default, auth
494 * modules are not authoritative, and allow other auth modules a chance at
495 * authenticating the user. This is not the most secure configuration, but
496 * it allows things like AuthUserFile to work "out of the box".
497 */
498 if (auth_otp_authtab[0].auth_flags & PR_AUTH_FL_REQUIRED) {
499 authoritative = TRUE;
500 }
501
502 #if defined(HAVE_SFTP)
503 if (auth_otp_using_sftp) {
504 const char *proto = NULL;
505
506 proto = pr_session_get_protocol(0);
507 if (strcmp(proto, "ssh2") == 0) {
508 /* We should already have done the keyboard-interactive challenge by
509 * this point in the session.
510 */
511
512 if (auth_otp_auth_code != PR_AUTH_OK &&
513 auth_otp_auth_code != PR_AUTH_RFC2228_OK) {
514 if (authoritative) {
515 /* Indicate ERROR. */
516 res = -1;
517
518 } else {
519 /* Indicate DECLINED. */
520 res = 0;
521 }
522
523 } else {
524 /* Indicate HANDLED. */
525 res = 1;
526 }
527
528 } else {
529 res = handle_user_otp(cmd->tmp_pool, user, user_otp, authoritative);
530 }
531
532 } else {
533 res = handle_user_otp(cmd->tmp_pool, user, user_otp, authoritative);
534 }
535 #else
536 res = handle_user_otp(cmd->tmp_pool, user, user_otp, authoritative);
537 #endif /* HAVE_SFTP */
538
539 if (res == 1) {
540 session.auth_mech = "mod_auth_otp.c";
541 return PR_HANDLED(cmd);
542
543 } else if (res < 0) {
544 return PR_ERROR_INT(cmd, auth_otp_auth_code);
545 }
546
547 return PR_DECLINED(cmd);
548 }
549
auth_otp_chkpass(cmd_rec * cmd)550 MODRET auth_otp_chkpass(cmd_rec *cmd) {
551 const char *real_otp, *user, *user_otp;
552
553 if (auth_otp_engine == FALSE) {
554 return PR_DECLINED(cmd);
555 }
556
557 real_otp = cmd->argv[0];
558 user = cmd->argv[1];
559 user_otp = cmd->argv[2];
560
561 if (strcmp(real_otp, user_otp) == 0) {
562 return PR_HANDLED(cmd);
563 }
564
565 switch (auth_otp_algo) {
566 case AUTH_OTP_ALGO_TOTP_SHA1:
567 pr_trace_msg(trace_channel, 9,
568 "expected TOTP-SHA1 '%s', got '%s' for user '%s'", real_otp, user_otp,
569 user);
570 break;
571
572 case AUTH_OTP_ALGO_TOTP_SHA256:
573 pr_trace_msg(trace_channel, 9,
574 "expected TOTP-SHA256 '%s', got '%s' for user '%s'", real_otp, user_otp,
575 user);
576 break;
577
578 case AUTH_OTP_ALGO_TOTP_SHA512:
579 pr_trace_msg(trace_channel, 9,
580 "expected TOTP-SHA512 '%s', got '%s' for user '%s'", real_otp, user_otp,
581 user);
582 break;
583
584 case AUTH_OTP_ALGO_HOTP:
585 pr_trace_msg(trace_channel, 9,
586 "expected HOTP '%s', got '%s' for user '%s'", real_otp, user_otp, user);
587 break;
588 }
589
590 return PR_DECLINED(cmd);
591 }
592
593 /* Configuration handlers
594 */
595
596 /* usage: AuthOTPAlgorithm algo */
set_authotpalgo(cmd_rec * cmd)597 MODRET set_authotpalgo(cmd_rec *cmd) {
598 int algo = -1;
599 config_rec *c = NULL;
600
601 CHECK_ARGS(cmd, 1);
602 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
603
604 if (strcasecmp(cmd->argv[1], "hotp") == 0) {
605 algo = AUTH_OTP_ALGO_HOTP;
606
607 } else if (strcasecmp(cmd->argv[1], "totp") == 0 ||
608 strcasecmp(cmd->argv[1], "totp-sha1") == 0) {
609 algo = AUTH_OTP_ALGO_TOTP_SHA1;
610
611 #ifdef HAVE_SHA256_OPENSSL
612 } else if (strcasecmp(cmd->argv[1], "totp-sha256") == 0) {
613 algo = AUTH_OTP_ALGO_TOTP_SHA256;
614 #endif /* SHA256 OpenSSL support */
615
616 #ifdef HAVE_SHA512_OPENSSL
617 } else if (strcasecmp(cmd->argv[1], "totp-sha512") == 0) {
618 algo = AUTH_OTP_ALGO_TOTP_SHA512;
619 #endif /* SHA512 OpenSSL support */
620
621 } else {
622 CONF_ERROR(cmd, "expected supported OTP algorithm");
623 }
624
625 c = add_config_param(cmd->argv[0], 1, NULL);
626 c->argv[0] = pcalloc(c->pool, sizeof(int));
627 *((int *) c->argv[0]) = algo;
628
629 return PR_HANDLED(cmd);
630 }
631
632 /* usage: AuthOTPEngine on|off */
set_authotpengine(cmd_rec * cmd)633 MODRET set_authotpengine(cmd_rec *cmd) {
634 int engine = -1;
635 config_rec *c = NULL;
636
637 CHECK_ARGS(cmd, 1);
638 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
639
640 engine = get_boolean(cmd, 1);
641 if (engine == -1) {
642 CONF_ERROR(cmd, "expected Boolean parameter");
643 }
644
645 c = add_config_param(cmd->argv[0], 1, NULL);
646 c->argv[0] = pcalloc(c->pool, sizeof(int));
647 *((int *) c->argv[0]) = engine;
648
649 return PR_HANDLED(cmd);
650 }
651
652 /* usage: AuthOTPLog path|"none" */
set_authotplog(cmd_rec * cmd)653 MODRET set_authotplog(cmd_rec *cmd) {
654 CHECK_ARGS(cmd, 1);
655 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
656
657 add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
658 return PR_HANDLED(cmd);
659 }
660
661 /* usage: AuthOTPOptions opt1 opt2 ... */
set_authotpoptions(cmd_rec * cmd)662 MODRET set_authotpoptions(cmd_rec *cmd) {
663 config_rec *c = NULL;
664 register unsigned int i = 0;
665 unsigned long opts = 0UL;
666
667 if (cmd->argc-1 == 0) {
668 CONF_ERROR(cmd, "wrong number of parameters");
669 }
670
671 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
672
673 c = add_config_param(cmd->argv[0], 1, NULL);
674
675 for (i = 1; i < cmd->argc; i++) {
676 if (strcmp(cmd->argv[i], "StandardResponse") == 0) {
677 opts |= AUTH_OTP_OPT_STANDARD_RESPONSE;
678
679 } else if (strcmp(cmd->argv[i], "RequireTableEntry") == 0) {
680 opts |= AUTH_OTP_OPT_REQUIRE_TABLE_ENTRY;
681
682 } else if (strcmp(cmd->argv[i], "DisplayVerificationCode") == 0) {
683 opts |= AUTH_OTP_OPT_DISPLAY_VERIFICATION_CODE;
684
685 } else {
686 CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": unknown AuthOTPOption: '",
687 cmd->argv[i], "'", NULL));
688 }
689 }
690
691 c->argv[0] = pcalloc(c->pool, sizeof(unsigned long));
692 *((unsigned long *) c->argv[0]) = opts;
693
694 return PR_HANDLED(cmd);
695 }
696
697 /* usage: AuthOTPTable sql:/... */
set_authotptable(cmd_rec * cmd)698 MODRET set_authotptable(cmd_rec *cmd) {
699 char *ptr = NULL;
700
701 CHECK_ARGS(cmd, 1);
702 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
703
704 /* Separate the parameter into the separate pieces. The parameter is
705 * given as one string to enhance its similarity to URL syntax.
706 */
707 ptr = strchr(cmd->argv[1], ':');
708 if (ptr == NULL) {
709 CONF_ERROR(cmd, "badly formatted parameter");
710 }
711
712 if (strncasecmp(cmd->argv[1], "sql:/", 5) != 0) {
713 CONF_ERROR(cmd, "badly formatted parameter");
714 }
715
716 *ptr++ = '\0';
717
718 add_config_param_str(cmd->argv[0], 1, ptr);
719 return PR_HANDLED(cmd);
720 }
721
722 /* usage: AuthOTPTableLock path */
set_authotptablelock(cmd_rec * cmd)723 MODRET set_authotptablelock(cmd_rec *cmd) {
724 CHECK_ARGS(cmd, 1);
725 CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
726
727 add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
728 return PR_HANDLED(cmd);
729 }
730
731 /* Command handlers
732 */
733
auth_otp_post_pass(cmd_rec * cmd)734 MODRET auth_otp_post_pass(cmd_rec *cmd) {
735 if (auth_otp_engine == FALSE) {
736 return PR_DECLINED(cmd);
737 }
738
739 if (dbh != NULL) {
740 if (auth_otp_db_close(dbh) < 0) {
741 (void) pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
742 "error closing AuthOTPTable: %s", strerror(errno));
743 }
744
745 dbh = NULL;
746 }
747
748 return PR_DECLINED(cmd);
749 }
750
auth_otp_pre_user(cmd_rec * cmd)751 MODRET auth_otp_pre_user(cmd_rec *cmd) {
752 const char *tabinfo;
753 int xerrno;
754
755 if (auth_otp_engine == FALSE) {
756 return PR_DECLINED(cmd);
757 }
758
759 tabinfo = auth_otp_db_config->argv[0];
760
761 PRIVS_ROOT
762 dbh = auth_otp_db_open(auth_otp_pool, tabinfo);
763 xerrno = errno;
764 PRIVS_RELINQUISH
765
766 if (dbh == NULL) {
767 pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
768 "unable to open AuthOTPTable: %s", strerror(xerrno));
769 }
770
771 return PR_DECLINED(cmd);
772 }
773
auth_otp_post_user(cmd_rec * cmd)774 MODRET auth_otp_post_user(cmd_rec *cmd) {
775 const char *user;
776
777 if (auth_otp_engine == FALSE ||
778 dbh == NULL) {
779 return PR_DECLINED(cmd);
780 }
781
782 user = cmd->argv[1];
783
784 /* We want to respond using the normal 331 response code, BUT we'd like
785 * to change the response message. Note that this might be considered
786 * an information leak, i.e. we're leaking the server's expectation of
787 * OTPs from the connecting client.
788 */
789
790 if (!(auth_otp_opts & AUTH_OTP_OPT_STANDARD_RESPONSE)) {
791 pr_response_clear(&resp_list);
792 #if defined(HAVE_SFTP)
793 /* Note: for some reason, when building with mod_sftp, the '_' function
794 * used for localization is not resolvable by the linker, thus we
795 * work around the problem. For now.
796 */
797 pr_response_add(R_331, "One-time password required for %s", user);
798 #else
799 pr_response_add(R_331, _("One-time password required for %s"), user);
800 #endif /* HAVE_SFTP */
801 }
802
803 return PR_DECLINED(cmd);
804 }
805
806 /* Event listeners
807 */
808
auth_otp_exit_ev(const void * event_data,void * user_data)809 static void auth_otp_exit_ev(const void *event_data, void *user_data) {
810 if (dbh != NULL) {
811 (void) auth_otp_db_close(dbh);
812 dbh = NULL;
813 }
814 }
815
816 #if defined(PR_SHARED_MODULE)
auth_otp_mod_unload_ev(const void * event_data,void * user_data)817 static void auth_otp_mod_unload_ev(const void *event_data, void *user_data) {
818 if (strcmp("mod_auth_otp.c", (const char *) event_data) == 0) {
819 # if defined(HAVE_SFTP)
820 if (pr_module_exists("mod_sftp.c") == TRUE) {
821 sftp_kbdint_unregister_driver("auth_otp");
822 }
823 # endif /* HAVE_SFTP */
824 pr_event_unregister(&auth_otp_module, NULL, NULL);
825 }
826 }
827 #endif /* PR_SHARED_MODULE */
828
auth_otp_sess_reinit_ev(const void * event_data,void * user_data)829 static void auth_otp_sess_reinit_ev(const void *event_data, void *user_data) {
830 int res;
831
832 /* A HOST command changed the main_server pointer; reinitialize ourselves. */
833
834 pr_event_unregister(&auth_otp_module, "core.exit", auth_otp_exit_ev);
835 pr_event_unregister(&auth_otp_module, "core.session-reinit",
836 auth_otp_sess_reinit_ev);
837
838 auth_otp_engine = FALSE;
839 auth_otp_opts = 0UL;
840 auth_otp_algo = AUTH_OTP_ALGO_TOTP_SHA1;
841 auth_otp_db_config = NULL;
842
843 if (auth_otp_logfd >= 0) {
844 (void) close(auth_otp_logfd);
845 auth_otp_logfd = -1;
846 }
847
848 #if defined(HAVE_SFTP)
849 auth_otp_using_sftp = FALSE;
850 (void) sftp_kbdint_register_driver("auth_otp", &auth_otp_kbdint_driver);
851 #endif /* HAVE_SFTP */
852
853 if (auth_otp_pool != NULL) {
854 destroy_pool(auth_otp_pool);
855 }
856
857 res = auth_otp_sess_init();
858 if (res < 0) {
859 pr_session_disconnect(&auth_otp_module,
860 PR_SESS_DISCONNECT_SESSION_INIT_FAILED, NULL);
861 }
862 }
863
864 /* Initialization routines
865 */
866
auth_otp_init(void)867 static int auth_otp_init(void) {
868
869 #if defined(PR_SHARED_MODULE)
870 pr_event_register(&auth_otp_module, "core.module-unload",
871 auth_otp_mod_unload_ev, NULL);
872 #endif /* PR_SHARED_MODULE */
873
874 if (pr_module_exists("mod_sql.c") == FALSE) {
875 pr_log_pri(PR_LOG_NOTICE, MOD_AUTH_OTP_VERSION
876 ": Missing required 'mod_sql.c'; HOTP/TOTP logins will FAIL");
877 }
878
879 #if defined(HAVE_SFTP)
880 auth_otp_using_sftp = pr_module_exists("mod_sftp.c");
881 if (auth_otp_using_sftp) {
882 /* Prepare our keyboard-interactive driver. */
883 memset(&auth_otp_kbdint_driver, 0, sizeof(auth_otp_kbdint_driver));
884 auth_otp_kbdint_driver.open = auth_otp_kbdint_open;
885 auth_otp_kbdint_driver.authenticate = auth_otp_kbdint_authenticate;
886 auth_otp_kbdint_driver.close = auth_otp_kbdint_close;
887
888 if (sftp_kbdint_register_driver("auth_otp", &auth_otp_kbdint_driver) < 0) {
889 int xerrno = errno;
890
891 pr_log_pri(PR_LOG_NOTICE, MOD_AUTH_OTP_VERSION
892 ": notice: error registering 'keyboard-interactive' driver: %s",
893 strerror(xerrno));
894
895 errno = xerrno;
896 return -1;
897 }
898
899 } else {
900 pr_log_debug(DEBUG1, MOD_AUTH_OTP_VERSION
901 ": mod_sftp not loaded, skipping keyboard-interactive support");
902 }
903 #endif /* HAVE_SFTP */
904
905 return 0;
906 }
907
auth_otp_sess_init(void)908 static int auth_otp_sess_init(void) {
909 config_rec *c;
910
911 pr_event_register(&auth_otp_module, "core.session-reinit",
912 auth_otp_sess_reinit_ev, NULL);
913
914 if (pr_auth_add_auth_only_module("mod_auth_otp.c") < 0 &&
915 errno != EEXIST) {
916 pr_log_pri(PR_LOG_NOTICE, MOD_AUTH_OTP_VERSION
917 ": unable to add 'mod_auth_otp.c' as an auth-only module: %s",
918 strerror(errno));
919
920 errno = EPERM;
921 return -1;
922 }
923
924 /* XXX Can we handle both FTP and SSH2 connections in the same module? */
925
926 c = find_config(main_server->conf, CONF_PARAM, "AuthOTPEngine", FALSE);
927 if (c != NULL) {
928 auth_otp_engine = *((int *) c->argv[0]);
929 }
930
931 if (auth_otp_engine == FALSE) {
932 #if defined(HAVE_SFTP)
933 if (auth_otp_using_sftp) {
934 sftp_kbdint_unregister_driver("auth_otp");
935 }
936 #endif /* HAVE_SFTP */
937 return 0;
938 }
939
940 c = find_config(main_server->conf, CONF_PARAM, "AuthOTPLog", FALSE);
941 if (c != NULL) {
942 char *path;
943
944 path = c->argv[0];
945 if (strncasecmp(path, "none", 5) != 0) {
946 int res, xerrno;
947
948 pr_signals_block();
949 PRIVS_ROOT
950 res = pr_log_openfile(path, &auth_otp_logfd, 0600);
951 xerrno = errno;
952 PRIVS_RELINQUISH
953 pr_signals_unblock();
954
955 if (res < 0) {
956 if (res == -1) {
957 pr_log_pri(PR_LOG_NOTICE, MOD_AUTH_OTP_VERSION
958 ": notice: unable to open AuthOTPLog '%s': %s", path,
959 strerror(xerrno));
960
961 } else if (res == PR_LOG_WRITABLE_DIR) {
962 pr_log_pri(PR_LOG_WARNING, MOD_AUTH_OTP_VERSION
963 ": notice: unable to open AuthOTPLog '%s': parent directory is "
964 "world-writable", path);
965
966 } else if (res == PR_LOG_SYMLINK) {
967 pr_log_pri(PR_LOG_WARNING, MOD_AUTH_OTP_VERSION
968 ": notice: unable to open AuthOTPLog '%s': cannot log to a symlink",
969 path);
970 }
971 }
972 }
973 }
974
975 c = find_config(main_server->conf, CONF_PARAM, "AuthOTPTable", FALSE);
976 if (c == NULL) {
977 pr_log_writefile(auth_otp_logfd, MOD_AUTH_OTP_VERSION,
978 "missing required AuthOTPTable directive, disabling module");
979 pr_log_pri(PR_LOG_NOTICE, MOD_AUTH_OTP_VERSION
980 ": missing required AuthOTPTable directive, disabling module");
981 auth_otp_engine = FALSE;
982 (void) close(auth_otp_logfd);
983 auth_otp_logfd = -1;
984
985 #if defined(HAVE_SFTP)
986 if (auth_otp_using_sftp) {
987 sftp_kbdint_unregister_driver("auth_otp");
988 }
989 #endif /* HAVE_SFTP */
990
991 return 0;
992 }
993 auth_otp_db_config = c;
994
995 auth_otp_pool = make_sub_pool(session.pool);
996 pr_pool_tag(auth_otp_pool, MOD_AUTH_OTP_VERSION);
997
998 c = find_config(main_server->conf, CONF_PARAM, "AuthOTPAlgorithm", FALSE);
999 if (c != NULL) {
1000 auth_otp_algo = *((int *) c->argv[0]);
1001 }
1002
1003 c = find_config(main_server->conf, CONF_PARAM, "AuthOTPOptions", FALSE);
1004 while (c != NULL) {
1005 unsigned long opts = 0;
1006
1007 pr_signals_handle();
1008
1009 opts = *((unsigned long *) c->argv[0]);
1010 auth_otp_opts |= opts;
1011
1012 c = find_config_next(c, c->next, CONF_PARAM, "AuthOTPOptions", FALSE);
1013 }
1014
1015 pr_event_register(&auth_otp_module, "core.exit", auth_otp_exit_ev, NULL);
1016 return 0;
1017 }
1018
1019 static cmdtable auth_otp_cmdtab[] = {
1020 { PRE_CMD, C_USER, G_NONE, auth_otp_pre_user, FALSE, FALSE },
1021 { POST_CMD, C_USER, G_NONE, auth_otp_post_user, FALSE, FALSE },
1022 { POST_CMD, C_PASS, G_NONE, auth_otp_post_pass, FALSE, FALSE },
1023 { POST_CMD_ERR, C_PASS, G_NONE, auth_otp_post_pass, FALSE, FALSE },
1024 { 0, NULL },
1025 };
1026
1027 static authtable auth_otp_authtab[] = {
1028 { 0, "auth", auth_otp_auth },
1029 { 0, "check", auth_otp_chkpass },
1030 { 0, NULL, NULL }
1031 };
1032
1033 static conftable auth_otp_conftab[] = {
1034 { "AuthOTPAlgorithm", set_authotpalgo, NULL },
1035 { "AuthOTPEngine", set_authotpengine, NULL },
1036 { "AuthOTPLog", set_authotplog, NULL },
1037 { "AuthOTPOptions", set_authotpoptions, NULL },
1038 { "AuthOTPTable", set_authotptable, NULL },
1039 { "AuthOTPTableLock", set_authotptablelock, NULL },
1040 { NULL, NULL, NULL }
1041 };
1042
1043 module auth_otp_module = {
1044 NULL, NULL,
1045
1046 /* Module API version */
1047 0x20,
1048
1049 /* Module name */
1050 "auth_otp",
1051
1052 /* Module configuration handler table */
1053 auth_otp_conftab,
1054
1055 /* Module command handler table */
1056 auth_otp_cmdtab,
1057
1058 /* Module authentication handler table */
1059 auth_otp_authtab,
1060
1061 /* Module initialization */
1062 auth_otp_init,
1063
1064 /* Session initialization */
1065 auth_otp_sess_init,
1066
1067 /* Module version */
1068 MOD_AUTH_OTP_VERSION
1069 };
1070