Blame view

pop3d/popauth.c 14.7 KB
1
/* GNU Mailutils -- a suite of utilities for electronic mail
2 3
   Copyright (C) 1999, 2000, 2001, 2002, 2005, 2007, 2008, 2009, 2010
   Free Software Foundation, Inc.
4

5
   GNU Mailutils 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 3, or (at your option)
8 9
   any later version.

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

   You should have received a copy of the GNU General Public License
16
   along with GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>. */
17 18

#include "pop3d.h"
19
#include <xalloc.h>
20
#include "mailutils/libargp.h"
21 22 23 24

int db_list (char *input_name, char *output_name);
int db_make (char *input_name, char *output_name);

25 26 27 28 29 30
#define ACT_CREATE  0
#define ACT_ADD     1
#define ACT_DELETE  2
#define ACT_LIST    3
#define ACT_CHPASS  4

31
static int permissions = 0600;
32
static int compatibility_option = 0;
33

34
struct action_data {
35
  int action;
36 37 38 39 40 41 42
  char *input_name;
  char *output_name;
  char *username;
  char *passwd;
};

void check_action(int action);
43 44 45 46 47
int action_create (struct action_data *ap);
int action_add (struct action_data *ap);
int action_delete (struct action_data *ap);
int action_list (struct action_data *ap);
int action_chpass (struct action_data *ap);
48

49
int (*ftab[]) (struct action_data *) = {
50 51 52 53 54 55 56
  action_create,
  action_add,
  action_delete,
  action_list,
  action_chpass
};

57
static char doc[] = N_("GNU popauth -- manage pop3 authentication database");
58 59
static error_t popauth_parse_opt  (int key, char *arg,
				   struct argp_state *astate);
60

61
void popauth_version (FILE *stream, struct argp_state *state);
62

63 64
#define COMPATIBILITY_OPTION 256

65 66
static struct argp_option options[] = 
{
67
  { NULL, 0, NULL, 0, N_("Actions are:"), 1 },
Sergey Poznyakoff authored
68 69 70 71 72
  { "add", 'a', 0, 0, N_("add user"), 1 },
  { "modify", 'm', 0, 0, N_("modify user's record (change password)"), 1 },
  { "delete", 'd', 0, 0, N_("delete user's record"), 1 },
  { "list", 'l', 0, 0, N_("list the contents of DBM file"), 1 },
  { "create", 'c', 0, 0, N_("create the DBM from a plaintext file"), 1 },
73 74

  { NULL, 0, NULL, 0,
75
    N_("Default action is:\n"
76
    "  For the file owner: --list\n"
77
    "  For a user: --modify --user <username>\n"), 2 },
78
  
79
  { NULL, 0, NULL, 0, N_("Options are:"), 3 },
Sergey Poznyakoff authored
80 81 82 83 84
  { "file", 'f', N_("FILE"), 0, N_("read input from FILE (default stdin)"), 3 },
  { "output", 'o', N_("FILE"), 0, N_("direct output to file"), 3 },
  { "password", 'p', N_("STRING"), 0, N_("specify user's password"), 3 },
  { "user", 'u', N_("USERNAME"), 0, N_("specify user name"), 3 },
  { "permissions", 'P', N_("PERM"), 0, N_("force given permissions on the database"), 3 },
85 86
  { "compatibility", COMPATIBILITY_OPTION, NULL, 0,
    N_("backward compatibility mode") },
87 88
  { NULL, }
};
89

90 91 92 93 94 95 96 97
static struct argp argp = {
  options,
  popauth_parse_opt,
  NULL,
  doc,
  NULL,
  NULL, NULL
};
98

99 100 101 102
static const char *popauth_argp_capa[] = {
  "common",
  NULL
};
103

104 105 106 107 108
static void
set_db_perms (struct argp_state *astate, char *opt, int *pperm)
{
  int perm = 0;
   
109
  if (mu_isdigit (opt[0]))
110 111 112 113 114
    {
      char *p;
      perm = strtoul (opt, &p, 8);
      if (*p)
	{
115
	  argp_error (astate, _("invalid octal number: %s"), opt);
Sergey Poznyakoff authored
116
	  exit (EX_USAGE);
117 118 119 120 121
	}
    }
  *pperm = perm;
}

122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
static error_t
popauth_parse_opt (int key, char *arg, struct argp_state *astate)
{
  struct action_data *ap = astate->input;
  switch (key)
    {
    case ARGP_KEY_INIT:
      memset (ap, 0, sizeof(*ap));
      ap->action = -1;
      break;
      
    case 'a':
      check_action (ap->action);
      ap->action = ACT_ADD;
      break;
137 138 139 140 141 142

    case 'c':
      check_action (ap->action);
      ap->action = ACT_CREATE;
      break;
      
143 144 145 146 147 148 149 150 151
    case 'l':
      check_action (ap->action);
      ap->action = ACT_LIST;
      break;
	
    case 'd':
      check_action (ap->action);
      ap->action = ACT_DELETE;
      break;
152
	  
153
    case 'p':
154
      ap->passwd = arg;
155 156 157 158 159 160 161 162
      break;
      
    case 'm':
      check_action (ap->action);
      ap->action = ACT_CHPASS;
      break;
	
    case 'f':
163
      ap->input_name = arg;
164
      break;
165
	  
166
    case 'o':
167
      ap->output_name = arg;
168 169 170
      break;
	
    case 'u':
171
      ap->username = arg;
172 173
      break;
	
174
    case 'P':
175
      set_db_perms (astate, arg, &permissions);
176
      break;
177 178 179 180 181

    case COMPATIBILITY_OPTION:
      compatibility_option = 1;
      break;
	
182 183 184 185 186 187 188 189
    case ARGP_KEY_FINI:
      if (ap->action == -1)
	{
	  /* Deduce the default action */
	  if (getuid () == 0)
	    ap->action = ACT_LIST;
	  else
	    ap->action = ACT_CHPASS;
190
	}
191 192
      break;

193 194
    default:
      return ARGP_ERR_UNKNOWN;
195
    }
196 197
  return 0;
}
198

199 200 201 202
int
main(int argc, char **argv)
{
  struct action_data adata;
203 204

  /* Native Language Support */
Sergey Poznyakoff authored
205
  MU_APP_INIT_NLS ();
206

207 208
  mu_argp_init (NULL, NULL);
  argp_program_version_hook = popauth_version;
209 210
  if (mu_app_init (&argp, popauth_argp_capa, NULL,
		   argc, argv, 0, NULL, &adata))
Sergey Poznyakoff authored
211
    exit (EX_USAGE);
212

213
  return (*ftab[adata.action]) (&adata);
214 215
}

216 217 218 219 220
void
check_action (int action)
{
  if (action != -1)
    {
221
      mu_error (_("You may not specify more than one `-aldp' option"));
Sergey Poznyakoff authored
222
      exit (EX_USAGE);
223 224
    }
}
225 226

int
227 228 229 230 231 232 233 234 235 236 237
check_user_perm (int action, struct action_data *ap)
{
  struct stat sb;
  struct passwd *pw;
  uid_t uid;
  
  if (!ap->input_name)
    ap->input_name = APOP_PASSFILE;

  if (mu_dbm_stat (ap->input_name, &sb))
    {
238 239 240
      if (ap->action == ACT_ADD)
	{
	  DBM_FILE db;
241
	  if (mu_dbm_open (ap->input_name, &db, MU_STREAM_CREAT, permissions))
242
	    {
243 244
	      mu_diag_funcall (MU_DIAG_ERROR, "mu_dbm_open",
			       ap->input_name, errno);
Sergey Poznyakoff authored
245
	      exit (EX_SOFTWARE);
246 247 248 249 250 251
	    }
	  mu_dbm_close (db);
	  mu_dbm_stat (ap->input_name, &sb);
	}
      else
	{
252
	  mu_diag_funcall (MU_DIAG_ERROR, "stat", ap->input_name, errno);
Sergey Poznyakoff authored
253
	  exit (EX_OSERR);
254
	}
255 256 257 258 259 260 261 262
    }

  uid = getuid ();
  if (uid == 0 || sb.st_uid == uid)
    return 0;

  if (ap->username)
    {
263
      mu_error (_("Only the file owner can use --username"));
Sergey Poznyakoff authored
264
      exit (EX_USAGE);
265 266 267 268
    }

  if (action != ACT_CHPASS)
    {
269
      mu_error (_("Operation not allowed"));
Sergey Poznyakoff authored
270
      exit (EX_USAGE);
271 272 273
    }
  pw = getpwuid (uid);
  if (!pw)
Sergey Poznyakoff authored
274
    exit (EX_OSERR);
275 276 277 278
  ap->username = pw->pw_name;
  return 1;
}

279
static void
280
print_entry (mu_stream_t str, DBM_DATUM key, DBM_DATUM contents)
281 282
{
  if (compatibility_option)
283 284 285 286 287
    mu_stream_printf (str, "%.*s: %.*s\n",
		      (int) MU_DATUM_SIZE (key),
		      (char*) MU_DATUM_PTR (key),
		      (int) MU_DATUM_SIZE (contents),
		      (char*) MU_DATUM_PTR (contents));
288
  else
289 290 291 292 293
    mu_stream_printf (str, "%.*s %.*s\n",
		      (int) MU_DATUM_SIZE (key),
		      (char*) MU_DATUM_PTR (key),
		      (int) MU_DATUM_SIZE (contents),
		      (char*) MU_DATUM_PTR (contents));
294 295
}

296 297
int
action_list (struct action_data *ap)
298
{
299
  mu_stream_t str;
300 301 302 303
  DBM_FILE db;
  DBM_DATUM key;
  DBM_DATUM contents;
  
304
  check_user_perm (ACT_LIST, ap);
305
  if (mu_dbm_open (ap->input_name, &db, MU_STREAM_READ, 0))
306
    {
307 308
      mu_error (_("cannot open file %s: %s"),
		ap->input_name, mu_strerror (errno));
309 310 311
      return 1;
    }
  
312
  if (ap->output_name)
313
    {
314 315 316
      int rc = mu_file_stream_create (&str, ap->output_name,
				      MU_STREAM_WRITE|MU_STREAM_CREAT);
      if (rc)
317
	{
318 319
	  mu_error (_("cannot create file %s: %s"),
		    ap->output_name, mu_strerror (rc));
320 321
	  return 1;
	}
322
      mu_stream_truncate (str, 0);
323 324
    }
  else
325 326 327 328
    {
      str = mu_strout;
      mu_stream_ref (str);
    }
329

330 331 332 333 334 335 336 337
  if (ap->username)
    {
      memset (&key, 0, sizeof key);
      memset (&contents, 0, sizeof contents);
      MU_DATUM_PTR (key) = ap->username;
      MU_DATUM_SIZE (key) = strlen (ap->username);
      if (mu_dbm_fetch (db, key, &contents))
	{
338
	  mu_error (_("no such user: %s"), ap->username);
339 340
	}
      else
341
	{
342
	  print_entry (str, key, contents);
343 344
	  mu_dbm_datum_free (&contents);
	}
345 346
    }
  else
347
    {
348 349 350 351 352
      for (key = mu_dbm_firstkey (db); MU_DATUM_PTR(key);
	   key = mu_dbm_nextkey (db, key))
	{
	  memset (&contents, 0, sizeof contents);
	  mu_dbm_fetch (db, key, &contents);
353
	  print_entry (str, key, contents);
354
	  mu_dbm_datum_free (&contents);
355
	}
356
    }
357
  
358
  mu_dbm_close (db);
359
  mu_stream_destroy (&str);
360 361 362 363
  return 0;
}

int
364
action_create (struct action_data *ap)
365
{
366 367
  int rc;
  mu_stream_t in;
368 369 370
  DBM_FILE db;
  DBM_DATUM key;
  DBM_DATUM contents;
371 372
  char *buf = NULL;
  size_t size = 0, len;
373
  int line = 0;
374
  
375 376
  /* Make sure we have proper privileges if popauth is setuid */
  setuid (getuid ());
377
  
378
  if (ap->input_name)
379
    {
380 381
      rc = mu_file_stream_create (&in, ap->input_name, MU_STREAM_READ);
      if (rc)
382
	{
383
	  mu_error (_("cannot open file %s: %s"),
384
		    ap->input_name, mu_strerror (rc));
385 386 387 388 389
	  return 1;
	}
    }
  else
    {
390
      ap->input_name = "";
391 392 393 394 395 396 397
      rc = mu_stdio_stream_create (&in, MU_STDIN_FD, MU_STREAM_READ);
      if (rc)
	{
	  mu_error (_("cannot open standard input: %s"),
		    mu_strerror (rc));
	  return 1;
	}
398 399
    }
  
400 401
  if (!ap->output_name)
    ap->output_name = APOP_PASSFILE;
402
  if (mu_dbm_open (ap->output_name, &db, MU_STREAM_CREAT, permissions))
403
    {
404 405
      mu_error (_("cannot create database %s: %s"),
		ap->output_name, mu_strerror (errno));
406 407 408 409
      return 1;
    }

  line = 0;
410 411
  while ((rc = mu_stream_getline (in, &buf, &size, &len)) == 0
	 && len > 0)
412
    {
413
      char *str, *pass;
414 415

      line++;
416 417 418 419 420
      str = mu_str_stripws (buf);
      if (*str == 0 || *str == '#')
	continue;
      pass = mu_str_skip_class_comp (str, MU_CTYPE_SPACE);
      if (*pass == 0)
421
	{
422
	  mu_error (_("%s:%d: malformed line"), ap->input_name, line);
423 424
	  continue;
	}
425 426 427 428 429 430
      /* Strip trailing semicolon, when in compatibility mode. */
      if (compatibility_option && pass > str && pass[-1] == ':')
	pass[-1] = 0;
      *pass++ = 0;
      pass = mu_str_skip_class (pass, MU_CTYPE_SPACE);
      if (*pass == 0)
431
	{
432
	  mu_error (_("%s:%d: malformed line"), ap->input_name, line);
433 434
	  continue;
	}
435
      
436 437
      memset (&key, 0, sizeof key);
      memset (&contents, 0, sizeof contents);
438 439 440 441
      MU_DATUM_PTR (key) = str;
      MU_DATUM_SIZE (key) = strlen (str);
      MU_DATUM_PTR (contents) = pass;
      MU_DATUM_SIZE (contents) = strlen (pass);
442 443

      if (mu_dbm_insert (db, key, contents, 1))
444
	mu_error (_("%s:%d: cannot store datum"), ap->input_name, line);
445
    }
446
  free (buf);
447
  mu_dbm_close (db);
448
  mu_stream_destroy (&in);
449 450 451
  return 0;
}

452 453 454 455 456 457
int
open_io (int action, struct action_data *ap, DBM_FILE *db, int *not_owner)
{
  int rc = check_user_perm (action, ap);
  if (not_owner)
    *not_owner = rc;
458
  if (mu_dbm_open (ap->input_name, db, MU_STREAM_RDWR, permissions))
459
    {
460 461
      mu_error (_("cannot open file %s: %s"),
		ap->input_name, mu_strerror (errno));
462 463 464 465 466 467 468 469 470 471 472
      return 1;
    }
  return 0;
}

void
fill_pass (struct action_data *ap)
{
  if (!ap->passwd)
    {
      char *p;
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
      mu_stream_t in, out;
      int rc;
      
      rc = mu_stdio_stream_create (&in, MU_STDIN_FD, MU_STREAM_READ);
      if (rc)
	{
	  mu_diag_funcall (MU_DIAG_ERROR, "mu_stdio_stream_create",
			   "MU_STDIN_FD", rc);
	  return;
	}
 
      rc = mu_stdio_stream_create (&out, MU_STDOUT_FD, MU_STREAM_WRITE);
      if (rc)
	{
	  mu_diag_funcall (MU_DIAG_ERROR, "mu_stdio_stream_create",
			   "MU_STDOUT_FD", rc);
	  return;
	}
491

492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523
      while (1)
	{
	  if (ap->passwd)
	    free (ap->passwd);
	  rc = mu_getpass (in, out, _("Password:"), &p);
	  if (rc)
	    {
	      mu_diag_funcall (MU_DIAG_ERROR, "mu_getpass", NULL, rc);
	      exit (EX_DATAERR);
	    }
	  
	  if (!p)
	    exit (EX_DATAERR);
	  
	  ap->passwd = strdup (p);
	  /* TRANSLATORS: Please try to format this string so that it has
	     the same length as the translation of 'Password:' above */
	  rc = mu_getpass (in, out, _("Confirm :"), &p);
	  if (rc)
	    {
	      mu_diag_funcall (MU_DIAG_ERROR, "mu_getpass", NULL, rc);
	      exit (EX_DATAERR);
	    }
	  
	  if (!p)
	    exit (EX_DATAERR);
	  if (strcmp (ap->passwd, p) == 0)
	    break;
	  mu_error (_("Passwords differ. Please retry."));
	}
      mu_stream_destroy (&in);
      mu_stream_destroy (&out);
524 525 526 527 528 529 530 531 532 533 534 535 536
    }
}

int
action_add (struct action_data *ap)
{
  DBM_FILE db;
  DBM_DATUM key;
  DBM_DATUM contents;
  int rc;
  
  if (!ap->username)
    {
537
      mu_error (_("missing username to add"));
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554
      return 1;
    }

  if (open_io (ACT_ADD, ap, &db, NULL))
    return 1;

  fill_pass (ap);
  
  memset (&key, 0, sizeof key);
  memset (&contents, 0, sizeof contents);
  MU_DATUM_PTR (key) = ap->username;
  MU_DATUM_SIZE (key) = strlen (ap->username);
  MU_DATUM_PTR (contents) = ap->passwd;
  MU_DATUM_SIZE (contents) = strlen (ap->passwd);

  rc = mu_dbm_insert (db, key, contents, 1);
  if (rc)
555
    mu_error (_("cannot store datum"));
556 557 558 559 560 561 562 563 564 565 566 567 568 569

  mu_dbm_close (db);
  return rc;
}

int
action_delete (struct action_data *ap)
{
  DBM_FILE db;
  DBM_DATUM key;
  int rc;
  
  if (!ap->username)
    {
570
      mu_error (_("missing username to delete"));
571 572 573 574 575 576 577 578 579 580 581
      return 1;
    }

  if (open_io (ACT_DELETE, ap, &db, NULL))
    return 1;
  
  MU_DATUM_PTR (key) = ap->username;
  MU_DATUM_SIZE (key) = strlen (ap->username);

  rc = mu_dbm_delete (db, key);
  if (rc)
582
    mu_error (_("cannot remove record for %s"), ap->username);
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601

  mu_dbm_close (db);
  return rc;
}

int
action_chpass (struct action_data *ap)
{
  DBM_FILE db;
  DBM_DATUM key;
  DBM_DATUM contents;
  int rc;
  int not_owner;
  
  if (open_io (ACT_CHPASS, ap, &db, &not_owner))
    return 1;

  if (!ap->username)
    {
602
      mu_error (_("missing username"));
603 604 605 606 607 608 609 610 611 612
      return 1;
    }

  memset (&key, 0, sizeof key);
  memset (&contents, 0, sizeof contents);

  MU_DATUM_PTR (key) = ap->username;
  MU_DATUM_SIZE (key) = strlen (ap->username);
  if (mu_dbm_fetch (db, key, &contents))
    {
613
      mu_error (_("no such user: %s"), ap->username);
614 615 616 617 618 619 620 621 622 623
      return 1;
    }

  if (not_owner)
    {
      char *oldpass, *p;
      
      oldpass = xmalloc (MU_DATUM_SIZE (contents) + 1);
      memcpy (oldpass, MU_DATUM_PTR (contents), MU_DATUM_SIZE (contents));
      oldpass[MU_DATUM_SIZE (contents)] = 0;
624
      p = getpass (_("Old Password:"));
625 626 627 628
      if (!p)
	return 1;
      if (strcmp (oldpass, p))
	{
629
	  mu_error (_("Sorry"));
630 631 632 633 634 635
	  return 1;
	}
    }

  fill_pass (ap);
  
636
  mu_dbm_datum_free (&contents);
637 638 639 640
  MU_DATUM_PTR (contents) = ap->passwd;
  MU_DATUM_SIZE (contents) = strlen (ap->passwd);
  rc = mu_dbm_insert (db, key, contents, 1);
  if (rc)
641
    mu_error (_("cannot replace datum"));
642 643 644 645 646

  mu_dbm_close (db);
  return rc;
}

647 648
void
popauth_version (FILE *stream, struct argp_state *state)
649 650 651
{
#if defined(WITH_GDBM)
# define FORMAT "GDBM"
652 653
#elif defined(WITH_BDB)
# define FORMAT "Berkeley DB"
654
#elif defined(WITH_NDBM)
655
# define FORMAT "NDBM"
656
#elif defined(WITH_OLD_DBM)
657 658 659
# define FORMAT "Old DBM"
#elif defined(WITH_TOKYOCABINET)
# define FORMAT "Tokyo Cabinet"
660
#endif
661
  mu_program_version_hook (stream, state);
662 663
  fprintf (stream, _("Database format: %s\n"), FORMAT);
  fprintf (stream, _("Database location: %s\n"), APOP_PASSFILE);
Sergey Poznyakoff authored
664
  exit (EX_OK);
665
}