source: src/router/proftpd/contrib/mod_sql_passwd.c @ 14677

Last change on this file since 14677 was 14677, checked in by BrainSlayer, 3 years ago

missing files

File size: 17.7 KB
Line 
1/*
2 * ProFTPD: mod_sql_passwd -- Various SQL password handlers
3 * Copyright (c) 2009-2010 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307, 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
22 * the source distribution.
23 *
24 * $Id: mod_sql_passwd.c,v 1.10 2010/02/01 19:20:05 castaglia Exp $
25 */
26
27#include "conf.h"
28#include "privs.h"
29#include "mod_sql.h"
30
31#define MOD_SQL_PASSWD_VERSION          "mod_sql_passwd/0.2"
32
33/* Make sure the version of proftpd is as necessary. */
34#if PROFTPD_VERSION_NUMBER < 0x0001030302
35# error "ProFTPD 1.3.3rc2 or later required"
36#endif
37
38#if !defined(HAVE_OPENSSL) && !defined(PR_USE_OPENSSL)
39# error "OpenSSL support required (--enable-openssl)"
40#else
41# include <openssl/evp.h>
42#endif
43
44module sql_passwd_module;
45
46static int sql_passwd_engine = FALSE;
47
48#define SQL_PASSWD_USE_BASE64           1
49#define SQL_PASSWD_USE_HEX_LC           2
50#define SQL_PASSWD_USE_HEX_UC           3
51static unsigned int sql_passwd_encoding = SQL_PASSWD_USE_HEX_LC;
52
53static char *sql_passwd_salt = NULL;
54static size_t sql_passwd_salt_len = 0;
55static unsigned int sql_passwd_salt_append = TRUE;
56
57static cmd_rec *sql_passwd_cmd_create(pool *parent_pool, int argc, ...) {
58  pool *cmd_pool = NULL;
59  cmd_rec *cmd = NULL;
60  register unsigned int i = 0;
61  va_list argp;
62 
63  cmd_pool = make_sub_pool(parent_pool);
64  cmd = (cmd_rec *) pcalloc(cmd_pool, sizeof(cmd_rec));
65  cmd->pool = cmd_pool;
66 
67  cmd->argc = argc;
68  cmd->argv = (char **) pcalloc(cmd->pool, argc * sizeof(char *));
69
70  /* Hmmm... */
71  cmd->tmp_pool = cmd->pool;
72
73  va_start(argp, argc);
74  for (i = 0; i < argc; i++)
75    cmd->argv[i] = va_arg(argp, char *);
76  va_end(argp);
77
78  return cmd;
79}
80
81static char *sql_passwd_get_str(pool *p, char *str) {
82  cmdtable *cmdtab;
83  cmd_rec *cmd;
84  modret_t *res;
85
86  if (strlen(str) == 0)
87    return str;
88
89  /* Find the cmdtable for the sql_escapestr command. */
90  cmdtab = pr_stash_get_symbol(PR_SYM_HOOK, "sql_escapestr", NULL, NULL);
91  if (cmdtab == NULL) {
92    pr_log_debug(DEBUG2, MOD_SQL_PASSWD_VERSION
93      ": unable to find SQL hook symbol 'sql_escapestr'");
94    return str;
95  }
96
97  cmd = sql_passwd_cmd_create(p, 1, pr_str_strip(p, str));
98
99  /* Call the handler. */
100  res = pr_module_call(cmdtab->m, cmdtab->handler, cmd);
101
102  /* Check the results. */
103  if (MODRET_ISERROR(res)) {
104    pr_log_debug(DEBUG0, MOD_SQL_PASSWD_VERSION
105      ": error executing 'sql_escapestring'");
106    return str;
107  }
108
109  return res->data;
110}
111
112static modret_t *sql_passwd_auth(cmd_rec *cmd, const char *plaintext,
113    const char *ciphertext, const char *digest) {
114  EVP_MD_CTX md_ctxt;
115  EVP_ENCODE_CTX base64_ctxt;
116  const EVP_MD *md;
117
118  /* According to RATS, the output buffer (buf) for EVP_EncodeBlock() needs to
119   * be 4/3 the size of the input buffer (mdval).  Let's make it easy, and
120   * use an output buffer that's twice the size of the input buffer.
121   */
122  unsigned char buf[EVP_MAX_MD_SIZE*2+1], mdval[EVP_MAX_MD_SIZE];
123  unsigned int mdlen;
124
125  char *copytext;               /* temporary copy of the ciphertext string */
126
127  if (!sql_passwd_engine) {
128    return PR_ERROR_INT(cmd, PR_AUTH_ERROR);
129  }
130
131  /* We need a copy of the ciphertext. */
132  copytext = pstrdup(cmd->tmp_pool, ciphertext);
133
134  OpenSSL_add_all_digests();
135
136  md = EVP_get_digestbyname(digest);
137  if (md == NULL) {
138    sql_log(DEBUG_WARN, "no such digest '%s' supported", digest);
139    return PR_ERROR_INT(cmd, PR_AUTH_BADPWD);
140  }
141
142  EVP_DigestInit(&md_ctxt, md);
143
144  /* If a salt is configured, do we prepend the salt as a prefix (i.e. throw
145   * it into the digest before the user-supplied password) or append it as a
146   * suffix?
147   */
148
149  if (sql_passwd_salt_len > 0 &&
150      sql_passwd_salt_append == FALSE) {
151    /* If we have salt data, add it to the mix. */
152    pr_log_debug(DEBUG9, MOD_SQL_PASSWD_VERSION
153      ": adding %lu bytes of salt data", (unsigned long) sql_passwd_salt_len);
154    EVP_DigestUpdate(&md_ctxt, (unsigned char *) sql_passwd_salt,
155      sql_passwd_salt_len);
156  }
157
158  EVP_DigestUpdate(&md_ctxt, plaintext, strlen(plaintext));
159
160  if (sql_passwd_salt_len > 0 &&
161      sql_passwd_salt_append == TRUE) {
162    /* If we have salt data, add it to the mix. */
163    pr_log_debug(DEBUG9, MOD_SQL_PASSWD_VERSION
164      ": adding %lu bytes of salt data", (unsigned long) sql_passwd_salt_len);
165    EVP_DigestUpdate(&md_ctxt, (unsigned char *) sql_passwd_salt,
166      sql_passwd_salt_len);
167  }
168
169  EVP_DigestFinal(&md_ctxt, mdval, &mdlen);
170
171  memset(buf, '\0', sizeof(buf));
172
173  switch (sql_passwd_encoding) {
174    case SQL_PASSWD_USE_BASE64:
175      EVP_EncodeInit(&base64_ctxt);
176      EVP_EncodeBlock(buf, mdval, (int) mdlen);
177      break;
178
179    case SQL_PASSWD_USE_HEX_LC: {
180      register unsigned int i;
181
182      for (i = 0; i < mdlen; i++) {
183        sprintf((char *) &(buf[i*2]), "%02x", mdval[i]);
184      }
185
186      break;
187    }
188
189    case SQL_PASSWD_USE_HEX_UC: {
190      register unsigned int i;
191
192      for (i = 0; i < mdlen; i++) {
193        sprintf((char *) &(buf[i*2]), "%02X", mdval[i]);
194      }
195
196      break;
197    }
198
199    default:
200      sql_log(DEBUG_WARN, "unsupported SQLPasswordEncoding configured");
201      return PR_ERROR_INT(cmd, PR_AUTH_ERROR);
202  }
203
204  if (strcmp((char *) buf, copytext) == 0) {
205    return PR_HANDLED(cmd);
206
207  } else {
208    pr_log_debug(DEBUG9, MOD_SQL_PASSWD_VERSION ": expected '%s', got '%s'",
209      buf, copytext);
210  }
211
212  return PR_ERROR_INT(cmd, PR_AUTH_BADPWD);
213}
214
215static modret_t *sql_passwd_md5(cmd_rec *cmd, const char *plaintext,
216    const char *ciphertext) {
217  return sql_passwd_auth(cmd, plaintext, ciphertext, "md5");
218}
219
220static modret_t *sql_passwd_sha1(cmd_rec *cmd, const char *plaintext,
221    const char *ciphertext) {
222  return sql_passwd_auth(cmd, plaintext, ciphertext, "sha1");
223}
224
225static modret_t *sql_passwd_sha256(cmd_rec *cmd, const char *plaintext,
226    const char *ciphertext) {
227  return sql_passwd_auth(cmd, plaintext, ciphertext, "sha256");
228}
229
230static modret_t *sql_passwd_sha512(cmd_rec *cmd, const char *plaintext,
231    const char *ciphertext) {
232  return sql_passwd_auth(cmd, plaintext, ciphertext, "sha512");
233}
234
235/* Event handlers
236 */
237
238#if defined(PR_SHARED_MODULE)
239static void sql_passwd_mod_unload_ev(const void *event_data, void *user_data) {
240  if (strcmp("mod_sql_passwd.c", (const char *) event_data) == 0) {
241    sql_unregister_authtype("md5");
242    sql_unregister_authtype("sha1");
243    sql_unregister_authtype("sha256");
244    sql_unregister_authtype("sha512");
245
246    pr_event_unregister(&sql_passwd_module, NULL, NULL);
247  }
248}
249#endif /* PR_SHARED_MODULE */
250
251/* Command handlers
252 */
253
254MODRET sql_passwd_pre_pass(cmd_rec *cmd) {
255  config_rec *c;
256
257  if (!sql_passwd_engine) {
258    return PR_DECLINED(cmd);
259  }
260
261  c = find_config(main_server->conf, CONF_PARAM, "SQLPasswordUserSalt", FALSE);
262  if (c) {
263    char *key;
264    char *append;
265
266    key = c->argv[0];
267    append = c->argv[1];
268
269    if (strcasecmp(key, "name") == 0) {
270      char *user;
271
272      user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
273      sql_passwd_salt = user;
274      sql_passwd_salt_len = strlen(user);
275
276    } else if (strncasecmp(key, "sql:/", 5) == 0) {
277      char *named_query, *ptr, *user, **values;
278      cmdtable *sql_cmdtab;
279      cmd_rec *sql_cmd;
280      modret_t *sql_res;
281      array_header *sql_data;
282
283      ptr = key + 5;
284      named_query = pstrcat(cmd->tmp_pool, "SQLNamedQuery_", ptr, NULL);
285
286      c = find_config(main_server->conf, CONF_PARAM, named_query, FALSE);
287      if (c == NULL) {
288        pr_log_debug(DEBUG3, MOD_SQL_PASSWD_VERSION
289          ": unable to resolve SQLNamedQuery '%s'", ptr);
290        return PR_DECLINED(cmd);
291      }
292
293      sql_cmdtab = pr_stash_get_symbol(PR_SYM_HOOK, "sql_lookup", NULL, NULL);
294      if (sql_cmdtab == NULL) {
295        pr_log_debug(DEBUG3, MOD_SQL_PASSWD_VERSION
296          ": unable to find SQL hook symbol 'sql_lookup'");
297        return PR_DECLINED(cmd);
298      }
299
300      user = pr_table_get(session.notes, "mod_auth.orig-user", NULL);
301
302      sql_cmd = sql_passwd_cmd_create(cmd->tmp_pool, 3, "sql_lookup", ptr,
303        sql_passwd_get_str(cmd->tmp_pool, user));
304
305      /* Call the handler. */
306      sql_res = pr_module_call(sql_cmdtab->m, sql_cmdtab->handler, sql_cmd);
307      if (sql_res == NULL ||
308          MODRET_ISERROR(sql_res)) {
309        pr_log_debug(DEBUG0, MOD_SQL_PASSWD_VERSION
310          ": error processing SQLNamedQuery '%s'", ptr);
311        return PR_DECLINED(cmd);
312      }
313
314      sql_data = (array_header *) sql_res->data;
315
316      if (sql_data->nelts != 1) {
317        pr_log_debug(DEBUG0, MOD_SQL_PASSWD_VERSION
318          ": SQLNamedQuery '%s' returned wrong number of rows (%d)", ptr,
319          sql_data->nelts);
320        return PR_DECLINED(cmd);
321      }
322
323      values = sql_data->elts;
324      sql_passwd_salt = pstrdup(session.pool, values[0]);
325      sql_passwd_salt_len = strlen(values[0]);
326     
327    } else {
328      return PR_DECLINED(cmd);
329    }
330
331    if (strcasecmp(append, "prepend") == 0) {
332      sql_passwd_salt_append = FALSE;
333
334    } else {
335      sql_passwd_salt_append = TRUE;
336    }
337  }
338
339  return PR_DECLINED(cmd);
340}
341
342/* Configuration handlers
343 */
344
345/* usage: SQLPasswordEncoding "base64"|"hex"|"HEX" */
346MODRET set_sqlpasswdencoding(cmd_rec *cmd) {
347  unsigned int encoding;
348  config_rec *c = NULL;
349
350  CHECK_ARGS(cmd, 1);
351  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
352
353  if (strcmp(cmd->argv[1], "base64") == 0) {
354    encoding = SQL_PASSWD_USE_BASE64;
355
356  } else if (strcmp(cmd->argv[1], "hex") == 0) {
357    encoding = SQL_PASSWD_USE_HEX_LC;
358
359  } else if (strcmp(cmd->argv[1], "HEX") == 0) {
360    encoding = SQL_PASSWD_USE_HEX_UC;
361
362  } else {
363    CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unsupported encoding '",
364      cmd->argv[1], "' configured", NULL));
365  }
366 
367  c = add_config_param(cmd->argv[0], 1, NULL);
368  c->argv[0] = pcalloc(c->pool, sizeof(unsigned int));
369  *((unsigned int *) c->argv[0]) = encoding;
370
371  return PR_HANDLED(cmd);
372}
373
374/* usage: SQLPasswordEngine on|off */
375MODRET set_sqlpasswdengine(cmd_rec *cmd) {
376  int bool = -1;
377  config_rec *c = NULL;
378
379  CHECK_ARGS(cmd, 1);
380  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
381
382  bool = get_boolean(cmd, 1);
383  if (bool == -1)
384    CONF_ERROR(cmd, "expected Boolean parameter");
385
386  c = add_config_param(cmd->argv[0], 1, NULL);
387  c->argv[0] = pcalloc(c->pool, sizeof(int));
388  *((int *) c->argv[0]) = bool;
389
390  return PR_HANDLED(cmd);
391}
392
393/* usage: SQLPasswordSaltFile path|"none" ["prepend"|"append"] */
394MODRET set_sqlpasswdsaltfile(cmd_rec *cmd) {
395  if (cmd->argc < 2 ||
396      cmd->argc > 3) {
397    CONF_ERROR(cmd, "wrong number of parameters");
398  }
399
400  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
401
402  (void) add_config_param_str(cmd->argv[0], 2, cmd->argv[1],
403    cmd->argc == 3 ? cmd->argv[2] : "append");
404  return PR_HANDLED(cmd);
405}
406
407/* usage: SQLPasswordUserSalt "name"|"sql:/named-query" ["prepend"|"append"] */
408MODRET set_sqlpasswdusersalt(cmd_rec *cmd) {
409  if (cmd->argc < 2 ||
410      cmd->argc > 3) {
411    CONF_ERROR(cmd, "wrong number of parameters");
412  }
413
414  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
415
416  if (strcasecmp(cmd->argv[1], "name") != 0 &&
417      strcasecmp(cmd->argv[1], "uid") != 0 &&
418      strncasecmp(cmd->argv[1], "sql:/", 5) != 0) {
419    CONF_ERROR(cmd, "badly formatted parameter");
420  }
421
422  (void) add_config_param_str(cmd->argv[0], 2, cmd->argv[1],
423    cmd->argc == 3 ? cmd->argv[2] : "append");
424  return PR_HANDLED(cmd);
425}
426
427/* Initialization routines
428 */
429
430static int sql_passwd_init(void) {
431
432#if defined(PR_SHARED_MODULE)
433  pr_event_register(&sql_passwd_module, "core.module-unload",
434    sql_passwd_mod_unload_ev, NULL);
435#endif /* PR_SHARED_MODULE */
436
437  if (sql_register_authtype("md5", sql_passwd_md5) < 0) {
438    pr_log_pri(PR_LOG_WARNING, MOD_SQL_PASSWD_VERSION
439      ": unable to register 'md5' SQLAuthType handler: %s", strerror(errno));
440
441  } else {
442    pr_log_debug(DEBUG6, MOD_SQL_PASSWD_VERSION
443      ": registered 'md5' SQLAuthType handler");
444  }
445
446  if (sql_register_authtype("sha1", sql_passwd_sha1) < 0) {
447    pr_log_pri(PR_LOG_WARNING, MOD_SQL_PASSWD_VERSION
448      ": unable to register 'sha1' SQLAuthType handler: %s", strerror(errno));
449
450  } else {
451    pr_log_debug(DEBUG6, MOD_SQL_PASSWD_VERSION
452      ": registered 'sha1' SQLAuthType handler");
453  }
454
455  if (sql_register_authtype("sha256", sql_passwd_sha256) < 0) {
456    pr_log_pri(PR_LOG_WARNING, MOD_SQL_PASSWD_VERSION
457      ": unable to register 'sha256' SQLAuthType handler: %s", strerror(errno));
458
459  } else {
460    pr_log_debug(DEBUG6, MOD_SQL_PASSWD_VERSION
461      ": registered 'sha256' SQLAuthType handler");
462  }
463
464  if (sql_register_authtype("sha512", sql_passwd_sha512) < 0) {
465    pr_log_pri(PR_LOG_WARNING, MOD_SQL_PASSWD_VERSION
466      ": unable to register 'sha512' SQLAuthType handler: %s", strerror(errno));
467
468  } else {
469    pr_log_debug(DEBUG6, MOD_SQL_PASSWD_VERSION
470      ": registered 'sha512' SQLAuthType handler");
471  }
472
473  return 0;
474}
475
476static int sql_passwd_sess_init(void) {
477  config_rec *c;
478
479  c = find_config(main_server->conf, CONF_PARAM, "SQLPasswordEngine", FALSE);
480  if (c) {
481    sql_passwd_engine = *((int *) c->argv[0]);
482  }
483
484  c = find_config(main_server->conf, CONF_PARAM, "SQLPasswordEncoding", FALSE);
485  if (c) {
486    sql_passwd_encoding = *((unsigned int *) c->argv[0]);
487  }
488
489  c = find_config(main_server->conf, CONF_PARAM, "SQLPasswordSaltFile", FALSE);
490  if (c) {
491    char *path;
492    char *append;
493
494    path = c->argv[0];
495    append = c->argv[1];
496
497    if (strcasecmp(path, "none") != 0) {
498      int fd, xerrno = 0;;
499
500      PRIVS_ROOT
501      fd = open(path, O_RDONLY|O_NONBLOCK);
502      if (fd < 0) {
503        xerrno = errno;
504      }
505      PRIVS_RELINQUISH
506
507      if (fd >= 0) {
508        int flags;
509        char buf[512];
510        ssize_t nread;
511 
512        /* Set this descriptor for blocking. */
513        flags = fcntl(fd, F_GETFL);
514        if (fcntl(fd, F_SETFL, flags & (U32BITS^O_NONBLOCK)) < 0) {
515          pr_log_debug(DEBUG3, MOD_SQL_PASSWD_VERSION
516            ": error setting blocking mode on SQLPasswordSaltFile '%s': %s",
517            path, strerror(errno));
518        }
519 
520        nread = read(fd, buf, sizeof(buf));
521        while (nread > 0) {
522          pr_signals_handle();
523
524          if (sql_passwd_salt == NULL) {
525
526            /* If the very last byte in the buffer is a newline, trim it. */
527            if (buf[nread-1] == '\n') {
528              buf[nread-1] = '\0';
529              nread--;
530            }
531
532            sql_passwd_salt_len = nread;
533            sql_passwd_salt = palloc(session.pool, sql_passwd_salt_len);
534            memcpy(sql_passwd_salt, buf, nread);
535
536          } else {
537            char *ptr, *tmp;
538
539            /* Allocate a larger buffer for the salt. */
540            ptr = tmp = palloc(session.pool, sql_passwd_salt_len + nread);
541            memcpy(tmp, sql_passwd_salt, sql_passwd_salt_len);
542            tmp += sql_passwd_salt_len;
543
544            memcpy(tmp, buf, nread);
545            sql_passwd_salt_len += nread;
546
547            /* XXX Yes, this is a minor memory leak; we are overwriting the
548             * previously allocated memory for the salt.  But it's per-session,
549             * so it's not a great concern at this point.
550             */
551            sql_passwd_salt = ptr;
552          }
553
554          nread = read(fd, buf, sizeof(buf));
555        }
556
557        if (nread < 0) {
558          pr_log_debug(DEBUG1, MOD_SQL_PASSWD_VERSION
559            ": error reading salt data from SQLPasswordSaltFile '%s': %s",
560            path, strerror(errno));
561          sql_passwd_salt = NULL;
562        }
563
564        (void) close(fd);
565
566        /* If the very last byte in the buffer is a newline, trim it.  This
567         * is to deal with cases where the SaltFile may have been written
568         * with an editor (e.g. vi) which automatically adds a trailing newline.
569         */
570        if (sql_passwd_salt[sql_passwd_salt_len-1] == '\n') {
571          sql_passwd_salt[sql_passwd_salt_len-1] = '\0';
572          sql_passwd_salt_len--;
573        }
574
575        /* Determine whether to use the obtained salt as a prefix or suffix. */
576        if (strcasecmp(append, "prepend") == 0) {
577          sql_passwd_salt_append = FALSE;
578
579        } else {
580          /* The default, for better/worse, is to append the salt as
581           * a suffix.
582           */
583          sql_passwd_salt_append = TRUE;
584        }
585
586      } else {
587        pr_log_debug(DEBUG1, MOD_SQL_PASSWD_VERSION
588          ": unable to read SQLPasswordSaltFile '%s': %s", path,
589          strerror(xerrno));
590      }
591    }
592  }
593
594  return 0;
595}
596
597/* Module API tables
598 */
599
600static conftable sql_passwd_conftab[] = {
601  { "SQLPasswordEncoding",      set_sqlpasswdencoding,  NULL },
602  { "SQLPasswordEngine",        set_sqlpasswdengine,    NULL },
603  { "SQLPasswordSaltFile",      set_sqlpasswdsaltfile,  NULL },
604  { "SQLPasswordUserSalt",      set_sqlpasswdusersalt,  NULL },
605
606  { NULL, NULL, NULL }
607};
608
609static cmdtable sql_passwd_cmdtab[] = {
610  { PRE_CMD,    C_PASS, G_NONE, sql_passwd_pre_pass,    FALSE,  FALSE },
611
612  { 0, NULL }
613};
614
615module sql_passwd_module = {
616
617  /* Always NULL */
618  NULL, NULL,
619
620  /* Module API version */
621  0x20,
622
623  /* Module name */
624  "sql_passwd",
625
626  /* Module configuration directive table */
627  sql_passwd_conftab,
628
629  /* Module command handler table */
630  sql_passwd_cmdtab,
631
632  /* Module auth handler table */
633  NULL,
634
635  /* Module initialization */
636  sql_passwd_init,
637
638  /* Session initialization */
639  sql_passwd_sess_init,
640
641  /* Module version */
642  MOD_SQL_PASSWD_VERSION
643};
644
Note: See TracBrowser for help on using the repository browser.