1 // Copyright 2009 The Archiveopteryx Developers <info@aox.org>
2 
3 #include "scope.h"
4 #include "server.h"
5 
6 #include "pop.h"
7 #include "imap.h"
8 #include "smtp.h"
9 #include "graph.h"
10 
11 #include "tlsthread.h"
12 #include "flag.h"
13 #include "event.h"
14 #include "cache.h"
15 #include "mailbox.h"
16 #include "listener.h"
17 #include "database.h"
18 #include "dbsignal.h"
19 #include "selector.h"
20 #include "managesieve.h"
21 #include "spoolmanager.h"
22 #include "entropy.h"
23 #include "egd.h"
24 
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
28 #include <stdlib.h> // exit()
29 
30 #include <time.h> // time()
31 
32 
33 /*! \nodoc */
34 
35 
36 class StartupWatcher
37     : public EventHandler
38 {
39 public:
StartupWatcher()40     StartupWatcher(): EventHandler() {}
execute()41     void execute() {
42         if ( Log::disastersYet() )
43             ::exit( 1 );
44         EventLoop::global()->setStartup( false );
45     }
46 };
47 
48 
49 class ConnectionObliterator
50     : public EventHandler
51 {
52 public:
ConnectionObliterator()53     ConnectionObliterator()
54         : EventHandler() {
55         (void)new DatabaseSignal( "obliterated", this );
56     }
execute()57     void execute() {
58         List<Connection>::Iterator i( EventLoop::global()->connections() );
59         while ( i ) {
60             Connection * c = i;
61             ++i;
62             if ( !c->hasProperty( Connection::Listens ) &&
63                  !c->hasProperty( Connection::Internal ) ) {
64                 Scope x( c->log() );
65                 log( "The database was obliterated" );
66                 c->close();
67             }
68         }
69         EventLoop::freeMemorySoon();
70         Cache::clearAllCaches( true );
71     }
72 };
73 
74 
75 class ArchiveopteryxEventLoop
76     : public EventLoop
77 {
78 public:
ArchiveopteryxEventLoop()79     ArchiveopteryxEventLoop()
80         : fine( true ), unhappinessStarted( 0 ) {}
81     ~ArchiveopteryxEventLoop();
82 
83     void freeMemory();
84 
85     bool fine;
86     time_t unhappinessStarted;
87 };
88 
89 
~ArchiveopteryxEventLoop()90 ArchiveopteryxEventLoop::~ArchiveopteryxEventLoop()
91 {
92 }
93 
94 
freeMemory()95 void ArchiveopteryxEventLoop::freeMemory()
96 {
97     EventLoop::freeMemory();
98     uint fromOS = Allocator::allocatedFromOS();
99     if ( fromOS < memoryUsage() ) {
100         fine = true;
101         return;
102     }
103     if ( fine ) {
104         unhappinessStarted = ::time( 0 );
105         fine = false;
106     }
107 
108     // how long have we been in a faq-inducing state?
109     if ( time( 0 ) < unhappinessStarted + 60 )
110         return;
111 
112     // oh no. make the parent replace this process with another.
113     if ( ::fork() > 0 )
114         ::exit( 0 );
115     // the process watcher will notice that the parent fork exited,
116     // and start a replacement. in the child, we shut down fairly
117     // quickly.
118     stop( 60 );
119 }
120 
121 
122 
main(int argc,char * argv[])123 int main( int argc, char *argv[] )
124 {
125     Scope global;
126 
127     Server s( "archiveopteryx", argc, argv );
128     EventLoop::setup( new ArchiveopteryxEventLoop );
129     s.setup( Server::Report );
130 
131     bool security( Configuration::toggle( Configuration::Security ) );
132     EString root( Configuration::text( Configuration::JailDir ) );
133 
134     if ( Configuration::toggle( Configuration::UseSmtp ) ||
135          Configuration::toggle( Configuration::UseLmtp ) )
136     {
137         EString mc( Configuration::text( Configuration::MessageCopy ) );
138         EString mcd( Configuration::text( Configuration::MessageCopyDir ) );
139         if ( mc == "all" || mc == "errors" || mc == "delivered" ) {
140             struct stat st;
141             if ( mcd.isEmpty() )
142                 log( "message-copy-directory not set", Log::Disaster );
143             else if ( ::stat( mcd.cstr(), &st ) < 0 || !S_ISDIR( st.st_mode ) )
144                 log( "Inaccessible message-copy-directory: " + mcd,
145                      Log::Disaster );
146             else if ( security && !mcd.startsWith( root ) )
147                 log( "message-copy-directory must be under jail directory " +
148                      root, Log::Disaster );
149         }
150         else if ( mc == "none" ) {
151             if ( Configuration::present( Configuration::MessageCopyDir ) )
152                 log( "Disregarding message-copy-directory (value " + mcd +
153                      ") because message-copy is set to none " );
154         }
155         else {
156             log( "Invalid value for message-copy: " + mc, Log::Disaster );
157         }
158     }
159 
160 
161     EString sA( Configuration::text( Configuration::SmartHostAddress ) );
162     uint sP( Configuration::scalar( Configuration::SmartHostPort ) );
163 
164     if ( Configuration::toggle( Configuration::UseSmtp ) &&
165          Configuration::scalar( Configuration::SmtpPort ) == sP &&
166          ( Configuration::text( Configuration::SmtpAddress ) == sA ||
167            ( Configuration::text( Configuration::SmtpAddress ) == "" &&
168              sA == "127.0.0.1" ) ) )
169     {
170         log( "smarthost-address/port are the same as smtp-address/port",
171              Log::Disaster );
172     }
173 
174     if ( Configuration::toggle( Configuration::UseLmtp ) &&
175          Configuration::scalar( Configuration::LmtpPort ) == sP &&
176          ( Configuration::text( Configuration::LmtpAddress ) == sA ||
177            ( Configuration::text( Configuration::LmtpAddress ) == "" &&
178              sA == "127.0.0.1" ) ) )
179     {
180         log( "smarthost-address/port are the same as lmtp-address/port",
181              Log::Disaster );
182     }
183 
184     if ( Configuration::toggle( Configuration::UseSmtpSubmit ) &&
185          Configuration::scalar( Configuration::SmtpSubmitPort ) == sP &&
186          ( Configuration::text( Configuration::SmtpSubmitAddress ) == sA ||
187            ( Configuration::text( Configuration::SmtpSubmitAddress ) == "" &&
188              sA == "127.0.0.1" ) ) )
189     {
190         log( "smarthost-address/port are the same as "
191              "smtp-submit-address/port", Log::Disaster );
192     }
193 
194 
195     EString app =
196         Configuration::text( Configuration::AllowPlaintextPasswords ).lower();
197     if ( !( app == "always" || app == "never" ) )
198         ::log( "Unknown value for allow-plaintext-passwords: " + app,
199                Log::Disaster );
200     if ( app == "never" &&
201          Configuration::toggle( Configuration::UseTls ) == false &&
202          Configuration::toggle( Configuration::AuthCramMd5 ) == false &&
203          Configuration::toggle( Configuration::AuthDigestMd5 ) == false )
204         ::log( "allow-plaintext-passwords is 'never' and use-tls is 'false', "
205                "but only plaintext authentication mechanisms are allowed",
206                Log::Disaster );
207 
208     EString apa =
209         Configuration::text( Configuration::AllowPlaintextAccess ).lower();
210     if ( !( apa == "always" || apa == "localhost" || apa == "never" ) )
211         ::log( "Unknown value for allow-plaintext-access: " + apa,
212                Log::Disaster );
213     if ( apa == "never" &&
214          Configuration::toggle( Configuration::UseTls ) == false )
215         ::log( "allow-plaintext-access is 'never', but use-tls is 'false'",
216                Log::Disaster );
217 
218     // set up an EGD server for openssl
219     Entropy::setup();
220     EString egd( root );
221     if ( !egd.endsWith( "/" ) )
222         egd.append( "/" );
223     egd.append( "var/run/egd-pool" );
224     (void)new Listener< EntropyProvider >( Endpoint( egd, 0 ), "EGD" );
225     if ( !security ) {
226         struct stat st;
227         if ( stat( "/var/run/edg-pool", &st ) < 0 ) {
228             log( "Security is disabled and /var/run/edg-pool does not exist. "
229                  "Creating it just in case openssl wants to access it." );
230             (void)new Listener< EntropyProvider >(
231                 Endpoint( "/var/run/edg-pool", 0 ), "EGD(/)" );
232         }
233     }
234     if ( ::chmod( egd.cstr(), 0666 ) < 0 )
235         log( "Could not grant r/w access to EGD socket", Log::Disaster );
236 
237     Listener< IMAP >::create(
238         "IMAP", Configuration::toggle( Configuration::UseImap ),
239         Configuration::ImapAddress, Configuration::ImapPort
240     );
241     Listener< IMAPS >::create(
242         "IMAPS", Configuration::toggle( Configuration::UseImaps ),
243         Configuration::ImapsAddress, Configuration::ImapsPort
244     );
245     Listener< POP >::create(
246         "POP3", Configuration::toggle( Configuration::UsePop ),
247         Configuration::PopAddress, Configuration::PopPort
248     );
249     Listener< POPS >::create(
250         "POP3S", Configuration::toggle( Configuration::UsePops ),
251         Configuration::PopsAddress, Configuration::PopsPort
252     );
253     Listener< ManageSieve >::create(
254         "Sieve", Configuration::toggle( Configuration::UseSieve ),
255         Configuration::ManageSieveAddress, Configuration::ManageSievePort
256     );
257     Listener< SMTP >::create(
258         "SMTP", Configuration::toggle( Configuration::UseSmtp ),
259         Configuration::SmtpAddress, Configuration::SmtpPort
260     );
261     Listener< LMTP >::create(
262         "LMTP", Configuration::toggle( Configuration::UseLmtp ),
263         Configuration::LmtpAddress, Configuration::LmtpPort
264     );
265     Listener< SMTPSubmit >::create(
266         "SMTP-Submit", Configuration::toggle( Configuration::UseSmtpSubmit ),
267         Configuration::SmtpSubmitAddress, Configuration::SmtpSubmitPort
268     );
269     Listener< SMTPS >::create(
270         "SMTPS", Configuration::toggle( Configuration::UseSmtps ),
271         Configuration::SmtpsAddress, Configuration::SmtpsPort
272     );
273 
274     if ( Configuration::toggle( Configuration::UseTls ) ) {
275         TlsThread::setup();
276     }
277 
278     s.setup( Server::LogStartup );
279 
280     Listener< GraphDumper >::create(
281         "Statistics", Configuration::toggle( Configuration::UseStatistics ),
282         Configuration::StatisticsAddress, Configuration::StatisticsPort
283     );
284 
285     EventLoop::global()->setMemoryUsage(
286         1024 * 1024 * Configuration::scalar( Configuration::MemoryLimit ) );
287 
288     s.setup( Server::Finish );
289 
290     Database::setup();
291 
292     StartupWatcher * w = new StartupWatcher;
293 
294     Database::checkSchema( w );
295     if ( security )
296         Database::checkAccess( w );
297     EventLoop::global()->setStartup( true );
298     Mailbox::setup( w );
299 
300     SpoolManager::setup();
301     Selector::setup();
302     Flag::setup();
303     IMAP::setup();
304 
305     if ( !security )
306         (void)new ConnectionObliterator;
307 
308     s.run();
309 }
310