Blame view

pop3d/pop3d.c 16 KB
1
/* GNU Mailutils -- a suite of utilities for electronic mail
2
   Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 
3
   2005, 2007, 2008 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, write to the Free Software
17 18
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
   MA 02110-1301 USA */
19 20

#include "pop3d.h"
21
#include "mailutils/pam.h"
22
#include "mailutils/libargp.h"
23
#include "tcpwrap.h"
24

25
mu_mailbox_t mbox;
26 27 28
int state;
char *username;
char *md5shared;
29

30 31 32
mu_m_server_t server;
unsigned int idle_timeout;
int pop3d_transcript;
33
int debug_mode;
34
int tls_required;
35

36 37 38 39 40
#ifdef WITH_TLS
int tls_available;
int tls_done;
#endif /* WITH_TLS */

41 42
int initial_state = AUTHORIZATION; 

43 44
/* Should all the messages be undeleted on startup */
int undelete_on_startup;
45 46 47 48 49
#ifdef ENABLE_LOGIN_DELAY
/* Minimum allowed delay between two successive logins */
time_t login_delay = 0;
char *login_stat_file = LOGIN_STAT_FILE;
#endif
50

51
unsigned expire = EXPIRE_NEVER; /* Expire messages after this number of days */
52
int expire_on_exit = 0;       /* Delete expired messages on exit */
53

54
static error_t pop3d_parse_opt  (int key, char *arg, struct argp_state *astate);
55

56
const char *program_version = "pop3d (" PACKAGE_STRING ")";
57
static char doc[] = N_("GNU pop3d -- the POP3 daemon");
58

59 60 61 62 63 64 65
#define OPT_LOGIN_DELAY     257
#define OPT_STAT_FILE       258
#define OPT_EXPIRE          259
#define OPT_EXPIRE_ON_EXIT  260
#define OPT_TLS_REQUIRED    261
#define OPT_BULLETIN_SOURCE 262
#define OPT_BULLETIN_DB     263
66
#define OPT_FOREGROUND      264
67

68
static struct argp_option options[] = {
69
#define GRP 0
70 71 72 73 74 75 76
  { "foreground", OPT_FOREGROUND, 0, 0, N_("Remain in foreground."), GRP+1},
  { "inetd",  'i', 0, 0, N_("Run in inetd mode"), GRP+1},
  { "daemon", 'd', N_("NUMBER"), OPTION_ARG_OPTIONAL,
    N_("Runs in daemon mode with a maximum of NUMBER children"), GRP+1 },
#undef GRP

#define GRP 5
77
  {"undelete", 'u', NULL, OPTION_HIDDEN,
78
   N_("Undelete all messages on startup"), GRP+1},
79
  {"expire", OPT_EXPIRE, N_("DAYS"), OPTION_HIDDEN,
80
   N_("Expire read messages after the given number of days"), GRP+1},
81
  {"delete-expired", OPT_EXPIRE_ON_EXIT, NULL, OPTION_HIDDEN,
82
   N_("Delete expired messages upon closing the mailbox"), GRP+1},
83
#ifdef WITH_TLS
84
  {"tls-required", OPT_TLS_REQUIRED, NULL, OPTION_HIDDEN,
85 86
   N_("Always require STLS before entering authentication phase")},
#endif
87 88 89 90
#undef GRP
  
#define GRP 10
#ifdef ENABLE_LOGIN_DELAY
91
  {"login-delay", OPT_LOGIN_DELAY, N_("SECONDS"), OPTION_HIDDEN,
92
   N_("Allowed delay between two successive logins"), GRP+1},
93
  {"stat-file", OPT_STAT_FILE, N_("FILENAME"), OPTION_HIDDEN,
94 95 96 97 98 99
   N_("Name of login statistics file"), GRP+1},
#endif
  
#undef GRP

#define GRP 20
100
  { "bulletin-source", OPT_BULLETIN_SOURCE, N_("MBOX"), OPTION_HIDDEN,
101 102
    N_("Set source mailbox to get bulletins from"), GRP+1 },
#ifdef USE_DBM
103
  { "bulletin-db", OPT_BULLETIN_DB, N_("FILE"), OPTION_HIDDEN,
104 105 106 107
    N_("Set the bulletin database file name"), GRP+1 },
#endif
#undef GRP
  
108 109 110
  {NULL, 0, NULL, 0, NULL, 0}
};

111
static int
112
cb_bulletin_source (mu_debug_t debug, void *data, mu_config_value_t *val)
113
{
114 115 116
  if (mu_cfg_assert_value_type (val, MU_CFG_STRING, debug))
    return 1;
  set_bulletin_source (val->v.string); /* FIXME: Error reporting? */
117 118 119 120
  return 0;
}

static int
121
cb_bulletin_db (mu_debug_t debug, void *data, mu_config_value_t *val)
122
{
123 124 125
  if (mu_cfg_assert_value_type (val, MU_CFG_STRING, debug))
    return 1;
  set_bulletin_db (val->v.string); /* FIXME: Error reporting? */
126 127 128 129
  return 0;
}

static struct mu_cfg_param pop3d_cfg_param[] = {
Sergey Poznyakoff authored
130
  { "undelete", mu_cfg_bool, &undelete_on_startup, 0, NULL,
131
    N_("On startup, clear deletion marks from all the messages.") },
132
  { "expire", mu_cfg_uint, &expire, 0, NULL,
133 134
    N_("Automatically expire read messages after the given number of days."),
    N_("days") },
Sergey Poznyakoff authored
135
  { "delete-expired", mu_cfg_bool, &expire_on_exit, 0, NULL,
136
    N_("Delete expired messages upon closing the mailbox.") },
137
#ifdef WITH_TLS
138
  { "tls-required", mu_cfg_bool, &tls_required, 0, NULL,
139
     N_("Always require STLS before entering authentication phase.") },
140
#endif
141
#ifdef ENABLE_LOGIN_DELAY
142
  { "login-delay", mu_cfg_time, &login_delay, 0, NULL,
143
    N_("Set the minimal allowed delay between two successive logins.") },
144
  { "stat-file", mu_cfg_string, &login_stat_file, 0, NULL,
145
    N_("Set the name of login statistics file (for login-delay).") },
146
#endif
147
  { "bulletin-source", mu_cfg_callback, NULL, 0, cb_bulletin_source,
148 149
    N_("Get bulletins from the specified mailbox."),
    N_("url") },
150
#ifdef USE_DBM
151
  { "bulletin-db", mu_cfg_callback, NULL, 0, cb_bulletin_db,
152 153
    N_("Set the bulletin database file name."),
    N_("file") },
154
#endif
155 156
  { ".server", mu_cfg_section, NULL, 0, NULL,
    N_("Server configuration.") },
157
  TCP_WRAPPERS_CONFIG
158 159 160
  { NULL }
};
    
161
static struct argp argp = {
162
  options,
Sergey Poznyakoff authored
163
  pop3d_parse_opt,
164
  NULL,
165
  doc,
Sergey Poznyakoff authored
166
  NULL,
167 168 169
  NULL, NULL
};

Sergey Poznyakoff authored
170 171
static const char *pop3d_argp_capa[] = {
  "auth",
172
  "common",
173
  "debug",
174
  "mailbox",
175
  "locking",
176 177
  "logging",
  "license",
Sergey Poznyakoff authored
178 179 180 181
  NULL
};

static error_t
182
pop3d_parse_opt (int key, char *arg, struct argp_state *astate)
Sergey Poznyakoff authored
183
{
184
  static struct mu_argp_node_list lst;
185
  
186 187
  switch (key)
    {
188 189 190 191 192 193 194 195 196 197 198 199 200 201
    case 'd':
      mu_argp_node_list_new (&lst, "mode", "daemon");
      if (arg)
	mu_argp_node_list_new (&lst, "max-children", arg);
      break;

    case 'i':
      mu_argp_node_list_new (&lst, "mode", "inetd");
      break;

    case OPT_FOREGROUND:
      mu_argp_node_list_new (&lst, "foreground", "yes");
      break;
      
202
    case 'u':
203
      mu_argp_node_list_new (&lst, "undelete", "yes");
204
      break;
205 206 207

#ifdef ENABLE_LOGIN_DELAY
    case OPT_LOGIN_DELAY:
208
      mu_argp_node_list_new (&lst, "login-delay", arg);
209 210 211
      break;

    case OPT_STAT_FILE:
212
      mu_argp_node_list_new (&lst, "stat-file", arg);
213 214
      break;
#endif  
215
 
216
    case OPT_EXPIRE:
217
      mu_argp_node_list_new (&lst, "expire", arg);
218
      break;
219 220

    case OPT_EXPIRE_ON_EXIT:
221
      mu_argp_node_list_new (&lst, "delete-expired", "yes");
222 223 224 225
      break;

#ifdef WITH_TLS
    case OPT_TLS_REQUIRED:
226
      mu_argp_node_list_new (&lst, "tls-required", "yes");
227 228
      break;
#endif
229 230

    case OPT_BULLETIN_SOURCE:
231
      mu_argp_node_list_new (&lst, "bulletin-source", arg);
232 233 234 235
      break;
      
#ifdef USE_DBM
    case OPT_BULLETIN_DB:
236
      mu_argp_node_list_new (&lst, "bulletin-db", arg);
237 238
      break;
#endif
239
      
240 241 242 243 244 245 246 247
    case ARGP_KEY_INIT:
      mu_argp_node_list_init (&lst);
      break;
      
    case ARGP_KEY_FINI:
      mu_argp_node_list_finish (&lst, NULL, NULL);
      break;
      
Sergey Poznyakoff authored
248 249 250 251 252 253
    default:
      return ARGP_ERR_UNKNOWN;
    }
  return 0;
}

254 255
int
pop3d_get_client_address (int fd, struct sockaddr_in *pcs)
256
{
257
  mu_diag_output (MU_DIAG_INFO, _("Incoming connection opened"));
258

259
  /* log information on the connecting client. */
260 261
  if (debug_mode)
    {
262
      mu_diag_output (MU_DIAG_INFO, _("Started in debugging mode"));
263
      return 1;
264 265 266
    }
  else
    {
267 268 269 270 271 272 273 274
      int len = sizeof *pcs;
      if (getpeername (fd, (struct sockaddr*) pcs, &len) < 0)
	{
	  mu_diag_output (MU_DIAG_ERROR,
			  _("Cannot obtain IP address of client: %s"),
			  strerror (errno));
	  return 1;
	}
275
    }
276
  return 0;
277 278 279 280 281 282 283 284
}

/* The main part of the daemon. This function reads input from the client and
   executes the proper functions. Also handles the bulk of error reporting.
   Arguments:
      fd        --  socket descriptor (for diagnostics)
      infile    --  input stream
      outfile   --  output stream */
285
int
286 287 288 289
pop3d_mainloop (int fd, FILE *infile, FILE *outfile)
{
  int status = OK;
  char buffer[512];
Sergey Poznyakoff authored
290 291 292 293
  static int sigtab[] = { SIGILL, SIGBUS, SIGFPE, SIGSEGV, SIGSTOP, SIGPIPE,
			  SIGABRT, SIGINT, SIGQUIT, SIGTERM, SIGHUP, SIGALRM };

  mu_set_signals (pop3d_child_signal, sigtab, MU_ARRAY_SIZE (sigtab));
294 295 296

  pop3d_setio (infile, outfile);

297
  state = initial_state;
298

299
  /* Prepare the shared secret for APOP.  */
Alain Magloire authored
300 301 302 303 304
  {
    char *local_hostname;
    local_hostname = malloc (MAXHOSTNAMELEN + 1);
    if (local_hostname == NULL)
      pop3d_abquit (ERR_NO_MEM);
305

Alain Magloire authored
306
    /* Get our canonical hostname. */
307
    {
Alain Magloire authored
308 309 310 311 312 313 314 315
      struct hostent *htbuf;
      gethostname (local_hostname, MAXHOSTNAMELEN);
      htbuf = gethostbyname (local_hostname);
      if (htbuf)
	{
	  free (local_hostname);
	  local_hostname = strdup (htbuf->h_name);
	}
316 317
    }

Alain Magloire authored
318 319 320
    md5shared = malloc (strlen (local_hostname) + 51);
    if (md5shared == NULL)
      pop3d_abquit (ERR_NO_MEM);
321

Alain Magloire authored
322 323 324 325
    snprintf (md5shared, strlen (local_hostname) + 50, "<%u.%u@%s>", getpid (),
	      (unsigned)time (NULL), local_hostname);
    free (local_hostname);
  }
326

Alain Magloire authored
327
  /* Lets boogie.  */
328
  pop3d_outf ("+OK POP3 Ready %s\r\n", md5shared);
329

330 331
  while (state != UPDATE)
    {
Alain Magloire authored
332 333
      char *buf, *arg, *cmd;

334
      pop3d_flush_output ();
335
      status = OK;
336
      buf = pop3d_readline (buffer, sizeof (buffer));
337 338 339
      cmd = pop3d_cmd (buf);
      arg = pop3d_args (buf);

Alain Magloire authored
340 341 342 343 344
      /* The mailbox size needs to be check to make sure that we are in
	 sync.  Some other applications may not respect the *.lock or
	 the lock may be stale because downloading on slow modem.
	 We rely on the size of the mailbox for the check and bail if out
	 of sync.  */
345
      if (state == TRANSACTION && !mu_mailbox_is_updated (mbox))
346
	{
347 348
	  static mu_off_t mailbox_size;
	  mu_off_t newsize = 0;
349
	  mu_mailbox_get_size (mbox, &newsize);
Alain Magloire authored
350
	  /* Did we shrink?  First time save the size.  */
351 352
	  if (!mailbox_size)
	    mailbox_size = newsize;
Alain Magloire authored
353 354
	  else if (newsize < mailbox_size) /* FIXME: Should it be a != ? */
	    pop3d_abquit (ERR_MBOX_SYNC); /* Out of sync, Bail out.  */
355
	}
356

357 358 359
      /* Refresh the Lock.  */
      pop3d_touchlock ();

360 361 362 363
      if (strlen (arg) > POP_MAXCMDLEN || strlen (cmd) > POP_MAXCMDLEN)
	status = ERR_TOO_LONG;
      else if (strlen (cmd) > 4)
	status = ERR_BAD_CMD;
364
      else if (strncasecmp (cmd, "RETR", 4) == 0)
365
	status = pop3d_retr (arg);
366
      else if (strncasecmp (cmd, "DELE", 4) == 0)
367
	status = pop3d_dele (arg);
368
      else if (strncasecmp (cmd, "USER", 4) == 0)
369
	status = pop3d_user (arg);
370
      else if (strncasecmp (cmd, "QUIT", 4) == 0)
371
	status = pop3d_quit (arg);
372
      else if (strncasecmp (cmd, "APOP", 4) == 0)
373
	status = pop3d_apop (arg);
374
      else if (strncasecmp (cmd, "AUTH", 4) == 0)
375
	status = pop3d_auth (arg);
376
      else if (strncasecmp (cmd, "STAT", 4) == 0)
377
	status = pop3d_stat (arg);
378
      else if (strncasecmp (cmd, "LIST", 4) == 0)
379
	status = pop3d_list (arg);
380
      else if (strncasecmp (cmd, "NOOP", 4) == 0)
381
	status = pop3d_noop (arg);
382
      else if (strncasecmp (cmd, "RSET", 4) == 0)
383
	status = pop3d_rset (arg);
384
      else if ((strncasecmp (cmd, "TOP", 3) == 0) && (strlen (cmd) == 3))
385
	status = pop3d_top (arg);
386
      else if (strncasecmp (cmd, "UIDL", 4) == 0)
387
	status = pop3d_uidl (arg);
388
      else if (strncasecmp (cmd, "CAPA", 4) == 0)
389
	status = pop3d_capa (arg);
390 391
#ifdef WITH_TLS
      else if ((strncasecmp (cmd, "STLS", 4) == 0) && tls_available)
392 393 394 395
	{
	  status = pop3d_stls (arg);
	  if (status)
	    {
396
	      mu_diag_output (MU_DIAG_ERROR, _("Session terminated"));
397 398 399
	      break;
	    }
	}
400
#endif /* WITH_TLS */
401 402 403 404
      else
	status = ERR_BAD_CMD;

      if (status == OK)
Alain Magloire authored
405
	; /* Everything is good.  */
406
      else if (status == ERR_WRONG_STATE)
407
	pop3d_outf ("-ERR " BAD_STATE "\r\n");
408
      else if (status == ERR_BAD_ARGS)
409
	pop3d_outf ("-ERR " BAD_ARGS "\r\n");
410
      else if (status == ERR_NO_MESG)
411
	pop3d_outf ("-ERR " NO_MESG "\r\n");
412
      else if (status == ERR_MESG_DELE)
413
	pop3d_outf ("-ERR " MESG_DELE "\r\n");
414
      else if (status == ERR_NOT_IMPL)
415
	pop3d_outf ("-ERR " NOT_IMPL "\r\n");
416
      else if (status == ERR_BAD_CMD)
417
	pop3d_outf ("-ERR " BAD_COMMAND "\r\n");
418
      else if (status == ERR_BAD_LOGIN)
419
	pop3d_outf ("-ERR " BAD_LOGIN "\r\n");
420
      else if (status == ERR_MBOX_LOCK)
421
	pop3d_outf ("-ERR [IN-USE] " MBOX_LOCK "\r\n");
422
      else if (status == ERR_TOO_LONG)
423
	pop3d_outf ("-ERR " TOO_LONG "\r\n");
424
      else if (status == ERR_FILE)
425
	pop3d_outf ("-ERR " FILE_EXP "\r\n");
426 427 428 429
#ifdef WITH_TLS
      else if (status == ERR_TLS_ACTIVE)
	pop3d_outf ("-ERR " TLS_ACTIVE "\r\n");
#endif /* WITH_TLS */
430 431
      else if (status == ERR_LOGIN_DELAY)
	pop3d_outf ("-ERR [LOGIN-DELAY] " LOGIN_DELAY "\r\n");
432
      else
433
	pop3d_outf ("-ERR unknown error\r\n");
434 435 436 437 438

      free (cmd);
      free (arg);
    }

439
  pop3d_bye ();
440

Sergey Poznyakoff authored
441
  return status;
442 443
}

444
int
Sergey Poznyakoff authored
445 446
pop3d_connection (int fd, struct sockaddr *sa, int salen, void *data,
		  mu_ip_server_t srv, time_t timeout, int transcript)
447 448 449 450 451 452 453 454 455
{
  idle_timeout = timeout;
  pop3d_transcript = transcript;
  pop3d_mainloop (fd, fdopen (fd, "r"), fdopen (fd, "w"));
  return 0;
}

int
main (int argc, char **argv)
456
{
457 458
  struct group *gr;
  int status = OK;
Sergey Poznyakoff authored
459 460
  static int sigtab[] = { SIGILL, SIGBUS, SIGFPE, SIGSEGV, SIGSTOP, SIGPIPE,
			  SIGABRT };
461

462
  /* Native Language Support */
Sergey Poznyakoff authored
463
  MU_APP_INIT_NLS ();
464 465 466 467 468 469 470 471 472 473 474 475 476 477

  MU_AUTH_REGISTER_ALL_MODULES();
  /* Register the desired formats.  */
  mu_register_local_mbox_formats ();

#ifdef WITH_TLS
  mu_gocs_register ("tls", mu_tls_module_init);
#endif /* WITH_TLS */
  mu_tcpwrapper_cfg_init ();
  mu_acl_cfg_init ();
  mu_m_server_cfg_init ();
  
  mu_argp_init (program_version, NULL);
  	
478
  mu_m_server_create (&server, program_version);
479
  mu_m_server_set_conn (server, pop3d_connection);
480
  mu_m_server_set_prefork (server, mu_tcp_wrapper_prefork);
481 482 483 484 485
  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, 110);
  mu_m_server_set_timeout (server, 600);
Sergey Poznyakoff authored
486 487
  mu_m_server_set_strexit (server, mu_strexit);
  
488 489
  if (mu_app_init (&argp, pop3d_argp_capa, pop3d_cfg_param, 
		   argc, argv, 0, NULL, server))
Sergey Poznyakoff authored
490
    exit (EX_CONFIG); /* FIXME: No way to discern from EX_USAGE? */
491

492 493 494
  if (tls_required)
    initial_state = INITIAL;
  
495 496 497 498 499 500 501 502 503
  if (expire == 0)
    expire_on_exit = 1;

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

  if (mu_m_server_mode (server) == MODE_INTERACTIVE && isatty (0))
504
    {
505 506
      /* If input is a tty, switch to debug mode */
      debug_mode = 1;
507
    }
508
  else
509
    {
Sergey Poznyakoff authored
510
      errno = 0;
511 512 513
      gr = getgrnam ("mail");
      if (gr == NULL)
	{
Sergey Poznyakoff authored
514 515 516 517 518 519 520 521 522 523 524
	  if (errno == 0 || errno == ENOENT)
            {
               mu_error (_("%s: No such group"), "mail");
               exit (EX_CONFIG);
            }
          else
            {
	       mu_error (_("Error getting mail group: %s"), 
                         mu_strerror (errno));
	       exit (EX_OSERR);
            }
525
	}
526

527 528
      if (setgid (gr->gr_gid) == -1)
	{
Sergey Poznyakoff authored
529 530
	  mu_error (_("Error setting mail group: %s"), mu_strerror (errno));
	  exit (EX_OSERR);
531
	}
532
    }
533

534
  /* Set the signal handlers.  */
Sergey Poznyakoff authored
535
  mu_set_signals (pop3d_master_signal, sigtab, MU_ARRAY_SIZE (sigtab));
536

537
  /* Set up for syslog.  */
538
  openlog (MU_LOG_TAG (), LOG_PID, mu_log_facility);
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560
  /* Redirect any stdout error from the library to syslog, they
     should not go to the client.  */
  {
    mu_debug_t debug;

    mu_diag_get_debug (&debug);
    mu_debug_set_print (debug, mu_diag_syslog_printer, NULL);
    
    mu_debug_default_printer = mu_debug_syslog_printer;
  }
  
  umask (S_IROTH | S_IWOTH | S_IXOTH);	/* 007 */

  /* Check TLS environment, i.e. cert and key files */
#ifdef WITH_TLS
  tls_available = mu_check_tls_environment ();
  if (tls_available)
    tls_available = mu_init_tls_libs ();
#endif /* WITH_TLS */

  /* Actually run the daemon.  */
  if (mu_m_server_mode (server) == MODE_DAEMON)
561
    {
562 563 564 565 566 567 568 569 570 571
      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 ("/");
      status = pop3d_mainloop (fileno (stdin), stdin, stdout);
572
    }
573 574 575 576 577
  
  if (status)
    mu_error (_("Main loop status: %s"), mu_strerror (status));	  
  /* Close the syslog connection and exit.  */
  closelog ();
Sergey Poznyakoff authored
578
  return status ? EX_SOFTWARE : EX_OK;
579
}
580