1 // Copyright 2009 The Archiveopteryx Developers <info@aox.org>
2
3 #include "user.h"
4
5 #include "helperrowcreator.h"
6 #include "configuration.h"
7 #include "transaction.h"
8 #include "address.h"
9 #include "mailbox.h"
10 #include "query.h"
11 #include "codec.h"
12 class UserData
13 : public Garbage
14 {
15 public:
UserData()16 UserData()
17 : id( 0 ), inbox( 0 ), inboxId( 0 ), home( 0 ), address( 0 ), quota( 0 ),
18 q( 0 ), result( 0 ), t( 0 ), user( 0 ),
19 state( User::Unverified ),
20 mode( LoungingAround )
21 {}
22
23 UString login;
24 UString secret;
25 UString ldapdn;
26 uint id;
27 Mailbox * inbox;
28 uint inboxId;
29 Mailbox * home;
30 Address * address;
31 int64 quota;
32 Query * q;
33 Query * result;
34 Transaction * t;
35 EventHandler * user;
36 EString error;
37 User::State state;
38
39 enum Operation {
40 LoungingAround,
41 Creating,
42 Refreshing,
43 ChangingSecret
44 };
45 Operation mode;
46 };
47
48
49 /*! \class User user.h
50
51 The User class models a single Archiveopteryx user, which may be
52 able to log in, own Mailbox objects, etc.
53 */
54
55
56 /*! Constructs an empty User. The result does not map to anything in
57 the database.
58 */
59
User()60 User::User()
61 : d( new UserData )
62 {
63 // nothing
64 }
65
66
67 /*! Returns the user's state, which is either Unverified (the object has
68 made no attempt to refresh itself from the database), Refreshed (the
69 object was successfully refreshed) or Nonexistent (the object tried
70 to refresh itself, but there was no corresponsing user in the
71 database).
72
73 The state is Unverified initially and is changed by refresh().
74 */
75
state() const76 User::State User::state() const
77 {
78 return d->state;
79 }
80
81
82 /*! Sets this User's id() to \a id. */
83
setId(uint id)84 void User::setId( uint id )
85 {
86 d->id = id;
87 }
88
89
90 /*! Returns the user's ID, ie. the primary key from the database, used
91 to link various other tables to this user.
92 */
93
id() const94 uint User::id() const
95 {
96 return d->id;
97 }
98
99
100 /*! Sets this User object to have login \a string. The database is not
101 updated - \a string is not used except to create Query objects
102 during e.g. refresh().
103 */
104
setLogin(const UString & string)105 void User::setLogin( const UString & string )
106 {
107 d->login = string;
108 }
109
110
111 /*! Returns the User's login string, which is an empty string
112 initially and is set up by refresh().
113 */
114
login() const115 UString User::login() const
116 {
117 return d->login;
118 }
119
120
121 /*! Sets this User to have \a secret as password. The database isn't
122 updated unless e.g. create() is called.
123 */
124
setSecret(const UString & secret)125 void User::setSecret( const UString & secret )
126 {
127 d->secret = secret;
128 }
129
130
131 /*! Returns the User's secret (password), which is an empty string
132 until refresh() has fetched the database contents.
133 */
134
secret() const135 UString User::secret() const
136 {
137 return d->secret;
138 }
139
140
141 /*! Returns the user's LDAP DN, or an empty string if this user
142 doesn't have an LDAP DN. The LDAP DN is only used for performing
143 LDAP authentication (by LdapRelay).
144 */
145
ldapdn() const146 UString User::ldapdn() const
147 {
148 return d->ldapdn;
149 }
150
151
152 /*! Returns a pointer to the user's inbox, or a null pointer if this
153 object doesn't know it or if the user has none.
154 */
155
inbox() const156 Mailbox * User::inbox() const
157 {
158 if ( !d->inbox )
159 d->inbox = Mailbox::find( d->inboxId );
160 return d->inbox;
161 }
162
163
164 /*! Sets this User object to have address \a a. The database is not
165 updated - \a a is not used except maybe to search in refresh().
166 */
167
setAddress(Address * a)168 void User::setAddress( Address * a )
169 {
170 d->address = a;
171 }
172
173
174 /*! Returns the address belonging to this User object, or a null
175 pointer if this User has no Address.
176 */
177
address()178 Address * User::address()
179 {
180 if ( !d->address ) {
181 // XXX: This does not match the documentation above.
182 EString dom = Configuration::hostname();
183 uint i = dom.find( '.' );
184 if ( i > 0 )
185 dom = dom.mid( i+1 );
186 AsciiCodec a;
187 d->address = new Address( UString(), d->login, a.toUnicode( dom ) );
188 }
189 return d->address;
190 }
191
192
193 /*! Returns the user's "home directory" - the mailbox under which all
194 of the user's mailboxes reside.
195
196 This is read-only since at the moment, the Archiveopteryx servers
197 only permit one setting: "/users/" + login. However, the database
198 permits more namespaces than just "/users", so one day this may
199 change.
200 */
201
home() const202 Mailbox * User::home() const
203 {
204 return d->home;
205 }
206
207
208 /*! Returns the mailbox to which \a name refers, or a null pointer if
209 there is no such mailbox.
210 */
211
mailbox(const UString & name) const212 Mailbox * User::mailbox( const UString & name ) const
213 {
214 return Mailbox::find( mailboxName( name ) );
215 }
216
217
218 /*! Returns the canonical name of the mailbox to which \a name
219 refers. This need not be the name of an existing mailbox, or even
220 well-formed.
221 */
222
mailboxName(const UString & name) const223 UString User::mailboxName( const UString & name ) const
224 {
225 if ( name.titlecased() == "INBOX" )
226 return inbox()->name();
227 if ( name.startsWith( "/" ) )
228 return name;
229 UString n;
230 n = home()->name();
231 n.append( '/' );
232 n.append( name );
233 return n;
234 }
235
236
237 /*! Returns true if this user is known to exist in the database, and
238 false if it's unknown or doesn't exist.
239 */
240
exists()241 bool User::exists()
242 {
243 return d->id > 0;
244 }
245
246
execute()247 void User::execute()
248 {
249 switch( d->mode ) {
250 case UserData::Creating:
251 createHelper();
252 break;
253 case UserData::Refreshing:
254 refreshHelper();
255 break;
256 case UserData::ChangingSecret:
257 csHelper();
258 break;
259 case UserData::LoungingAround:
260 break;
261 }
262 }
263
264
265 static PreparedStatement * psl;
266 static PreparedStatement * psa;
267
268
269 /*! Starts refreshing this object from the database, and remembers to
270 call \a user when the refresh is complete.
271 */
272
refresh(EventHandler * user)273 void User::refresh( EventHandler * user )
274 {
275 if ( d->q )
276 return;
277 d->user = user;
278 if ( !psl ) {
279 psl = new PreparedStatement(
280 "select u.id, u.login, u.secret, u.ldapdn, "
281 "a.name, a.localpart::text, a.domain::text, u.quota, "
282 "al.mailbox as inbox, n.name as parentspace "
283 "from users u "
284 "join namespaces n on (u.parentspace=n.id) "
285 "left join aliases al on (u.alias=al.id) "
286 "left join addresses a on (al.address=a.id) "
287 "where lower(u.login)=lower($1)"
288 );
289
290 psa = new PreparedStatement(
291 "select u.id, u.login, u.secret, u.ldapdn, "
292 "a.name, a.localpart::text, a.domain::text, u.quota, "
293 "al.mailbox as inbox, n.name as parentspace "
294 "from users u "
295 "join namespaces n on (u.parentspace=n.id) "
296 "join aliases al on (u.alias=al.id) "
297 "join addresses a on (al.address=a.id) "
298 "where a.localpart=$1 and a.domain=$2"
299 );
300 }
301 if ( !d->login.isEmpty() ) {
302 d->q = new Query( *psl, this );
303 d->q->bind( 1, d->login );
304 }
305 else if ( d->address ) {
306 d->q = new Query( *psa, this );
307 d->q->bind( 1, d->address->localpart() );
308 d->q->bind( 2, d->address->domain() );
309 }
310 if ( d->q ) {
311 d->q->execute();
312 d->mode = UserData::Refreshing;
313 }
314 else {
315 d->state = Nonexistent;
316 }
317 }
318
319
320 /*! Parses the query results for refresh(). */
321
refreshHelper()322 void User::refreshHelper()
323 {
324 if ( !d->q || !d->q->done() )
325 return;
326
327 d->state = Nonexistent;
328 Row * r = d->q->nextRow();
329 if ( r ) {
330 d->id = r->getInt( "id" );
331 d->login = r->getUString( "login" );
332 if ( r->isNull( "secret" ) )
333 d->secret.truncate();
334 else
335 d->secret = r->getUString( "secret" );
336 d->inboxId = r->getInt( "inbox" );
337 if ( r->isNull( "ldapdn" ) )
338 d->ldapdn.truncate();
339 else
340 d->ldapdn = r->getUString( "ldapdn" );
341 UString tmp = r->getUString( "parentspace" );
342 tmp.append( '/' );
343 tmp.append( d->login );
344 d->home = Mailbox::obtain( tmp, true );
345 d->home->setOwner( d->id );
346 if ( r->isNull( "localpart" ) ) {
347 d->address = new Address();
348 }
349 else {
350 UString n = r->getUString( "name" );
351 UString l = r->getUString( "localpart" );
352 UString h = r->getUString( "domain" );
353 d->address = new Address( n, l, h );
354 }
355 d->quota = r->getBigint( "quota" );
356 d->state = Refreshed;
357 d->q = 0;
358 }
359 if ( d->user )
360 d->user->execute();
361 d->user = 0;
362 }
363
364
365 /*! This function is used to create a user on behalf of \a owner.
366
367 It returns a pointer to a Query that can be used to track the
368 progress of the operation. If (and only if) this Query hasn't
369 already failed upon return from this function, the caller must
370 call execute() to initiate the operation.
371
372 The query may fail immediately if the user is not valid(), or if it
373 already exists().
374
375 This function (indeed, this whole class) is overdue for change.
376 */
377
create(EventHandler * owner)378 Query * User::create( EventHandler * owner )
379 {
380 Query *q = new Query( owner );
381
382 if ( !valid() ) {
383 q->setError( "Invalid user data." );
384 }
385 else if ( exists() ) {
386 q->setError( "User exists already." );
387 }
388 else {
389 d->q = 0;
390 d->t = new Transaction( this );
391 d->mode = UserData::Creating;
392 d->state = Unverified;
393 d->user = owner;
394 d->result = q;
395 }
396
397 return q;
398 }
399
400
401 /*! This private function carries out create() work on behalf of
402 execute().
403 */
404
createHelper()405 void User::createHelper()
406 {
407 Address * a = address();
408
409 if ( !d->q ) {
410 if ( !a->id() ) {
411 AddressCreator * ac = new AddressCreator( a, d->t );
412 ac->execute();
413 }
414
415 d->q = new Query( "select name from namespaces where id="
416 "(select max(id) from namespaces)", this );
417 d->t->enqueue( d->q );
418 d->t->execute();
419 }
420
421 if ( d->q->done() && a->id() && !d->inbox ) {
422 Row *r = d->q->nextRow();
423 if ( !r ) {
424 d->t->commit();
425 return;
426 }
427
428 UString m = r->getUString( "name" );
429 m.append( '/' );
430 m.append( d->login );
431 m.append( "/INBOX" );
432 d->inbox = Mailbox::obtain( m, true );
433 d->inbox->create( d->t, 0 );
434
435 Query * q1
436 = new Query( "insert into aliases (address, mailbox) values "
437 "($1, (select id from mailboxes where name=$2))", 0 );
438 q1->bind( 1, a->id() );
439 q1->bind( 2, m );
440 d->t->enqueue( q1 );
441
442 Query * q2
443 = new Query( "insert into users "
444 "(alias,parentspace,login,secret,quota) values "
445 "((select id from aliases where address=$1), "
446 "(select max(id) from namespaces), $2, $3, "
447 "coalesce((select quota from users group by quota"
448 " order by count(*) desc limit 1), 2147483647))", 0 );
449 q2->bind( 1, a->id() );
450 q2->bind( 2, d->login );
451 q2->bind( 3, d->secret );
452 d->t->enqueue( q2 );
453
454 Query *q3 =
455 new Query( "update mailboxes set "
456 "owner=(select id from users where login=$1) "
457 "where name=$2 or name like $2||'/%'", 0 );
458 q3->bind( 1, d->login );
459 q3->bind( 2, m );
460 d->t->enqueue( q3 );
461
462 d->t->commit();
463 }
464
465 if ( !d->t->done() )
466 return;
467
468 if ( d->t->failed() ) {
469 d->state = Nonexistent;
470 d->result->setError( d->t->error() );
471 }
472 else {
473 d->state = Refreshed;
474 d->result->setState( Query::Completed );
475 }
476
477 d->result->notify();
478 }
479
480
481 /*! Enqueues a query to remove this user in the Transaction \a t, and
482 returns the Query. Does not commit the Transaction.
483 */
484
remove(Transaction * t)485 Query * User::remove( Transaction * t )
486 {
487 Query * q = new Query( "delete from users where login=$1", 0 );
488 q->bind( 1, d->login );
489 t->enqueue( q );
490 return q;
491 }
492
493
494 /*! This function changes a user's password on behalf of \a owner.
495
496 It returns a pointer to a Query that can be used to track the
497 progress of the operation. If (and only if) this Query hasn't
498 already failed upon return from this function, the caller must
499 call execute() to initiate the operation.
500 */
501
changeSecret(EventHandler * owner)502 Query * User::changeSecret( EventHandler * owner )
503 {
504 Query *q = new Query( owner );
505
506 d->q = 0;
507 d->mode = UserData::ChangingSecret;
508 d->user = owner;
509 d->result = q;
510
511 return q;
512 }
513
514
515 /*! Finish the work of changeSecret(). */
516
csHelper()517 void User::csHelper()
518 {
519 if ( !d->q ) {
520 d->q =
521 new Query( "update users set secret=$1 where login=$2",
522 this );
523 d->q->bind( 1, d->secret );
524 d->q->bind( 2, d->login );
525 d->q->execute();
526 }
527
528 if ( !d->q->done() )
529 return;
530
531 if ( d->q->failed() )
532 d->result->setError( d->q->error() );
533 else
534 d->result->setState( Query::Completed );
535
536 d->result->notify();
537 }
538
539
540 /*! Returns true if this user is valid, that is, if it has the
541 information that must be present in order to write it to the
542 database and do not have defaults.
543
544 Sets error() if applicable.
545 */
546
valid()547 bool User::valid()
548 {
549 if ( d->login.isEmpty() ) {
550 d->error = "Login name must be supplied";
551 return false;
552 }
553
554 return true;
555 }
556
557
558 /*! Returns a textual description of the last error seen, or a null
559 string if everything is in order. The string is set by valid() and
560 perhaps other functions.
561 */
562
error() const563 EString User::error() const
564 {
565 return d->error;
566 }
567
568
569 /*! Returns the user's database quota, as recorderd by users.quota. */
570
quota() const571 int64 User::quota() const
572 {
573 return d->quota;
574 }
575