Blame view

libmu_auth/sql.c 17.4 KB
1
/* GNU Mailutils -- a suite of utilities for electronic mail
Sergey Poznyakoff authored
2
   Copyright (C) 2002-2012, 2014-2017 Free Software Foundation, Inc.
3

4 5 6
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
7
   version 3 of the License, or (at your option) any later version.
8

9
   This library is distributed in the hope that it will be useful,
10
   but WITHOUT ANY WARRANTY; without even the implied warranty of
11 12
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.
13

14
   You should have received a copy of the GNU Lesser General
15 16
   Public License along with this library.  If not, see 
   <http://www.gnu.org/licenses/>. */
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#ifdef HAVE_SHADOW_H
# include <shadow.h>
#endif
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_STRINGS_H
# include <strings.h>
34 35
#else
# include <string.h>
36
#endif
37 38 39
#ifdef HAVE_CRYPT_H
# include <crypt.h>
#endif
40

41
#include <mailutils/assoc.h>
42 43 44 45
#include <mailutils/list.h>
#include <mailutils/iterator.h>
#include <mailutils/mailbox.h>
#include <mailutils/mu_auth.h>
46
#include <mailutils/error.h>
47
#include <mailutils/errno.h>
Sergey Poznyakoff authored
48
#include <mailutils/nls.h>
49
#include <mailutils/util.h>
50
#include <mailutils/sql.h>
Sergey Poznyakoff authored
51
#include <mailutils/cstr.h>
Sergey Poznyakoff authored
52
#include <mailutils/wordsplit.h>
53
#include <mailutils/cli.h>
54
#include <mailutils/kwd.h>
55
#include "sql.h"
56

57
#ifdef USE_SQL
58

59
struct mu_sql_module_config mu_sql_module_config;
60

61
/* Resource file configuration */
62 63 64 65 66 67 68 69 70

static struct mu_kwd password_encryption[] = {
  { "plain", mu_sql_password_plaintext },
  { "scrambled", mu_sql_password_scrambled },
  { "hash", mu_sql_password_hash },
  { "crypt", mu_sql_password_hash },
  { NULL }
};

71
static int
72
cb_password_encryption (void *data, mu_config_value_t *val)
73
{
74 75
  int res;
  
76 77 78
  if (mu_cfg_assert_value_type (val, MU_CFG_STRING))
    return 1;

79 80 81 82
  if (mu_kwd_xlat_name (password_encryption, val->v.string, &res))
    mu_error ("%s", _("unrecognized password encryption"));
  else
    mu_sql_module_config.password_encryption = res;
83 84
  return 0;
}
85

86 87 88
static int
cb_field_map (void *data, mu_config_value_t *val)
{
89 90 91 92 93 94 95 96 97 98 99 100
  char *err_term;
  int rc = mu_cfg_field_map (val, &mu_sql_module_config.field_map, &err_term);

  if (rc)
    {
      if (err_term)
	mu_error (_("error near %s: %s"), err_term, mu_strerror (rc));
      else
	mu_error ("%s", mu_strerror (rc));
    }

  return rc;
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
}

static int
cb_interface (void *data, mu_config_value_t *val)
{
  if (mu_cfg_assert_value_type (val, MU_CFG_STRING))
    return 1;
  mu_sql_module_config.interface = mu_sql_interface_index (val->v.string);
  if (mu_sql_module_config.interface == 0)
    {
      mu_error (_("unknown SQL interface `%s'"), val->v.string);
      return 1;
    }
  return 0;
}
  
static struct mu_cfg_param mu_sql_param[] = {
  { "interface", mu_cfg_callback, &mu_sql_module_config.interface, 0,
    cb_interface,
    N_("Set SQL interface to use."),
Sergey Poznyakoff authored
121
    /* TRANSLATORS: Words to the right of : are keywords - do not translate */
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
    N_("iface: mysql|odbc|postgres") },
  { "getpwnam", mu_c_string, &mu_sql_module_config.getpwnam_query, 0, NULL,
    N_("SQL query to use for getpwnam requests."),
    N_("query") },
  { "getpwuid", mu_c_string, &mu_sql_module_config.getpwuid_query, 0, NULL,
    N_("SQL query to use for getpwuid requests."),
    N_("query") },
  { "getpass", mu_c_string, &mu_sql_module_config.getpass_query, 0, NULL,
    N_("SQL query returning the user's password."),
    N_("query") },
  { "host", mu_c_string, &mu_sql_module_config.host, 0, NULL,
    N_("SQL server host name.") },
  { "user", mu_c_string, &mu_sql_module_config.user, 0, NULL,
    N_("SQL user name.") },
  { "passwd", mu_c_string, &mu_sql_module_config.passwd, 0, NULL,
    N_("Password for the SQL user.") },
  { "port", mu_c_int, &mu_sql_module_config.port, 0, NULL,
    N_("SQL server port.") },
  { "db", mu_c_string, &mu_sql_module_config.db, 0, NULL,
    N_("Database name.") },
142
  { "password-encryption", mu_cfg_callback, NULL, 0, cb_password_encryption,
Sergey Poznyakoff authored
143 144
    N_("Type of password returned by getpass query."),
    /* TRANSLATORS: Words to the right of : are keywords - do not translate */
145
    N_("arg: plain|hash|crypt|scrambled") },
146 147 148 149 150 151 152 153
  { "field-map", mu_cfg_callback, NULL, 0, cb_field_map,
    N_("Set a field-map for parsing SQL replies.  The map is a "
       "column-separated list of definitions.  Each definition has the "
       "following form:\n"
       "   <name: string>=<column: string>\n"
       "where <name> is one of the following: name, passwd, uid, gid, "
       "gecos, dir, shell, mailbox, quota, and <column> is the name of "
       "the corresponding SQL column."),
Sergey Poznyakoff authored
154
    N_("map: definition") },
155 156 157 158
  { NULL }
};


159 160 161 162 163 164
static char *
sql_escape_string (const char *ustr)
{
  char *str, *q;
  const unsigned char *p;
  size_t len = strlen (ustr);
165
#define ESCAPABLE_CHAR "\\'\""
166 167 168
  
  for (p = (const unsigned char *) ustr; *p; p++)
    {
169
      if (strchr (ESCAPABLE_CHAR, *p))
170 171 172 173 174 175 176 177 178
	len++;
    }

  str = malloc (len + 1);
  if (!str)
    return NULL;

  for (p = (const unsigned char *) ustr, q = str; *p; p++)
    {
179
      if (strchr (ESCAPABLE_CHAR, *p))
180 181 182 183 184 185 186
	*q++ = '\\';
      *q++ = *p;
    }
  *q = 0;
  return str;
}

187 188
char *
mu_sql_expand_query (const char *query, const char *ustr)
189
{
190
  int rc;
191
  char *res;
192
  char *esc_ustr;
193

194 195
  if (!query)
    return NULL;
196 197

  esc_ustr = sql_escape_string (ustr);
198 199 200
  rc = mu_str_vexpand (&res, query, "user", esc_ustr, NULL);
  free (esc_ustr);
  if (rc)
201
    {
202 203 204 205 206 207 208
      if (rc == MU_ERR_FAILURE)
	{
	  mu_error (_("cannot expand line `%s': %s"), query, res);
	  free (res);
	}
      else
	mu_error (_("cannot expand line `%s': %s"), query, mu_strerror (rc));
209 210
      return NULL;
    }
211 212
  return res;
}
213

214
static int
215 216
get_field (mu_sql_connection_t conn, const char *id, char **ret, int mandatory)
{
217 218 219 220 221
  int rc;
  const char *name = mu_assoc_get (mu_sql_module_config.field_map, id);
  if (!name)
    name = id;
  rc = mu_sql_get_field (conn, 0, name, ret);
222 223 224
  if (rc)
    {
      if (mandatory || rc != MU_ERR_NOENT)
225
	mu_error (_("cannot get SQL field `%s' (`%s'): %s"),
226
		  id, name, mu_strerror (rc));
227 228 229
    }
  else if (!*ret)
    {
230 231 232
      if (mandatory)
	{
	  mu_error (_("SQL field `%s' (`%s') has NULL value"),
233
		    id, name);
234 235 236 237
	  rc = MU_ERR_READ;
	}
      else
	rc = MU_ERR_NOENT;
238 239 240 241 242 243
    }

  return rc;
}

static int
244 245
decode_tuple (mu_sql_connection_t conn, int n,
	      struct mu_auth_data **return_data)
246 247 248 249 250 251 252 253 254
{
  int rc;
  char *mailbox_name = NULL;
  char *name;
  char *passwd, *suid, *sgid, *dir, *shell, *gecos, *squota;
  mu_off_t quota = 0;
  char *p;
  uid_t uid;
  gid_t gid;
255

256 257 258 259 260 261 262 263 264 265 266 267 268 269
  if (get_field (conn, MU_AUTH_NAME, &name, 1)
      || get_field (conn, MU_AUTH_PASSWD, &passwd, 1)
      || get_field (conn, MU_AUTH_UID, &suid, 1)
      || get_field (conn, MU_AUTH_GID, &sgid, 1)     
      || get_field (conn, MU_AUTH_DIR, &dir, 1)     
      || get_field (conn, MU_AUTH_SHELL, &shell, 1))
    return MU_ERR_FAILURE;

  if (get_field (conn, MU_AUTH_GECOS, &gecos, 0))
    gecos = "SQL user";
  
  uid = strtoul (suid, &p, 0);
  if (*p)
    {
270
      mu_error (_("invalid value for uid: %s"), suid);
271 272 273 274 275 276
      return MU_ERR_FAILURE;
    }

  gid = strtoul (sgid, &p, 0);
  if (*p)
    {
277
      mu_error (_("invalid value for gid: %s"), sgid);
278 279 280 281 282 283 284 285
      return MU_ERR_FAILURE;
    }
  
  rc = get_field (conn, MU_AUTH_MAILBOX, &mailbox_name, 0);
  switch (rc)
    {
    case 0:
      mailbox_name = strdup (mailbox_name);
286 287
      if (!mailbox_name)
        return ENOMEM;
288 289 290 291 292 293 294 295 296 297 298 299 300 301
      break;
      
    case MU_ERR_NOENT:
      if (mu_construct_user_mailbox_url (&mailbox_name, name))
	return MU_ERR_FAILURE;
      break;

    default:
      return MU_ERR_FAILURE;
    }

  rc = get_field (conn, MU_AUTH_QUOTA, &squota, 0);
  if (rc == 0)
    {
302
      if (mu_c_strcasecmp (squota, "none") == 0)
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
	quota = 0;
      else
	{
	  quota = strtoul (squota, &p, 10);
	  switch (*p)
	    {
	    case 0:
	      break;
	      
	    case 'k':
	    case 'K':
	      quota *= 1024;
	      break;
      
	    case 'm':
	    case 'M':
	      quota *= 1024*1024;
	      break;
	      
	    default:
323
	      mu_error (_("invalid value for quota: %s"), squota);
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
	      free (mailbox_name);
	      return MU_ERR_FAILURE;
	    }
	}
    }
  else if (rc ==  MU_ERR_NOENT)
    quota = 0;
  else
    {
      free (mailbox_name);
      return MU_ERR_FAILURE;
    }

  rc = mu_auth_data_alloc (return_data,
			   name,
			   passwd,
			   uid,
			   gid,
			   gecos,
			   dir,
			   shell,
			   mailbox_name,
			   1);
347 348
  if (rc == 0)
    mu_auth_data_set_quota (*return_data, quota);
349 350 351 352 353 354
  
  free (mailbox_name);
  return rc;
}  

static int
355 356
mu_auth_sql_by_name (struct mu_auth_data **return_data,
		     const void *key,
357 358
		     void *func_data MU_ARG_UNUSED,
		     void *call_data MU_ARG_UNUSED)
359 360 361 362 363 364 365
{
  int status, rc;
  char *query_str = NULL;
  mu_sql_connection_t conn;
  size_t n;
  
  if (!key)
366
    return EINVAL;
367

368
  query_str = mu_sql_expand_query (mu_sql_module_config.getpwnam_query, key);
369 370

  if (!query_str)
371
    return MU_ERR_FAILURE;
372 373

  status = mu_sql_connection_init (&conn,
374 375 376 377 378 379
				   mu_sql_module_config.interface,
				   mu_sql_module_config.host,
				   mu_sql_module_config.port,
				   mu_sql_module_config.user,
				   mu_sql_module_config.passwd,
				   mu_sql_module_config.db);
380 381 382

  if (status)
    {
383
      mu_error ("%s: %s", mu_strerror (status), mu_sql_strerror (conn));
384 385
      mu_sql_connection_destroy (&conn);
      free (query_str);
386
      return MU_ERR_FAILURE;
387 388 389 390 391 392
    }

  status = mu_sql_connect (conn);

  if (status)
    {
393
      mu_error ("%s: %s", mu_strerror (status), mu_sql_strerror (conn));
394 395
      mu_sql_connection_destroy (&conn);
      free (query_str);
396
      return EAGAIN;
397 398 399 400 401 402 403
    }
  
  status = mu_sql_query (conn, query_str);
  free (query_str);
  
  if (status)
    {
Sergey Poznyakoff authored
404
      mu_error (_("SQL query failed: %s"),
405 406 407
		(status == MU_ERR_SQL) ?  mu_sql_strerror (conn) :
	 	                          mu_strerror (status));
      mu_sql_connection_destroy (&conn);
408
      return MU_ERR_FAILURE;
409 410 411 412 413 414
    }

  status = mu_sql_store_result (conn);

  if (status)
    {
415
      mu_error (_("cannot store SQL result: %s"),
416 417 418
		(status == MU_ERR_SQL) ?  mu_sql_strerror (conn) :
	 	                          mu_strerror (status));
      mu_sql_connection_destroy (&conn);
419
      return MU_ERR_FAILURE;
420 421
    }

Sergey Poznyakoff authored
422 423 424 425 426 427 428 429 430 431 432
  status = mu_sql_num_tuples (conn, &n);
  if (status)
    {
      mu_error (_("cannot get number of tuples: %s"),
                (status == MU_ERR_SQL) ?  mu_sql_strerror (conn) :
                                          mu_strerror (status));
      mu_sql_release_result (conn);
      mu_sql_connection_destroy (&conn);
      return MU_ERR_FAILURE;
    }
  
433
  if (n == 0)
434
    rc = MU_ERR_AUTH_FAILURE;
435
  else
436
    rc = decode_tuple (conn, n, return_data);
437 438 439 440 441 442 443 444 445 446 447
  
  mu_sql_release_result (conn);
  mu_sql_disconnect (conn);
  mu_sql_connection_destroy (&conn);
  
  return rc;
}

static int
mu_auth_sql_by_uid (struct mu_auth_data **return_data,
		    const void *key,
448 449
		    void *func_data MU_ARG_UNUSED,
		    void *call_data MU_ARG_UNUSED)
450 451 452 453 454 455 456 457
{
  char uidstr[64];
  int status, rc;
  char *query_str = NULL;
  mu_sql_connection_t conn;
  size_t n;
  
  if (!key)
458
    return EINVAL;
459 460

  snprintf (uidstr, sizeof (uidstr), "%u", *(uid_t*)key);
461 462
  query_str = mu_sql_expand_query (mu_sql_module_config.getpwuid_query,
				   uidstr);
463 464

  if (!query_str)
465
    return ENOMEM;
466 467

  status = mu_sql_connection_init (&conn,
468 469 470 471 472 473
				   mu_sql_module_config.interface,
				   mu_sql_module_config.host,
				   mu_sql_module_config.port,
				   mu_sql_module_config.user,
				   mu_sql_module_config.passwd,
				   mu_sql_module_config.db);
474 475 476

  if (status)
    {
477
      mu_error ("%s: %s", mu_strerror (status), mu_sql_strerror (conn));
478 479
      mu_sql_connection_destroy (&conn);
      free (query_str);
480
      return MU_ERR_FAILURE;
481 482 483
    }

  status = mu_sql_connect (conn);
484

485 486
  if (status)
    {
487
      mu_error ("%s: %s", mu_strerror (status), mu_sql_strerror (conn));
488 489
      mu_sql_connection_destroy (&conn);
      free (query_str);
490
      return EAGAIN;
491 492 493 494 495 496 497
    }
  
  status = mu_sql_query (conn, query_str);
  free (query_str);
  
  if (status)
    {
Sergey Poznyakoff authored
498
      mu_error (_("SQL query failed: %s"),
499 500 501
		(status == MU_ERR_SQL) ?  mu_sql_strerror (conn) :
	 	                          mu_strerror (status));
      mu_sql_connection_destroy (&conn);
502
      return MU_ERR_FAILURE;
503
    }
504

505
  status = mu_sql_store_result (conn);
506

507 508
  if (status)
    {
509
      mu_error (_("cannot store SQL result: %s"),
510 511 512
		(status == MU_ERR_SQL) ?  mu_sql_strerror (conn) :
	 	                          mu_strerror (status));
      mu_sql_connection_destroy (&conn);
513
      return MU_ERR_FAILURE;
514
    }
515

Sergey Poznyakoff authored
516 517 518 519 520 521 522 523 524 525
  status = mu_sql_num_tuples (conn, &n);
  if (status)
    {
      mu_error (_("cannot get number of tuples: %s"),
                (status == MU_ERR_SQL) ?  mu_sql_strerror (conn) :
                                          mu_strerror (status));
      mu_sql_release_result (conn);
      mu_sql_connection_destroy (&conn);
      return MU_ERR_FAILURE;
    }
526

527
  if (n == 0)
528
    rc = MU_ERR_AUTH_FAILURE;
529
  else
530
    rc = decode_tuple (conn, n, return_data);
531 532 533 534 535 536 537
  
  mu_sql_release_result (conn);
  mu_sql_disconnect (conn);
  mu_sql_connection_destroy (&conn);
  
  return rc;
}
538

539 540
int
mu_sql_getpass (const char *username, char **passwd)
541 542
{
  mu_sql_connection_t conn;
543 544
  char *query_str;
  int status;
545
  char *sql_pass;
Sergey Poznyakoff authored
546 547
  size_t nt;

548
  query_str = mu_sql_expand_query (mu_sql_module_config.getpass_query, username);
549 550

  if (!query_str)
551
    return MU_ERR_FAILURE;
552 553

  status = mu_sql_connection_init (&conn,
554 555 556 557 558 559
				   mu_sql_module_config.interface,
				   mu_sql_module_config.host,
				   mu_sql_module_config.port,
				   mu_sql_module_config.user,
				   mu_sql_module_config.passwd,
				   mu_sql_module_config.db);
560 561 562

  if (status)
    {
563
      mu_error ("%s: %s", mu_strerror (status), mu_sql_strerror (conn));
564 565
      mu_sql_connection_destroy (&conn);
      free (query_str);
566
      return MU_ERR_FAILURE;
567 568 569 570 571 572
    }

  status = mu_sql_connect (conn);

  if (status)
    {
573
      mu_error ("%s: %s", mu_strerror (status), mu_sql_strerror (conn));
574 575
      mu_sql_connection_destroy (&conn);
      free (query_str);
576
      return EAGAIN;
577 578 579 580 581 582 583
    }
  
  status = mu_sql_query (conn, query_str);
  free (query_str);
  
  if (status)
    {
Sergey Poznyakoff authored
584
      mu_error (_("SQL query failed: %s"),
585 586 587
		(status == MU_ERR_SQL) ?  mu_sql_strerror (conn) :
	 	                          mu_strerror (status));
      mu_sql_connection_destroy (&conn);
588
      return MU_ERR_FAILURE;
589
    }
Sergey Poznyakoff authored
590
  
591 592 593 594
  status = mu_sql_store_result (conn);

  if (status)
    {
595
      mu_error (_("cannot store SQL result: %s"),
596 597 598
		(status == MU_ERR_SQL) ?  mu_sql_strerror (conn) :
	 	                          mu_strerror (status));
      mu_sql_connection_destroy (&conn);
599
      return MU_ERR_FAILURE;
600 601
    }

Sergey Poznyakoff authored
602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618
  status = mu_sql_num_tuples (conn, &nt);
  if (status)
    {
      mu_error (_("cannot get number of tuples: %s"),
                (status == MU_ERR_SQL) ?  mu_sql_strerror (conn) :
                                          mu_strerror (status));
      mu_sql_release_result (conn);
      mu_sql_connection_destroy (&conn);
      return MU_ERR_FAILURE;
    }
  if (nt == 0)
    {
      mu_sql_release_result (conn);
      mu_sql_connection_destroy (&conn);
      return MU_ERR_FAILURE;
    }

619 620 621
  status = mu_sql_get_column (conn, 0, 0, &sql_pass);
  if (status)
    {
622
      mu_error (_("cannot get password from SQL: %s"),
623 624 625 626
		(status == MU_ERR_SQL) ?  mu_sql_strerror (conn) :
	 	                          mu_strerror (status));
      mu_sql_release_result (conn);
      mu_sql_connection_destroy (&conn);
627
      return MU_ERR_FAILURE;
628
    }
629

Sergey Poznyakoff authored
630 631 632 633 634 635 636 637
  if (!sql_pass)
    {
      mu_error (_("SQL returned NULL password"));
      mu_sql_release_result (conn);
      mu_sql_connection_destroy (&conn);
      return MU_ERR_FAILURE;
    }

638
  *passwd = strdup (sql_pass);
639 640 641

  mu_sql_disconnect (conn);
  mu_sql_connection_destroy (&conn);
642 643 644 645 646 647 648 649

  if (!*passwd)
    return ENOMEM;

  return 0;
}

static int
650
mu_sql_authenticate (struct mu_auth_data **return_data MU_ARG_UNUSED,
651
		     const void *key,
652
		     void *func_data MU_ARG_UNUSED, void *call_data)
653
{
654
  const struct mu_auth_data *auth_data = key;
655
  char *pass = call_data;
656
  char *sql_pass, *crypt_pass;
657 658 659
  int rc;
  
  if (!auth_data)
660
    return EINVAL;
661

662 663
  if ((rc = mu_sql_getpass (auth_data->name, &sql_pass)))
    return rc;
664

665
  switch (mu_sql_module_config.password_encryption)
666
    {
667
    case mu_sql_password_hash:
668 669 670 671 672
      crypt_pass = crypt (pass, sql_pass);
      if (!crypt_pass)
        rc = 1;
      else
        rc = strcmp (sql_pass, crypt_pass);
673 674
      break;

675
    case mu_sql_password_scrambled:
676 677 678 679 680 681
      /* FIXME: Should this call be implementation-independent? I mean,
         should we have mu_sql_check_scrambled() that will match the
	 password depending on the exact type of the underlying database,
	 just as the rest of mu_sql_.* functions do */
#ifdef HAVE_MYSQL
      rc = mu_check_mysql_scrambled_password (sql_pass, pass);
682 683
#else
      rc = 1;
684 685 686
#endif
      break;

687
    case mu_sql_password_plaintext:
688 689 690 691 692
      rc = strcmp (sql_pass, pass);
      break;
    }

  free (sql_pass);
693
  
694
  return rc == 0 ? 0 : MU_ERR_AUTH_FAILURE;
695
}
696

697 698
#else

699 700
# define mu_sql_authenticate mu_auth_nosupport
# define mu_auth_sql_by_name mu_auth_nosupport
701
# define mu_auth_sql_by_uid mu_auth_nosupport
702
# define mu_sql_param NULL
703 704
#endif

705

706
struct mu_auth_module mu_auth_sql_module = {
707 708 709 710 711 712 713
  .name = "sql",
  .cfg = mu_sql_param,
  .handler = {
    [mu_auth_authenticate] = mu_sql_authenticate,
    [mu_auth_getpwnam]     = mu_auth_sql_by_name,
    [mu_auth_getpwuid]     = mu_auth_sql_by_uid
  }
714 715
};