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