Blame view

imap4d/imap4d.c 15.6 KB
1
/* GNU Mailutils -- a suite of utilities for electronic mail
2 3
   Copyright (C) 1999, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008,
   2009, 2010 Free Software Foundation, Inc.
Jakob Kaivo authored
4

5
   GNU Mailutils is free software; you can redistribute it and/or modify
Jakob Kaivo authored
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)
Jakob Kaivo authored
8 9
   any later version.

10
   GNU Mailutils is distributed in the hope that it will be useful,
Jakob Kaivo authored
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/>. */
Jakob Kaivo authored
17 18

#include "imap4d.h"
19 20 21
#ifdef WITH_GSASL
# include <mailutils/gsasl.h>
#endif
22
#include "mailutils/libargp.h"
23
#include "tcpwrap.h"
24

25 26 27 28
mu_m_server_t server;
unsigned int idle_timeout;
int imap4d_transcript;

29 30 31 32 33 34
mu_mailbox_t mbox;              /* Current mailbox */
char *real_homedir;             /* Homedir as returned by user database */
char *imap4d_homedir;           /* Homedir as visible for the remote party */
char *modify_homedir;           /* Expression to produce imap4d_homedir */
int state = STATE_NONAUTH;      /* Current IMAP4 state */
struct mu_auth_data *auth_data; 
35

36 37
int login_disabled;             /* Disable LOGIN command */
int tls_required;               /* Require STARTTLS */
38 39
int create_home_dir;            /* Create home directory if it does not
				   exist */
40 41
int home_dir_mode = S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH;

42 43
int mailbox_mode[NS_MAX];

44 45 46 47
/* Saved command line. */
int imap4d_argc;                 
char **imap4d_argv;

48 49 50 51 52 53
enum imap4d_preauth preauth_mode;
char *preauth_program;
int preauth_only;
int ident_port;
char *ident_keyfile;
int ident_encrypt_only;
54

55
const char *program_version = "imap4d (" PACKAGE_STRING ")";
56
static char doc[] = N_("GNU imap4d -- the IMAP4D daemon.");
57

58 59
#define OPT_PREAUTH         259
#define OPT_FOREGROUND      260
60

61
static struct argp_option options[] = {
Sergey Poznyakoff authored
62 63
  { "foreground", OPT_FOREGROUND, 0, 0, N_("remain in foreground"), 0},
  { "inetd",  'i', 0, 0, N_("run in inetd mode"), 0},
64
  { "daemon", 'd', N_("NUMBER"), OPTION_ARG_OPTIONAL,
Sergey Poznyakoff authored
65
    N_("runs in daemon mode with a maximum of NUMBER children"), 0 },
66

67 68
  { "preauth", OPT_PREAUTH, NULL, 0,
    N_("start in preauth mode") },
69
  
70
  {NULL, 0, NULL, 0, NULL, 0}
71 72
};

73

74 75
static error_t imap4d_parse_opt (int key, char *arg,
				 struct argp_state *state);
76 77 78 79

static struct argp argp = {
  options,
  imap4d_parse_opt,
80
  NULL,
81
  doc,
Sergey Poznyakoff authored
82
  NULL,
83 84
  NULL, NULL
};
85

Sergey Poznyakoff authored
86 87
static const char *imap4d_capa[] = {
  "auth",
88
  "common",
89
  "debug",
90
  "mailbox",
91
  "locking",
92
  "logging",
Sergey Poznyakoff authored
93 94 95
  NULL
};

96
static int imap4d_mainloop (int, int);
97

98 99 100
static error_t
imap4d_parse_opt (int key, char *arg, struct argp_state *state)
{
101
  static mu_list_t lst;
102

103 104
  switch (key)
    {
105
    case 'd':
106
      mu_argp_node_list_new (lst, "mode", "daemon");
107
      if (arg)
108
	mu_argp_node_list_new (lst, "max-children", arg);
109 110 111
      break;

    case 'i':
112
      mu_argp_node_list_new (lst, "mode", "inetd");
113 114 115
      break;

    case OPT_FOREGROUND:
116
      mu_argp_node_list_new (lst, "foreground", "yes");
117 118 119
      break;
      
    case OPT_PREAUTH:
Sergey Poznyakoff authored
120 121 122
      preauth_mode = preauth_stdio;
      break;
      
123 124 125 126 127
    case ARGP_KEY_INIT:
      mu_argp_node_list_init (&lst);
      break;
      
    case ARGP_KEY_FINI:
128
      mu_argp_node_list_finish (lst, NULL, NULL);
129
      break;
130
      
131 132 133 134 135
    default:
      return ARGP_ERR_UNKNOWN;
    }
  return 0;
}
Jakob Kaivo authored
136

137
static int
138
cb_mode (void *data, mu_config_value_t *val)
139 140
{
  char *p;
141
  if (mu_cfg_assert_value_type (val, MU_CFG_STRING))
142 143
    return 1;
  home_dir_mode = strtoul (val->v.string, &p, 8);
144
  if (p[0] || (home_dir_mode & ~0777))
145
    mu_error (_("invalid mode specification: %s"), val->v.string);
146 147 148
  return 0;
}

149
int
150
parse_preauth_scheme (const char *scheme, mu_url_t url)
151 152 153 154 155 156 157 158 159 160
{
  int rc = 0;
  if (strcmp (scheme, "stdio") == 0)
    preauth_mode = preauth_stdio;
  else if (strcmp (scheme, "prog") == 0)
    {
      char *path;
      rc = mu_url_aget_path (url, &path);
      if (rc)
	{
161
	  mu_error (_("URL error: cannot get path: %s"), mu_strerror (rc));
162 163 164 165 166 167 168 169
	  return 1;
	}
      preauth_program = path;
      preauth_mode = preauth_prog;
    }
  else if (strcmp (scheme, "ident") == 0)
    {
      struct servent *sp;
170
      unsigned n;
171 172
      if (url && mu_url_get_port (url, &n) == 0)
	ident_port = (short) n;
173
      else if ((sp = getservbyname ("auth", "tcp")))
174 175 176 177 178 179 180
	ident_port = ntohs (sp->s_port);
      else
	ident_port = 113;
      preauth_mode = preauth_ident;
    }
  else
    {
181
      mu_error (_("unknown preauth scheme"));
182 183 184 185 186 187 188 189 190 191 192
      rc = 1;
    }

  return rc;
}
      
/* preauth prog:///usr/sbin/progname
   preauth ident[://:port]
   preauth stdio
*/
static int
193
cb_preauth (void *data, mu_config_value_t *val)
194
{
195
  if (mu_cfg_assert_value_type (val, MU_CFG_STRING))
196 197
    return 1;
  if (strcmp (val->v.string, "stdio") == 0)
198
    preauth_mode = preauth_stdio;
199
  else if (strcmp (val->v.string, "ident") == 0)
200
    return parse_preauth_scheme (val->v.string, NULL);
201
  else if (val->v.string[0] == '/')
202
    {
203
      preauth_program = xstrdup (val->v.string);
204 205 206 207 208 209
      preauth_mode = preauth_prog;
    }
  else
    {
      mu_url_t url;
      char *scheme;
210
      int rc = mu_url_create (&url, val->v.string);
211 212 213

      if (rc)
	{
214
	  mu_diag_funcall (MU_DIAG_ERROR, "mu_url_create", val->v.string, rc);
215 216 217 218 219 220 221
	  return 1;
	}

      rc = mu_url_aget_scheme (url, &scheme);
      if (rc)
	{
	  mu_url_destroy (&url);
222
	  mu_error (_("URL error: %s"), mu_strerror (rc));
223 224 225
	  return 1;
	}

226
      rc = parse_preauth_scheme (scheme, url);
227 228 229 230 231 232
      mu_url_destroy (&url);
      free (scheme);
      return rc;
    }
  return 0;
}
233 234

static int
235
cb_mailbox_mode (void *data, mu_config_value_t *val)
236
{
237
  const char *p;
238
  if (mu_cfg_assert_value_type (val, MU_CFG_STRING))
239
    return 1;
240
  if (mu_parse_stream_perm_string ((int *)data, val->v.string, &p))
241
    mu_error (_("invalid mode string near %s"), p);
242 243 244
  return 0;
}

245
static struct mu_cfg_param imap4d_cfg_param[] = {
246
  { "homedir", mu_cfg_string, &modify_homedir, 0, NULL,
Sergey Poznyakoff authored
247
    N_("Modify home directory.") },
248 249 250
  { "personal-namespace", MU_CFG_LIST_OF(mu_cfg_string), &namespace[NS_PRIVATE],
    0, NULL, 
    N_("Set personal namespace.") },
251 252 253 254 255 256
  { "other-namespace", MU_CFG_LIST_OF(mu_cfg_string), &namespace[NS_OTHER],
    0, NULL, 
    N_("Set other users' namespace.") },
  { "shared-namespace", MU_CFG_LIST_OF(mu_cfg_string), &namespace[NS_SHARED],
    0, NULL,
    N_("Set shared namespace.") },
257 258 259 260 261 262
  { "other-mailbox-mode", mu_cfg_callback, &mailbox_mode[NS_OTHER], 0,
    cb_mailbox_mode,
    N_("File mode for mailboxes in other namespace.") },
  { "shared-mailbox-mode", mu_cfg_callback, &mailbox_mode[NS_SHARED], 0,
    cb_mailbox_mode,
    N_("File mode for mailboxes in shared namespace.") },
263
  { "login-disabled", mu_cfg_bool, &login_disabled, 0, NULL,
264
    N_("Disable LOGIN command.") },
265
  { "create-home-dir", mu_cfg_bool, &create_home_dir, 0, NULL,
266
    N_("If true, create non-existing user home directories.") },
267
  { "home-dir-mode", mu_cfg_callback, NULL, 0, cb_mode,
268 269
    N_("File mode for creating user home directories (octal)."),
    N_("mode") },
270
  { "tls-required", mu_cfg_bool, &tls_required, 0, NULL,
271
    N_("Always require STARTTLS before entering authentication phase.") },
272
  { "preauth", mu_cfg_callback, NULL, 0, cb_preauth,
273 274 275 276
    N_("Configure PREAUTH mode.  MODE is one of:\n"
       "  prog:///<full-program-name: string>\n"
       "  ident[://:<port: string-or-number>]\n"
       "  stdio"),
277
    N_("mode") },
278
  { "preauth-only", mu_cfg_bool, &preauth_only, 0, NULL,
279 280
    N_("Use only preauth mode.  If unable to setup it, disconnect "
       "immediately.") },
281
  { "ident-keyfile", mu_cfg_string, &ident_keyfile, 0, NULL,
282
    N_("Name of DES keyfile for decoding ecrypted ident responses.") },
Sergey Poznyakoff authored
283
  { "ident-encrypt-only", mu_cfg_bool, &ident_encrypt_only, 0, NULL,
284
    N_("Use only encrypted ident responses.") },
285 286
  { "id-fields", MU_CFG_LIST_OF(mu_cfg_string), &imap4d_id_list, 0, NULL,
    N_("List of fields to return in response to ID command.") },
287 288
  { ".server", mu_cfg_section, NULL, 0, NULL,
    N_("Server configuration.") },
289 290
  { "transcript", mu_cfg_bool, &imap4d_transcript, 0, NULL,
    N_("Set global transcript mode.") },
291
  TCP_WRAPPERS_CONFIG
292 293
  { NULL }
};
294

Jakob Kaivo authored
295
int
296 297
imap4d_session_setup0 ()
{
298 299
  real_homedir = mu_normalize_path (mu_strdup (auth_data->dir));
  if (imap4d_check_home_dir (real_homedir, auth_data->uid, auth_data->gid))
300
    return 1;
301 302 303 304

  if (modify_homedir)
    {
      char *expr = mu_tilde_expansion (modify_homedir, "/", real_homedir);
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
      struct mu_wordsplit ws;
      const char *env[3];

      env[0] = "user";
      env[1] = auth_data->name;
      env[2] = "home";
      env[3] = real_homedir;
      env[4] = NULL;

      ws.ws_env = env;
      if (mu_wordsplit (expr, &ws,
			MU_WRDSF_NOSPLIT | MU_WRDSF_NOCMD |
			MU_WRDSF_ENV | MU_WRDSF_ENV_KV))
	{
	  mu_error (_("cannot expand line `%s': %s"), expr,
		    mu_wordsplit_strerror (&ws));
	  return 1;
	}
      else if (ws.ws_wordc == 0)
	{
	  mu_error (_("expanding %s yields empty string"), expr);
	  return 1;
	}
      imap4d_homedir = strdup (ws.ws_wordv[0]);
      if (!imap4d_homedir)
330
	{
331
	  mu_error ("%s", mu_strerror (errno));
332 333 334 335 336 337 338 339 340 341 342 343 344 345
	  return 1;
	}
    }
  else
    imap4d_homedir = strdup (real_homedir);

  if (strcmp (imap4d_homedir, real_homedir)
      && imap4d_check_home_dir (imap4d_homedir,
				auth_data->uid, auth_data->gid))
    {
      free (imap4d_homedir);
      free (real_homedir);
      return 1;
    }
346 347 348 349
  
  if (auth_data->change_uid)
    setuid (auth_data->uid);

350 351
  util_chdir (imap4d_homedir);
  namespace_init_session (imap4d_homedir);
352
  
353
  mu_diag_output (MU_DIAG_INFO,
354
		  _("user `%s' logged in (source: %s)"), auth_data->name,
355
		  auth_data->source);
356 357 358 359

  if (auth_data->quota)
    quota_setup ();
  
360 361 362 363 364 365 366 367 368
  return 0;
}

int
imap4d_session_setup (char *username)
{
  auth_data = mu_get_auth_by_name (username);
  if (auth_data == NULL)
    {
369
      mu_diag_output (MU_DIAG_INFO, _("user `%s' nonexistent"), username);
370 371 372 373 374
      return 1;
    }
  return imap4d_session_setup0 ();
}

375 376 377
int
get_client_address (int fd, struct sockaddr_in *pcs)
{
378
  socklen_t len = sizeof *pcs;
379 380 381

  if (getpeername (fd, (struct sockaddr *) pcs, &len) < 0)
    {
382
      mu_diag_funcall (MU_DIAG_ERROR, "getpeername", NULL, errno);
383 384 385 386 387
      return 1;
    }
  return 0;
}

388 389 390 391 392 393 394 395
void
imap4d_child_signal_setup (RETSIGTYPE (*handler) (int signo))
{
  static int sigtab[] = { SIGILL, SIGBUS, SIGFPE, SIGSEGV, SIGSTOP, SIGPIPE,
			  SIGABRT, SIGINT, SIGQUIT, SIGTERM, SIGHUP, SIGALRM };
  mu_set_signals (handler, sigtab, MU_ARRAY_SIZE (sigtab));
}

396
static int
397
imap4d_mainloop (int ifd, int ofd)
398
{
399
  imap4d_tokbuf_t tokp;
400
  char *text;
401
  int debug_mode = isatty (ifd);
402

403
  imap4d_child_signal_setup (imap4d_child_signal);
404
  io_setio (ifd, ofd);
405

406
  if (imap4d_preauth_setup (ifd) == 0)
407
    {
408 409
      if (debug_mode)
	{
410
	  mu_diag_output (MU_DIAG_INFO, _("started in debugging mode"));
411 412 413 414
	  text = "IMAP4rev1 Debugging mode";
	}
      else
	text = "IMAP4rev1";
415 416 417
    }
  else
    {
418
      io_flush ();
Sergey Poznyakoff authored
419
      return 0;
420
    }
421

422
  /* Greetings. */
423 424 425
  io_untagged_response ((state == STATE_AUTH) ? 
                        RESP_PREAUTH : RESP_OK, "%s", text);
  io_flush ();
426

427 428
  set_xscript_level ((state == STATE_AUTH) ?
                      MU_XSCRIPT_NORMAL : MU_XSCRIPT_SECURE);
429
  
430
  tokp = imap4d_tokbuf_init ();
431 432
  while (1)
    {
433 434
      if (idle_timeout && io_wait_input (idle_timeout) != 1)
	imap4d_bye (ERR_TIMEOUT);
435
      imap4d_readline (tokp);
436
      /* check for updates */
437
      imap4d_sync ();
438
      util_do_command (tokp);
439
      imap4d_sync ();
440
      io_flush ();
441
    }
Jakob Kaivo authored
442

Sergey Poznyakoff authored
443
  return 0;
444 445
}

446
int
Sergey Poznyakoff authored
447 448
imap4d_connection (int fd, struct sockaddr *sa, int salen, void *data,
		   mu_ip_server_t srv, time_t timeout, int transcript)
449
{
450
  idle_timeout = timeout;
451 452
  if (imap4d_transcript != transcript)
    imap4d_transcript = transcript;
453
  imap4d_mainloop (fd, fd);
454
  return 0;
455
}
456 457 458 459 460 461

int
imap4d_check_home_dir (const char *dir, uid_t uid, gid_t gid)
{
  struct stat st;

462
  if (stat (dir, &st))
463 464 465 466
    {
      if (errno == ENOENT && create_home_dir)
	{
	  mode_t mode = umask (0);
467
	  int rc = mkdir (dir, home_dir_mode);
468 469 470 471
	  umask (mode);
	  if (rc)
	    {
	      mu_error ("Cannot create home directory `%s': %s",
472
			dir, mu_strerror (errno));
473 474
	      return 1;
	    }
475
	  if (chown (dir, uid, gid))
476 477
	    {
	      mu_error ("Cannot set owner for home directory `%s': %s",
478
			dir, mu_strerror (errno));
479 480 481 482 483 484 485 486
	      return 1;
	    }
	}
    }
  
  return 0;
}

487 488 489 490
int
main (int argc, char **argv)
{
  struct group *gr;
Sergey Poznyakoff authored
491 492 493
  int status = 0;
  static int sigtab[] = { SIGILL, SIGBUS, SIGFPE, SIGSEGV, SIGSTOP, SIGPIPE,
			  SIGABRT };
494

495 496 497
  imap4d_argc = argc;
  imap4d_argv = argv;
  
498
  /* Native Language Support */
Sergey Poznyakoff authored
499
  MU_APP_INIT_NLS ();
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517

  state = STATE_NONAUTH;	/* Starting state in non-auth.  */

  MU_AUTH_REGISTER_ALL_MODULES ();
  /* Register the desired formats. */
  mu_register_local_mbox_formats ();
  
  imap4d_capability_init ();
#ifdef WITH_TLS
  mu_gocs_register ("tls", mu_tls_module_init);
#endif /* WITH_TLS */
#ifdef WITH_GSASL
  mu_gocs_register ("gsasl", mu_gsasl_module_init);
#endif
  mu_tcpwrapper_cfg_init ();
  mu_acl_cfg_init ();
  mu_m_server_cfg_init ();
  
518
  mu_argp_init (NULL, NULL);
519

520
  mu_m_server_create (&server, program_version);
521
  mu_m_server_set_conn (server, imap4d_connection);
522
  mu_m_server_set_prefork (server, mu_tcp_wrapper_prefork);
523 524 525 526 527
  mu_m_server_set_mode (server, MODE_INTERACTIVE);
  mu_m_server_set_max_children (server, 20);
  /* FIXME mu_m_server_set_pidfile (); */
  mu_m_server_set_default_port (server, 143);
  mu_m_server_set_timeout (server, 1800);  /* RFC2060: 30 minutes. */
Sergey Poznyakoff authored
528
  mu_m_server_set_strexit (server, mu_strexit);
529
  
530 531
  mu_log_syslog = 1;

532 533
  if (mu_app_init (&argp, imap4d_capa, imap4d_cfg_param, 
		   argc, argv, 0, NULL, server))
Sergey Poznyakoff authored
534
    exit (EX_CONFIG); /* FIXME: No way to discern from EX_USAGE? */
535 536 537 538 539 540 541

  if (login_disabled)
    imap4d_capability_add (IMAP_CAPA_LOGINDISABLED);
#ifdef WITH_TLS
  if (tls_required)
    imap4d_capability_add (IMAP_CAPA_XTLSREQUIRED);
#endif
542 543

  namespace_init ();
544 545 546 547 548 549 550 551 552 553 554 555 556
  
  auth_gssapi_init ();
  auth_gsasl_init ();

#ifdef USE_LIBPAM
  if (!mu_pam_service)
    mu_pam_service = "gnu-imap4d";
#endif

  if (mu_m_server_mode (server) == MODE_DAEMON)
    {
      /* Normal operation: */
      /* First we want our group to be mail so we can access the spool.  */
Sergey Poznyakoff authored
557
      errno = 0;
558 559 560
      gr = getgrnam ("mail");
      if (gr == NULL)
	{
Sergey Poznyakoff authored
561 562
	  if (errno == 0 || errno == ENOENT)
            {
563
               mu_error (_("%s: no such group"), "mail");
Sergey Poznyakoff authored
564 565 566 567
               exit (EX_CONFIG);
            }
          else
            {
568 569
	      mu_diag_funcall (MU_DIAG_ERROR, "getgrnam", "mail", errno);
	      exit (EX_OSERR);
Sergey Poznyakoff authored
570
            }
571 572 573 574
	}

      if (setgid (gr->gr_gid) == -1)
	{
575
	  mu_error (_("error setting mail group: %s"), mu_strerror (errno));
Sergey Poznyakoff authored
576
	  exit (EX_OSERR);
577 578 579 580
	}
    }

  /* Set the signal handlers.  */
Sergey Poznyakoff authored
581
  mu_set_signals (imap4d_master_signal, sigtab, MU_ARRAY_SIZE (sigtab));
582

583 584
  mu_stdstream_strerr_setup (mu_log_syslog ?
			     MU_STRERR_SYSLOG : MU_STRERR_STDERR);
585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604

  umask (S_IROTH | S_IWOTH | S_IXOTH);	/* 007 */

  /* Check TLS environment, i.e. cert and key files */
#ifdef WITH_TLS
  starttls_init ();
#endif /* WITH_TLS */

  /* Actually run the daemon.  */
  if (mu_m_server_mode (server) == MODE_DAEMON)
    {
      mu_m_server_begin (server);
      status = mu_m_server_run (server);
      mu_m_server_end (server);
      mu_m_server_destroy (&server);
    }
  else
    {
      /* Make sure we are in the root directory.  */
      chdir ("/");
605
      status = imap4d_mainloop (MU_STDIN_FD, MU_STDOUT_FD);
606 607 608
    }

  if (status)
609
    mu_error (_("main loop status: %s"), mu_strerror (status));	  
610 611 612
  /* Close the syslog connection and exit.  */
  closelog ();

Sergey Poznyakoff authored
613
  return status ? EX_SOFTWARE : EX_OK;
614 615
}