Mercurial > dovecot > original-hg > dovecot-1.2
comparison src/master/login-process.c @ 4538:9d9e72374164 HEAD
Fixes to login process handling, especially with
login_process_per_connection=no. Removed login_max_logging_users setting
since it was somewhat weird in how it worked. Added login_max_connections to
replace it with login_process_per_connection=no, and with =yes its
functionality is now within login_max_processes_count.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Sun, 06 Aug 2006 23:05:32 +0300 |
parents | af61031c746f |
children | 9dc62b7594de |
comparison
equal
deleted
inserted
replaced
4537:555c27e58cb1 | 4538:9d9e72374164 |
---|---|
19 #include <unistd.h> | 19 #include <unistd.h> |
20 #include <syslog.h> | 20 #include <syslog.h> |
21 | 21 |
22 struct login_process { | 22 struct login_process { |
23 struct login_group *group; | 23 struct login_group *group; |
24 struct login_process *prev_nonlisten, *next_nonlisten; | 24 struct login_process *prev_prelogin, *next_prelogin; |
25 int refcount; | 25 int refcount; |
26 | 26 |
27 pid_t pid; | 27 pid_t pid; |
28 int fd; | 28 int fd; |
29 struct io *io; | 29 struct io *io; |
30 struct ostream *output; | 30 struct ostream *output; |
31 enum master_login_state state; | |
32 | |
31 unsigned int initialized:1; | 33 unsigned int initialized:1; |
32 unsigned int listening:1; | |
33 unsigned int destroyed:1; | 34 unsigned int destroyed:1; |
35 unsigned int inetd_child:1; | |
34 }; | 36 }; |
35 | 37 |
36 struct login_auth_request { | 38 struct login_auth_request { |
37 struct login_process *process; | 39 struct login_process *process; |
38 unsigned int tag; | 40 unsigned int tag; |
112 i_error("close(mail client) failed: %m"); | 114 i_error("close(mail client) failed: %m"); |
113 login_process_unref(request->process); | 115 login_process_unref(request->process); |
114 i_free(request); | 116 i_free(request); |
115 } | 117 } |
116 | 118 |
117 static void login_process_mark_nonlistening(struct login_process *p) | 119 static void process_remove_from_prelogin_lists(struct login_process *p) |
118 { | 120 { |
119 if (!p->listening) { | 121 if (p->state != LOGIN_STATE_FULL_PRELOGINS) |
120 i_error("login: received another \"not listening\" " | 122 return; |
121 "notification (if you can't login at all, " | 123 |
122 "see src/lib/fdpass.c)"); | 124 if (p->prev_prelogin == NULL) |
123 return; | 125 p->group->oldest_prelogin_process = p->next_prelogin; |
124 } | 126 else |
125 | 127 p->prev_prelogin->next_prelogin = p->next_prelogin; |
126 p->listening = FALSE; | 128 |
127 | 129 if (p->next_prelogin == NULL) |
128 if (p->group != NULL) { | 130 p->group->newest_prelogin_process = p->prev_prelogin; |
131 else | |
132 p->next_prelogin->prev_prelogin = p->prev_prelogin; | |
133 | |
134 p->prev_prelogin = p->next_prelogin = NULL; | |
135 } | |
136 | |
137 static void process_mark_nonlistening(struct login_process *p, | |
138 enum master_login_state new_state) | |
139 { | |
140 if (p->group == NULL) | |
141 return; | |
142 | |
143 if (p->state == LOGIN_STATE_LISTENING) | |
129 p->group->listening_processes--; | 144 p->group->listening_processes--; |
130 p->prev_nonlisten = p->group->newest_nonlisten_process; | 145 |
131 | 146 if (new_state == LOGIN_STATE_FULL_PRELOGINS) { |
132 if (p->group->newest_nonlisten_process != NULL) | 147 /* add to prelogin list */ |
133 p->group->newest_nonlisten_process->next_nonlisten = p; | 148 i_assert(p->state != new_state); |
134 p->group->newest_nonlisten_process = p; | 149 |
135 | 150 p->prev_prelogin = p->group->newest_prelogin_process; |
136 if (p->group->oldest_nonlisten_process == NULL) | 151 if (p->group->newest_prelogin_process == NULL) |
137 p->group->oldest_nonlisten_process = p; | 152 p->group->oldest_prelogin_process = p; |
138 } | 153 else |
154 p->group->newest_prelogin_process->next_prelogin = p; | |
155 p->group->newest_prelogin_process = p; | |
156 } else { | |
157 process_remove_from_prelogin_lists(p); | |
158 } | |
159 } | |
160 | |
161 static void process_mark_listening(struct login_process *p) | |
162 { | |
163 if (p->group == NULL) | |
164 return; | |
165 | |
166 if (p->state != LOGIN_STATE_LISTENING) | |
167 p->group->listening_processes++; | |
168 | |
169 process_remove_from_prelogin_lists(p); | |
170 } | |
171 | |
172 static void | |
173 login_process_set_state(struct login_process *p, enum master_login_state state) | |
174 { | |
175 if (state == p->state || state > LOGIN_STATE_COUNT || | |
176 (state < p->state && p->group->set->login_process_per_connection)) { | |
177 i_error("login: tried to change state %d -> %d " | |
178 "(if you can't login at all, see src/lib/fdpass.c)", | |
179 p->state, state); | |
180 return; | |
181 } | |
182 | |
183 if (state == LOGIN_STATE_LISTENING) { | |
184 process_mark_listening(p); | |
185 } else { | |
186 process_mark_nonlistening(p, state); | |
187 } | |
188 | |
189 p->state = state; | |
139 } | 190 } |
140 | 191 |
141 static void login_process_groups_create(void) | 192 static void login_process_groups_create(void) |
142 { | 193 { |
143 struct server_settings *server; | 194 struct server_settings *server; |
269 return; | 320 return; |
270 } | 321 } |
271 | 322 |
272 if (client_fd == -1) { | 323 if (client_fd == -1) { |
273 /* just a notification that the login process */ | 324 /* just a notification that the login process */ |
325 enum master_login_state state = req.tag; | |
326 | |
274 if (!p->initialized) { | 327 if (!p->initialized) { |
275 /* initialization notify */ | 328 /* initialization notify */ |
276 p->initialized = TRUE;; | 329 p->initialized = TRUE;; |
277 } else { | 330 } else { |
278 /* not listening for new connections anymore */ | 331 /* change "listening for new connections" status */ |
279 login_process_mark_nonlistening(p); | 332 login_process_set_state(p, state); |
280 } | 333 } |
281 return; | 334 return; |
282 } | 335 } |
283 | 336 |
284 fd_close_on_exec(client_fd, TRUE); | 337 fd_close_on_exec(client_fd, TRUE); |
314 p = i_new(struct login_process, 1); | 367 p = i_new(struct login_process, 1); |
315 p->group = group; | 368 p->group = group; |
316 p->refcount = 1; | 369 p->refcount = 1; |
317 p->pid = pid; | 370 p->pid = pid; |
318 p->fd = fd; | 371 p->fd = fd; |
319 p->listening = TRUE; | |
320 p->io = io_add(fd, IO_READ, login_process_input, p); | 372 p->io = io_add(fd, IO_READ, login_process_input, p); |
321 p->output = o_stream_create_file(fd, default_pool, | 373 p->output = o_stream_create_file(fd, default_pool, |
322 sizeof(struct master_login_reply)*10, | 374 sizeof(struct master_login_reply)*10, |
323 FALSE); | 375 FALSE); |
324 | 376 |
325 PID_ADD_PROCESS_TYPE(pid, PROCESS_TYPE_LOGIN); | 377 PID_ADD_PROCESS_TYPE(pid, PROCESS_TYPE_LOGIN); |
326 hash_insert(processes, POINTER_CAST(pid), p); | 378 hash_insert(processes, POINTER_CAST(pid), p); |
327 | 379 |
380 p->state = LOGIN_STATE_LISTENING; | |
381 | |
328 if (p->group != NULL) { | 382 if (p->group != NULL) { |
329 p->group->processes++; | 383 p->group->processes++; |
330 p->group->listening_processes++; | 384 p->group->listening_processes++; |
331 } | 385 } |
332 return p; | 386 return p; |
333 } | 387 } |
334 | 388 |
335 static void login_process_remove_from_lists(struct login_process *p) | 389 static void login_process_exited(struct login_process *p) |
336 { | 390 { |
337 if (p->group == NULL) | 391 if (p->group != NULL) |
338 return; | 392 p->group->processes--; |
339 | 393 |
340 if (p == p->group->oldest_nonlisten_process) | 394 hash_remove(processes, POINTER_CAST(p->pid)); |
341 p->group->oldest_nonlisten_process = p->next_nonlisten; | 395 login_process_unref(p); |
342 else | |
343 p->prev_nonlisten->next_nonlisten = p->next_nonlisten; | |
344 | |
345 if (p == p->group->newest_nonlisten_process) | |
346 p->group->newest_nonlisten_process = p->prev_nonlisten; | |
347 else | |
348 p->next_nonlisten->prev_nonlisten = p->prev_nonlisten; | |
349 | |
350 p->next_nonlisten = p->prev_nonlisten = NULL; | |
351 } | 396 } |
352 | 397 |
353 static void login_process_destroy(struct login_process *p) | 398 static void login_process_destroy(struct login_process *p) |
354 { | 399 { |
355 if (p->destroyed) | 400 if (p->destroyed) |
358 | 403 |
359 if (!p->initialized && io_loop_is_running(ioloop)) { | 404 if (!p->initialized && io_loop_is_running(ioloop)) { |
360 i_error("Login process died too early - shutting down"); | 405 i_error("Login process died too early - shutting down"); |
361 io_loop_stop(ioloop); | 406 io_loop_stop(ioloop); |
362 } | 407 } |
363 | |
364 if (p->listening && p->group != NULL) | |
365 p->group->listening_processes--; | |
366 | 408 |
367 o_stream_close(p->output); | 409 o_stream_close(p->output); |
368 io_remove(&p->io); | 410 io_remove(&p->io); |
369 if (close(p->fd) < 0) | 411 if (close(p->fd) < 0) |
370 i_error("close(login) failed: %m"); | 412 i_error("close(login) failed: %m"); |
371 | 413 |
372 if (!p->listening) | 414 process_mark_nonlistening(p, LOGIN_STATE_FULL_LOGINS); |
373 login_process_remove_from_lists(p); | 415 |
374 | 416 if (p->inetd_child) |
375 if (p->group != NULL) | 417 login_process_exited(p); |
376 p->group->processes--; | |
377 | |
378 if (p->pid != 0) | |
379 hash_remove(processes, POINTER_CAST(p->pid)); | |
380 | |
381 login_process_unref(p); | |
382 } | 418 } |
383 | 419 |
384 static void login_process_unref(struct login_process *p) | 420 static void login_process_unref(struct login_process *p) |
385 { | 421 { |
386 if (--p->refcount > 0) | 422 if (--p->refcount > 0) |
443 | 479 |
444 if (set->login_process_per_connection) { | 480 if (set->login_process_per_connection) { |
445 env_put("PROCESS_PER_CONNECTION=1"); | 481 env_put("PROCESS_PER_CONNECTION=1"); |
446 env_put("MAX_LOGGING_USERS=1"); | 482 env_put("MAX_LOGGING_USERS=1"); |
447 } else { | 483 } else { |
448 env_put(t_strdup_printf("MAX_LOGGING_USERS=%u", | 484 env_put(t_strdup_printf("MAX_CONNECTIONS=%u", |
449 set->login_max_logging_users)); | 485 set->login_max_connections)); |
450 } | 486 } |
451 | 487 |
452 env_put(t_strconcat("PROCESS_UID=", dec2str(pid), NULL)); | 488 env_put(t_strconcat("PROCESS_UID=", dec2str(pid), NULL)); |
453 env_put(t_strconcat("GREETING=", set->login_greeting, NULL)); | 489 env_put(t_strconcat("GREETING=", set->login_greeting, NULL)); |
454 env_put(t_strconcat("LOG_FORMAT_ELEMENTS=", | 490 env_put(t_strconcat("LOG_FORMAT_ELEMENTS=", |
470 struct log_io *log; | 506 struct log_io *log; |
471 unsigned int max_log_lines_per_sec; | 507 unsigned int max_log_lines_per_sec; |
472 const char *prefix; | 508 const char *prefix; |
473 pid_t pid; | 509 pid_t pid; |
474 int fd[2], log_fd; | 510 int fd[2], log_fd; |
475 | |
476 if (group->set->login_process_per_connection && | |
477 group->processes - group->listening_processes >= | |
478 group->set->login_max_logging_users) { | |
479 if (group->oldest_nonlisten_process != NULL) | |
480 login_process_destroy(group->oldest_nonlisten_process); | |
481 } | |
482 | 511 |
483 if (group->set->login_uid == 0) | 512 if (group->set->login_uid == 0) |
484 i_fatal("Login process must not run as root"); | 513 i_fatal("Login process must not run as root"); |
485 | 514 |
486 /* create communication to process with a socket pair */ | 515 /* create communication to process with a socket pair */ |
567 i_fatal_status(FATAL_EXEC, "execv(%s) failed: %m", | 596 i_fatal_status(FATAL_EXEC, "execv(%s) failed: %m", |
568 group->set->login_executable); | 597 group->set->login_executable); |
569 return -1; | 598 return -1; |
570 } | 599 } |
571 | 600 |
572 void login_process_abormal_exit(pid_t pid) | 601 void login_process_destroyed(pid_t pid, bool abnormal_exit) |
573 { | 602 { |
574 struct login_process *p; | 603 struct login_process *p; |
575 | 604 |
576 /* don't start raising the process count if they're dying all | |
577 the time */ | |
578 p = hash_lookup(processes, POINTER_CAST(pid)); | 605 p = hash_lookup(processes, POINTER_CAST(pid)); |
579 if (p != NULL && p->group != NULL) | 606 if (p == NULL) |
580 p->group->wanted_processes_count = 0; | 607 i_panic("Lost login process PID %s", dec2str(pid)); |
581 } | 608 i_assert(!p->inetd_child); |
582 | 609 |
583 void login_processes_destroy_all(void) | 610 if (abnormal_exit) { |
611 /* don't start raising the process count if they're dying all | |
612 the time */ | |
613 if (p->group != NULL) | |
614 p->group->wanted_processes_count = 0; | |
615 } | |
616 | |
617 login_process_destroy(p); | |
618 login_process_exited(p); | |
619 } | |
620 | |
621 void login_processes_destroy_all(bool unref) | |
584 { | 622 { |
585 struct hash_iterate_context *iter; | 623 struct hash_iterate_context *iter; |
586 void *key, *value; | 624 void *key, *value; |
587 | 625 |
588 iter = hash_iterate_init(processes); | 626 iter = hash_iterate_init(processes); |
589 while (hash_iterate(iter, &key, &value)) | 627 while (hash_iterate(iter, &key, &value)) { |
590 login_process_destroy(value); | 628 login_process_destroy(value); |
629 if (unref) login_process_unref(value); | |
630 } | |
591 hash_iterate_deinit(iter); | 631 hash_iterate_deinit(iter); |
592 | 632 |
593 while (login_groups != NULL) { | 633 while (login_groups != NULL) { |
594 struct login_group *group = login_groups; | 634 struct login_group *group = login_groups; |
595 | 635 |
596 login_groups = group->next; | 636 login_groups = group->next; |
597 login_group_destroy(group); | 637 login_group_destroy(group); |
598 } | 638 } |
599 } | 639 } |
600 | 640 |
641 static void login_processes_notify_group(struct login_group *group) | |
642 { | |
643 struct hash_iterate_context *iter; | |
644 struct master_login_reply reply; | |
645 void *key, *value; | |
646 | |
647 memset(&reply, 0, sizeof(reply)); | |
648 | |
649 iter = hash_iterate_init(processes); | |
650 while (hash_iterate(iter, &key, &value)) { | |
651 struct login_process *p = value; | |
652 | |
653 if (p->group == group) | |
654 (void)o_stream_send(p->output, &reply, sizeof(reply)); | |
655 } | |
656 hash_iterate_deinit(iter); | |
657 } | |
658 | |
601 static int login_group_start_missings(struct login_group *group) | 659 static int login_group_start_missings(struct login_group *group) |
602 { | 660 { |
603 if (!group->set->login_process_per_connection) { | 661 if (group->set->login_process_per_connection && |
604 /* create max. one process every second, that way if it keeps | 662 group->processes >= group->set->login_max_processes_count && |
605 dying all the time we don't eat all cpu with fork()ing. */ | 663 group->listening_processes == 0) { |
606 if (group->listening_processes < | 664 /* destroy the oldest listening process. non-listening |
607 group->set->login_processes_count) { | 665 processes are logged in users who we don't want to kick out |
608 if (create_login_process(group) < 0) | 666 because someone's started flooding */ |
609 return -1; | 667 if (group->oldest_prelogin_process != NULL) |
610 } | 668 login_process_destroy(group->oldest_prelogin_process); |
611 return 0; | |
612 } | 669 } |
613 | 670 |
614 /* we want to respond fast when multiple clients are connecting | 671 /* we want to respond fast when multiple clients are connecting |
615 at once, but we also want to prevent fork-bombing. use the | 672 at once, but we also want to prevent fork-bombing. use the |
616 same method as apache: check once a second if we need new | 673 same method as apache: check once a second if we need new |
625 group->wanted_processes_count *= 2; | 682 group->wanted_processes_count *= 2; |
626 else if (group->wanted_processes_count > | 683 else if (group->wanted_processes_count > |
627 group->set->login_processes_count) | 684 group->set->login_processes_count) |
628 group->wanted_processes_count--; | 685 group->wanted_processes_count--; |
629 | 686 |
630 if (group->wanted_processes_count > | 687 while (group->listening_processes < group->wanted_processes_count && |
631 group->set->login_max_processes_count) { | 688 group->processes < group->set->login_max_processes_count) { |
632 group->wanted_processes_count = | |
633 group->set->login_max_processes_count; | |
634 } | |
635 | |
636 while (group->listening_processes < group->wanted_processes_count) { | |
637 if (create_login_process(group) < 0) | 689 if (create_login_process(group) < 0) |
638 return -1; | 690 return -1; |
691 } | |
692 | |
693 if (group->listening_processes == 0 && | |
694 !group->set->login_process_per_connection) { | |
695 /* we've reached our limit. notify the processes to start | |
696 listening again which makes them kill some of their | |
697 oldest clients when accepting the next connection */ | |
698 login_processes_notify_group(group); | |
639 } | 699 } |
640 return 0; | 700 return 0; |
641 } | 701 } |
642 | 702 |
643 static void login_processes_stall(void) | 703 static void login_processes_stall(void) |
742 net_set_nonblock(fd, TRUE); | 802 net_set_nonblock(fd, TRUE); |
743 fd_close_on_exec(fd, TRUE); | 803 fd_close_on_exec(fd, TRUE); |
744 | 804 |
745 p = login_process_new(NULL, ++login_pid_counter, fd); | 805 p = login_process_new(NULL, ++login_pid_counter, fd); |
746 p->initialized = TRUE; | 806 p->initialized = TRUE; |
807 p->inetd_child = TRUE; | |
747 } | 808 } |
748 } | 809 } |
749 | 810 |
750 void login_processes_init(void) | 811 void login_processes_init(void) |
751 { | 812 { |
769 if (to != NULL) | 830 if (to != NULL) |
770 timeout_remove(&to); | 831 timeout_remove(&to); |
771 if (io_listen != NULL) | 832 if (io_listen != NULL) |
772 io_remove(&io_listen); | 833 io_remove(&io_listen); |
773 | 834 |
774 login_processes_destroy_all(); | 835 login_processes_destroy_all(TRUE); |
775 hash_destroy(processes); | 836 hash_destroy(processes); |
776 } | 837 } |