2003-03-04 Havoc Pennington <hp@pobox.com>
[platform/upstream/dbus.git] / dbus / dbus-auth.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* dbus-auth.c Authentication
3  *
4  * Copyright (C) 2002, 2003 Red Hat Inc.
5  *
6  * Licensed under the Academic Free License version 1.2
7  * 
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  * 
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23 #include "dbus-auth.h"
24 #include "dbus-string.h"
25 #include "dbus-list.h"
26 #include "dbus-internals.h"
27 #include "dbus-keyring.h"
28 #include "dbus-sha.h"
29
30 /* See doc/dbus-sasl-profile.txt */
31
32 /**
33  * @defgroup DBusAuth Authentication
34  * @ingroup  DBusInternals
35  * @brief DBusAuth object
36  *
37  * DBusAuth manages the authentication negotiation when a connection
38  * is first established, and also manage any encryption used over a
39  * connection.
40  *
41  * The file doc/dbus-sasl-profile.txt documents the network protocol
42  * used for authentication.
43  *
44  * @todo some SASL profiles require sending the empty string as a
45  * challenge/response, but we don't currently allow that in our
46  * protocol.
47  *
48  * @todo DBusAuth really needs to be rewritten as an explicit state
49  * machine. Right now it's too hard to prove to yourself by inspection
50  * that it works.
51  *
52  * @todo right now sometimes both ends will block waiting for input
53  * from the other end, e.g. if there's an error during
54  * DBUS_COOKIE_SHA1.
55  *
56  * @todo the cookie keyring needs to be cached globally not just
57  * per-auth (which raises threadsafety issues too)
58  * 
59  * @todo grep FIXME in dbus-auth.c
60  */
61
62 /**
63  * @defgroup DBusAuthInternals Authentication implementation details
64  * @ingroup  DBusInternals
65  * @brief DBusAuth implementation details
66  *
67  * Private details of authentication code.
68  *
69  * @{
70  */
71
72 /**
73  * Processes a command. Returns whether we had enough memory to
74  * complete the operation.
75  */
76 typedef dbus_bool_t (* DBusProcessAuthCommandFunction) (DBusAuth         *auth,
77                                                         const DBusString *command,
78                                                         const DBusString *args);
79
80 typedef struct
81 {
82   const char *command;
83   DBusProcessAuthCommandFunction func;
84 } DBusAuthCommandHandler;
85
86 /**
87  * This function appends an initial client response to the given string
88  */
89 typedef dbus_bool_t (* DBusInitialResponseFunction)  (DBusAuth         *auth,
90                                                       DBusString       *response);
91
92 /**
93  * This function processes a block of data received from the peer.
94  * i.e. handles a DATA command.
95  */
96 typedef dbus_bool_t (* DBusAuthDataFunction)     (DBusAuth         *auth,
97                                                   const DBusString *data);
98
99 /**
100  * This function encodes a block of data from the peer.
101  */
102 typedef dbus_bool_t (* DBusAuthEncodeFunction)   (DBusAuth         *auth,
103                                                   const DBusString *data,
104                                                   DBusString       *encoded);
105
106 /**
107  * This function decodes a block of data from the peer.
108  */
109 typedef dbus_bool_t (* DBusAuthDecodeFunction)   (DBusAuth         *auth,
110                                                   const DBusString *data,
111                                                   DBusString       *decoded);
112
113 /**
114  * This function is called when the mechanism is abandoned.
115  */
116 typedef void        (* DBusAuthShutdownFunction) (DBusAuth       *auth);
117
118 typedef struct
119 {
120   const char *mechanism;
121   DBusAuthDataFunction server_data_func;
122   DBusAuthEncodeFunction server_encode_func;
123   DBusAuthDecodeFunction server_decode_func;
124   DBusAuthShutdownFunction server_shutdown_func;
125   DBusInitialResponseFunction client_initial_response_func;
126   DBusAuthDataFunction client_data_func;
127   DBusAuthEncodeFunction client_encode_func;
128   DBusAuthDecodeFunction client_decode_func;
129   DBusAuthShutdownFunction client_shutdown_func;
130 } DBusAuthMechanismHandler;
131
132 /**
133  * Internal members of DBusAuth.
134  */
135 struct DBusAuth
136 {
137   int refcount;           /**< reference count */
138
139   DBusString incoming;    /**< Incoming data buffer */
140   DBusString outgoing;    /**< Outgoing data buffer */
141   
142   const DBusAuthCommandHandler *handlers; /**< Handlers for commands */
143
144   const DBusAuthMechanismHandler *mech;   /**< Current auth mechanism */
145
146   DBusString identity;                   /**< Current identity we're authorizing
147                                           *   as.
148                                           */
149   
150   DBusCredentials credentials;      /**< Credentials read from socket,
151                                      * fields may be -1
152                                      */
153
154   DBusCredentials authorized_identity; /**< Credentials that are authorized */
155
156   DBusCredentials desired_identity;    /**< Identity client has requested */
157   
158   DBusString context;               /**< Cookie scope */
159   DBusKeyring *keyring;             /**< Keyring for cookie mechanism. */
160   int cookie_id;                    /**< ID of cookie to use */
161   DBusString challenge;             /**< Challenge sent to client */
162   
163   unsigned int needed_memory : 1;   /**< We needed memory to continue since last
164                                      * successful getting something done
165                                      */
166   unsigned int need_disconnect : 1; /**< We've given up, time to disconnect */
167   unsigned int authenticated : 1;   /**< We are authenticated */
168   unsigned int authenticated_pending_output : 1; /**< Authenticated once we clear outgoing buffer */
169   unsigned int authenticated_pending_begin : 1;  /**< Authenticated once we get BEGIN */
170   unsigned int already_got_mechanisms : 1;       /**< Client already got mech list */
171   unsigned int already_asked_for_initial_response : 1; /**< Already sent a blank challenge to get an initial response */
172 };
173
174 typedef struct
175 {
176   DBusAuth base;
177
178   DBusList *mechs_to_try; /**< Mechanisms we got from the server that we're going to try using */
179   
180 } DBusAuthClient;
181
182 typedef struct
183 {
184   DBusAuth base;
185
186   int failures;     /**< Number of times client has been rejected */
187   int max_failures; /**< Number of times we reject before disconnect */
188   
189 } DBusAuthServer;
190
191 static dbus_bool_t process_auth         (DBusAuth         *auth,
192                                          const DBusString *command,
193                                          const DBusString *args);
194 static dbus_bool_t process_cancel       (DBusAuth         *auth,
195                                          const DBusString *command,
196                                          const DBusString *args);
197 static dbus_bool_t process_begin        (DBusAuth         *auth,
198                                          const DBusString *command,
199                                          const DBusString *args);
200 static dbus_bool_t process_data_server  (DBusAuth         *auth,
201                                          const DBusString *command,
202                                          const DBusString *args);
203 static dbus_bool_t process_error_server (DBusAuth         *auth,
204                                          const DBusString *command,
205                                          const DBusString *args);
206 static dbus_bool_t process_rejected     (DBusAuth         *auth,
207                                          const DBusString *command,
208                                          const DBusString *args);
209 static dbus_bool_t process_ok           (DBusAuth         *auth,
210                                          const DBusString *command,
211                                          const DBusString *args);
212 static dbus_bool_t process_data_client  (DBusAuth         *auth,
213                                          const DBusString *command,
214                                          const DBusString *args);
215 static dbus_bool_t process_error_client (DBusAuth         *auth,
216                                          const DBusString *command,
217                                          const DBusString *args);
218
219
220 static dbus_bool_t client_try_next_mechanism (DBusAuth *auth);
221 static dbus_bool_t send_rejected             (DBusAuth *auth);
222
223 static DBusAuthCommandHandler
224 server_handlers[] = {
225   { "AUTH", process_auth },
226   { "CANCEL", process_cancel },
227   { "BEGIN", process_begin },
228   { "DATA", process_data_server },
229   { "ERROR", process_error_server },
230   { NULL, NULL }
231 };
232
233 static DBusAuthCommandHandler
234 client_handlers[] = {
235   { "REJECTED", process_rejected },
236   { "OK", process_ok },
237   { "DATA", process_data_client },
238   { "ERROR", process_error_client },
239   { NULL, NULL }
240 };
241
242 /**
243  * @param auth the auth conversation
244  * @returns #TRUE if the conversation is the server side
245  */
246 #define DBUS_AUTH_IS_SERVER(auth) ((auth)->handlers == server_handlers)
247 /**
248  * @param auth the auth conversation
249  * @returns #TRUE if the conversation is the client side
250  */
251 #define DBUS_AUTH_IS_CLIENT(auth) ((auth)->handlers == client_handlers)
252 /**
253  * @param auth the auth conversation
254  * @returns auth cast to DBusAuthClient
255  */
256 #define DBUS_AUTH_CLIENT(auth)    ((DBusAuthClient*)(auth))
257 /**
258  * @param auth the auth conversation
259  * @returns auth cast to DBusAuthServer
260  */
261 #define DBUS_AUTH_SERVER(auth)    ((DBusAuthServer*)(auth))
262
263 static DBusAuth*
264 _dbus_auth_new (int size)
265 {
266   DBusAuth *auth;
267   
268   auth = dbus_malloc0 (size);
269   if (auth == NULL)
270     return NULL;
271   
272   auth->refcount = 1;
273
274   auth->credentials.pid = -1;
275   auth->credentials.uid = -1;
276   auth->credentials.gid = -1;
277
278   auth->authorized_identity.pid = -1;
279   auth->authorized_identity.uid = -1;
280   auth->authorized_identity.gid = -1;
281
282   auth->desired_identity.pid = -1;
283   auth->desired_identity.uid = -1;
284   auth->desired_identity.gid = -1;
285   
286   auth->keyring = NULL;
287   auth->cookie_id = -1;
288   
289   /* note that we don't use the max string length feature,
290    * because you can't use that feature if you're going to
291    * try to recover from out-of-memory (it creates
292    * what looks like unrecoverable inability to alloc
293    * more space in the string). But we do handle
294    * overlong buffers in _dbus_auth_do_work().
295    */
296   
297   if (!_dbus_string_init (&auth->incoming, _DBUS_INT_MAX))
298     goto enomem_0;
299
300   if (!_dbus_string_init (&auth->outgoing, _DBUS_INT_MAX))
301     goto enomem_1;
302     
303   if (!_dbus_string_init (&auth->identity, _DBUS_INT_MAX))
304     goto enomem_2;
305
306   if (!_dbus_string_init (&auth->context, _DBUS_INT_MAX))
307     goto enomem_3;
308
309   if (!_dbus_string_init (&auth->challenge, _DBUS_INT_MAX))
310     goto enomem_4;
311
312   /* default context if none is specified */
313   if (!_dbus_string_append (&auth->context, "org_freedesktop_general"))
314     goto enomem_5;
315   
316   return auth;
317
318  enomem_5:
319   _dbus_string_free (&auth->challenge);
320  enomem_4:
321   _dbus_string_free (&auth->context);
322  enomem_3:
323   _dbus_string_free (&auth->identity);
324  enomem_2:
325   _dbus_string_free (&auth->incoming);
326  enomem_1:
327   _dbus_string_free (&auth->outgoing);
328  enomem_0:
329   dbus_free (auth);
330   return NULL;
331 }
332
333 static void
334 shutdown_mech (DBusAuth *auth)
335 {
336   /* Cancel any auth */
337   auth->authenticated_pending_begin = FALSE;
338   auth->authenticated = FALSE;
339   auth->already_asked_for_initial_response = FALSE;
340   _dbus_string_set_length (&auth->identity, 0);
341   
342   auth->authorized_identity.pid = -1;
343   auth->authorized_identity.uid = -1;
344   auth->authorized_identity.gid = -1;
345
346   auth->desired_identity.pid = -1;
347   auth->desired_identity.uid = -1;
348   auth->desired_identity.gid = -1;
349   
350   if (auth->mech != NULL)
351     {
352       _dbus_verbose ("Shutting down mechanism %s\n",
353                      auth->mech->mechanism);
354       
355       if (DBUS_AUTH_IS_CLIENT (auth))
356         (* auth->mech->client_shutdown_func) (auth);
357       else
358         (* auth->mech->server_shutdown_func) (auth);
359       
360       auth->mech = NULL;
361     }
362 }
363
364 /* Returns TRUE but with an empty string hash if the
365  * cookie_id isn't known. As with all this code
366  * TRUE just means we had enough memory.
367  */
368 static dbus_bool_t
369 sha1_compute_hash (DBusAuth         *auth,
370                    int               cookie_id,
371                    const DBusString *server_challenge,
372                    const DBusString *client_challenge,
373                    DBusString       *hash)
374 {
375   DBusString cookie;
376   DBusString to_hash;
377   dbus_bool_t retval;
378   
379   _dbus_assert (auth->keyring != NULL);
380
381   retval = FALSE;
382   
383   if (!_dbus_string_init (&cookie, _DBUS_INT_MAX))
384     return FALSE;
385
386   if (!_dbus_keyring_get_hex_key (auth->keyring, cookie_id,
387                                   &cookie))
388     goto out_0;
389
390   if (_dbus_string_get_length (&cookie) == 0)
391     {
392       retval = TRUE;
393       goto out_0;
394     }
395
396   if (!_dbus_string_init (&to_hash, _DBUS_INT_MAX))
397     goto out_0;
398   
399   if (!_dbus_string_copy (server_challenge, 0,
400                           &to_hash, _dbus_string_get_length (&to_hash)))
401     goto out_1;
402
403   if (!_dbus_string_append (&to_hash, ":"))
404     goto out_1;
405   
406   if (!_dbus_string_copy (client_challenge, 0,
407                           &to_hash, _dbus_string_get_length (&to_hash)))
408     goto out_1;
409
410   if (!_dbus_string_append (&to_hash, ":"))
411     goto out_1;
412
413   if (!_dbus_string_copy (&cookie, 0,
414                           &to_hash, _dbus_string_get_length (&to_hash)))
415     goto out_1;
416
417   if (!_dbus_sha_compute (&to_hash, hash))
418     goto out_1;
419   
420   retval = TRUE;
421
422  out_1:
423   _dbus_string_zero (&to_hash);
424   _dbus_string_free (&to_hash);
425  out_0:
426   _dbus_string_zero (&cookie);
427   _dbus_string_free (&cookie);
428   return retval;
429 }
430
431 /* http://www.ietf.org/rfc/rfc2831.txt suggests at least 64 bits of
432  * entropy, we use 128
433  */
434 #define N_CHALLENGE_BYTES (128/8)
435
436 static dbus_bool_t
437 sha1_handle_first_client_response (DBusAuth         *auth,
438                                    const DBusString *data)
439 {
440   /* We haven't sent a challenge yet, we're expecting a desired
441    * username from the client.
442    */
443   DBusString tmp;
444   DBusString tmp2;
445   dbus_bool_t retval;
446   int old_len;
447   DBusError error;
448   
449   retval = FALSE;
450
451   _dbus_string_set_length (&auth->challenge, 0);
452   
453   if (_dbus_string_get_length (data) > 0)
454     {
455       if (_dbus_string_get_length (&auth->identity) > 0)
456         {
457           /* Tried to send two auth identities, wtf */
458           _dbus_verbose ("client tried to send auth identity, but we already have one\n");
459           return send_rejected (auth);
460         }
461       else
462         {
463           /* this is our auth identity */
464           if (!_dbus_string_copy (data, 0, &auth->identity, 0))
465             return FALSE;
466         }
467     }
468       
469   if (!_dbus_credentials_from_username (data, &auth->desired_identity))
470     {
471       _dbus_verbose ("Did not get a valid username from client\n");
472       return send_rejected (auth);
473     }
474       
475   if (!_dbus_string_init (&tmp, _DBUS_INT_MAX))
476     return FALSE;
477
478   if (!_dbus_string_init (&tmp2, _DBUS_INT_MAX))
479     {
480       _dbus_string_free (&tmp);
481       return FALSE;
482     }
483
484   old_len = _dbus_string_get_length (&auth->outgoing);
485   
486   /* we cache the keyring for speed, so here we drop it if it's the
487    * wrong one. FIXME caching the keyring here is useless since we use
488    * a different DBusAuth for every connection.
489    */
490   if (auth->keyring &&
491       !_dbus_keyring_is_for_user (auth->keyring,
492                                   data))
493     {
494       _dbus_keyring_unref (auth->keyring);
495       auth->keyring = NULL;
496     }
497   
498   if (auth->keyring == NULL)
499     {
500       DBusError error;
501
502       dbus_error_init (&error);
503       auth->keyring = _dbus_keyring_new_homedir (data,
504                                                  &auth->context,
505                                                  &error);
506
507       if (auth->keyring == NULL)
508         {
509           if (dbus_error_has_name (&error,
510                                    DBUS_ERROR_NO_MEMORY))
511             {
512               dbus_error_free (&error);
513               goto out;
514             }
515           else
516             {
517               _dbus_assert (dbus_error_is_set (&error));
518               _dbus_verbose ("Error loading keyring: %s\n",
519                              error.message);
520               if (send_rejected (auth))
521                 retval = TRUE; /* retval is only about mem */
522               dbus_error_free (&error);
523               goto out;
524             }
525         }
526       else
527         {
528           _dbus_assert (!dbus_error_is_set (&error));
529         }
530     }
531
532   _dbus_assert (auth->keyring != NULL);
533
534   dbus_error_init (&error);
535   auth->cookie_id = _dbus_keyring_get_best_key (auth->keyring, &error);
536   if (auth->cookie_id < 0)
537     {
538       _dbus_assert (dbus_error_is_set (&error));
539       _dbus_verbose ("Could not get a cookie ID to send to client: %s\n",
540                      error.message);
541       if (send_rejected (auth))
542         retval = TRUE;
543       dbus_error_free (&error);
544       goto out;
545     }
546   else
547     {
548       _dbus_assert (!dbus_error_is_set (&error));
549     }
550
551   if (!_dbus_string_copy (&auth->context, 0,
552                           &tmp2, _dbus_string_get_length (&tmp2)))
553     goto out;
554
555   if (!_dbus_string_append (&tmp2, " "))
556     goto out;
557
558   if (!_dbus_string_append_int (&tmp2, auth->cookie_id))
559     goto out;
560
561   if (!_dbus_string_append (&tmp2, " "))
562     goto out;  
563   
564   if (!_dbus_generate_random_bytes (&tmp, N_CHALLENGE_BYTES))
565     goto out;
566
567   _dbus_string_set_length (&auth->challenge, 0);
568   if (!_dbus_string_hex_encode (&tmp, 0, &auth->challenge, 0))
569     goto out;
570   
571   if (!_dbus_string_hex_encode (&tmp, 0, &tmp2,
572                                 _dbus_string_get_length (&tmp2)))
573     goto out;
574
575   if (!_dbus_string_append (&auth->outgoing,
576                             "DATA "))
577     goto out;
578   
579   if (!_dbus_string_base64_encode (&tmp2, 0, &auth->outgoing,
580                                    _dbus_string_get_length (&auth->outgoing)))
581     goto out;
582
583   if (!_dbus_string_append (&auth->outgoing,
584                             "\r\n"))
585     goto out;
586       
587   retval = TRUE;
588   
589  out:
590   _dbus_string_zero (&tmp);
591   _dbus_string_free (&tmp);
592   _dbus_string_zero (&tmp2);
593   _dbus_string_free (&tmp2);
594   if (!retval)
595     _dbus_string_set_length (&auth->outgoing, old_len);
596   return retval;
597 }
598
599 static dbus_bool_t
600 sha1_handle_second_client_response (DBusAuth         *auth,
601                                     const DBusString *data)
602 {
603   /* We are expecting a response which is the hex-encoded client
604    * challenge, space, then SHA-1 hash of the concatenation of our
605    * challenge, ":", client challenge, ":", secret key, all
606    * hex-encoded.
607    */
608   int i;
609   DBusString client_challenge;
610   DBusString client_hash;
611   dbus_bool_t retval;
612   DBusString correct_hash;
613   
614   retval = FALSE;
615   
616   if (!_dbus_string_find_blank (data, 0, &i))
617     {
618       _dbus_verbose ("no space separator in client response\n");
619       return send_rejected (auth);
620     }
621   
622   if (!_dbus_string_init (&client_challenge, _DBUS_INT_MAX))
623     goto out_0;
624
625   if (!_dbus_string_init (&client_hash, _DBUS_INT_MAX))
626     goto out_1;  
627
628   if (!_dbus_string_copy_len (data, 0, i, &client_challenge,
629                               0))
630     goto out_2;
631
632   _dbus_string_skip_blank (data, i, &i);
633   
634   if (!_dbus_string_copy_len (data, i,
635                               _dbus_string_get_length (data) - i,
636                               &client_hash,
637                               0))
638     goto out_2;
639
640   if (_dbus_string_get_length (&client_challenge) == 0 ||
641       _dbus_string_get_length (&client_hash) == 0)
642     {
643       _dbus_verbose ("zero-length client challenge or hash\n");
644       if (send_rejected (auth))
645         retval = TRUE;
646       goto out_2;
647     }
648
649   if (!_dbus_string_init (&correct_hash, _DBUS_INT_MAX))
650     goto out_2;
651
652   if (!sha1_compute_hash (auth, auth->cookie_id,
653                           &auth->challenge, 
654                           &client_challenge,
655                           &correct_hash))
656     goto out_3;
657
658   /* if cookie_id was invalid, then we get an empty hash */
659   if (_dbus_string_get_length (&correct_hash) == 0)
660     {
661       if (send_rejected (auth))
662         retval = TRUE;
663       goto out_3;
664     }
665   
666   if (!_dbus_string_equal (&client_hash, &correct_hash))
667     {
668       if (send_rejected (auth))
669         retval = TRUE;
670       goto out_3;
671     }
672       
673   if (!_dbus_string_append (&auth->outgoing,
674                             "OK\r\n"))
675     goto out_3;
676
677   _dbus_verbose ("authenticated client with UID %d using DBUS_COOKIE_SHA1\n",
678                  auth->desired_identity.uid);
679   
680   auth->authorized_identity = auth->desired_identity;
681   auth->authenticated_pending_begin = TRUE;
682   retval = TRUE;
683   
684  out_3:
685   _dbus_string_zero (&correct_hash);
686   _dbus_string_free (&correct_hash);
687  out_2:
688   _dbus_string_zero (&client_hash);
689   _dbus_string_free (&client_hash);
690  out_1:
691   _dbus_string_free (&client_challenge);
692  out_0:
693   return retval;
694 }
695
696 static dbus_bool_t
697 handle_server_data_cookie_sha1_mech (DBusAuth         *auth,
698                                      const DBusString *data)
699 {
700   if (auth->cookie_id < 0)
701     return sha1_handle_first_client_response (auth, data);
702   else
703     return sha1_handle_second_client_response (auth, data);
704 }
705
706 static void
707 handle_server_shutdown_cookie_sha1_mech (DBusAuth *auth)
708 {
709   auth->cookie_id = -1;  
710   _dbus_string_set_length (&auth->challenge, 0);
711 }
712
713 static dbus_bool_t
714 handle_client_initial_response_cookie_sha1_mech (DBusAuth   *auth,
715                                                  DBusString *response)
716 {
717   const DBusString *username;
718   dbus_bool_t retval;
719
720   retval = FALSE;
721
722   if (!_dbus_user_info_from_current_process (&username,
723                                              NULL, NULL))
724     goto out_0;
725
726   if (!_dbus_string_base64_encode (username, 0,
727                                    response,
728                                    _dbus_string_get_length (response)))
729     goto out_0;
730
731   retval = TRUE;
732   
733  out_0:
734   return retval;
735 }
736
737 /* FIXME if we send the server an error, right now both sides
738  * just hang. Server has to reject on getting an error, or
739  * client has to cancel. Should be in the spec.
740  */
741 static dbus_bool_t
742 handle_client_data_cookie_sha1_mech (DBusAuth         *auth,
743                                      const DBusString *data)
744 {
745   /* The data we get from the server should be the cookie context
746    * name, the cookie ID, and the server challenge, separated by
747    * spaces. We send back our challenge string and the correct hash.
748    */
749   dbus_bool_t retval;
750   DBusString context;
751   DBusString cookie_id_str;
752   DBusString server_challenge;
753   DBusString client_challenge;
754   DBusString correct_hash;
755   DBusString tmp;
756   int i, j;
757   long val;
758   int old_len;
759   
760   retval = FALSE;                 
761   
762   if (!_dbus_string_find_blank (data, 0, &i))
763     {
764       if (_dbus_string_append (&auth->outgoing,
765                                "ERROR \"Server did not send context/ID/challenge properly\"\r\n"))
766         retval = TRUE;
767       goto out_0;
768     }
769
770   if (!_dbus_string_init (&context, _DBUS_INT_MAX))
771     goto out_0;
772
773   if (!_dbus_string_copy_len (data, 0, i,
774                               &context, 0))
775     goto out_1;
776   
777   _dbus_string_skip_blank (data, i, &i);
778   if (!_dbus_string_find_blank (data, i, &j))
779     {
780       if (_dbus_string_append (&auth->outgoing,
781                                "ERROR \"Server did not send context/ID/challenge properly\"\r\n"))
782         retval = TRUE;
783       goto out_1;
784     }
785
786   if (!_dbus_string_init (&cookie_id_str, _DBUS_INT_MAX))
787     goto out_1;
788   
789   if (!_dbus_string_copy_len (data, i, j - i,
790                               &cookie_id_str, 0))
791     goto out_2;  
792
793   if (!_dbus_string_init (&server_challenge, _DBUS_INT_MAX))
794     goto out_2;
795
796   i = j;
797   _dbus_string_skip_blank (data, i, &i);
798   j = _dbus_string_get_length (data);
799
800   if (!_dbus_string_copy_len (data, i, j - i,
801                               &server_challenge, 0))
802     goto out_3;
803
804   if (!_dbus_keyring_validate_context (&context))
805     {
806       if (_dbus_string_append (&auth->outgoing,
807                                "ERROR \"Server sent invalid cookie context\"\r\n"))
808         retval = TRUE;
809       goto out_3;
810     }
811
812   if (!_dbus_string_parse_int (&cookie_id_str, 0, &val, NULL))
813     {
814       if (_dbus_string_append (&auth->outgoing,
815                                "ERROR \"Could not parse cookie ID as an integer\"\r\n"))
816         retval = TRUE;
817       goto out_3;
818     }
819
820   if (_dbus_string_get_length (&server_challenge) == 0)
821     {
822       if (_dbus_string_append (&auth->outgoing,
823                                "ERROR \"Empty server challenge string\"\r\n"))
824         retval = TRUE;
825       goto out_3;
826     }
827
828   if (auth->keyring == NULL)
829     {
830       DBusError error;
831
832       dbus_error_init (&error);
833       auth->keyring = _dbus_keyring_new_homedir (NULL,
834                                                  &context,
835                                                  &error);
836
837       if (auth->keyring == NULL)
838         {
839           if (dbus_error_has_name (&error,
840                                    DBUS_ERROR_NO_MEMORY))
841             {
842               dbus_error_free (&error);
843               goto out_3;
844             }
845           else
846             {
847               _dbus_assert (dbus_error_is_set (&error));
848               _dbus_verbose ("Error loading keyring: %s\n",
849                              error.message);
850               
851               if (_dbus_string_append (&auth->outgoing,
852                                        "ERROR \"Could not load cookie file\"\r\n"))
853                 retval = TRUE; /* retval is only about mem */
854               
855               dbus_error_free (&error);
856               goto out_3;
857             }
858         }
859       else
860         {
861           _dbus_assert (!dbus_error_is_set (&error));
862         }
863     }
864   
865   _dbus_assert (auth->keyring != NULL);
866   
867   if (!_dbus_string_init (&tmp, _DBUS_INT_MAX))
868     goto out_3;
869   
870   if (!_dbus_generate_random_bytes (&tmp, N_CHALLENGE_BYTES))
871     goto out_4;
872
873   if (!_dbus_string_init (&client_challenge, _DBUS_INT_MAX))
874     goto out_4;
875
876   if (!_dbus_string_hex_encode (&tmp, 0, &client_challenge, 0))
877     goto out_5;
878
879   if (!_dbus_string_init (&correct_hash, _DBUS_INT_MAX))
880     goto out_6;
881   
882   if (!sha1_compute_hash (auth, val,
883                           &server_challenge,
884                           &client_challenge,
885                           &correct_hash))
886     goto out_6;
887
888   if (_dbus_string_get_length (&correct_hash) == 0)
889     {
890       /* couldn't find the cookie ID or something */
891       if (_dbus_string_append (&auth->outgoing,
892                                "ERROR \"Don't have the requested cookie ID\"\r\n"))
893         retval = TRUE;
894       goto out_6;
895     }
896   
897   _dbus_string_set_length (&tmp, 0);
898   
899   if (!_dbus_string_copy (&client_challenge, 0, &tmp,
900                           _dbus_string_get_length (&tmp)))
901     goto out_6;
902
903   if (!_dbus_string_append (&tmp, " "))
904     goto out_6;
905
906   if (!_dbus_string_copy (&correct_hash, 0, &tmp,
907                           _dbus_string_get_length (&tmp)))
908     goto out_6;
909
910   old_len = _dbus_string_get_length (&auth->outgoing);
911   if (!_dbus_string_append (&auth->outgoing, "DATA "))
912     goto out_6;
913
914   if (!_dbus_string_base64_encode (&tmp, 0,
915                                    &auth->outgoing,
916                                    _dbus_string_get_length (&auth->outgoing)))
917     {
918       _dbus_string_set_length (&auth->outgoing, old_len);
919       goto out_6;
920     }
921
922   if (!_dbus_string_append (&auth->outgoing, "\r\n"))
923     {
924       _dbus_string_set_length (&auth->outgoing, old_len);
925       goto out_6;
926     }
927   
928   retval = TRUE;
929
930  out_6:
931   _dbus_string_zero (&correct_hash);
932   _dbus_string_free (&correct_hash);
933  out_5:
934   _dbus_string_free (&client_challenge);
935  out_4:
936   _dbus_string_zero (&tmp);
937   _dbus_string_free (&tmp);
938  out_3:
939   _dbus_string_free (&server_challenge);
940  out_2:
941   _dbus_string_free (&cookie_id_str);
942  out_1:
943   _dbus_string_free (&context);
944  out_0:
945   return retval;
946 }
947
948 static void
949 handle_client_shutdown_cookie_sha1_mech (DBusAuth *auth)
950 {
951   auth->cookie_id = -1;  
952   _dbus_string_set_length (&auth->challenge, 0);
953 }
954
955 static dbus_bool_t
956 handle_server_data_external_mech (DBusAuth         *auth,
957                                   const DBusString *data)
958 {
959   if (auth->credentials.uid < 0)
960     {
961       _dbus_verbose ("no credentials, mechanism EXTERNAL can't authenticate\n");
962       return send_rejected (auth);
963     }
964   
965   if (_dbus_string_get_length (data) > 0)
966     {
967       if (_dbus_string_get_length (&auth->identity) > 0)
968         {
969           /* Tried to send two auth identities, wtf */
970           _dbus_verbose ("client tried to send auth identity, but we already have one\n");
971           return send_rejected (auth);
972         }
973       else
974         {
975           /* this is our auth identity */
976           if (!_dbus_string_copy (data, 0, &auth->identity, 0))
977             return FALSE;
978         }
979     }
980
981   /* Poke client for an auth identity, if none given */
982   if (_dbus_string_get_length (&auth->identity) == 0 &&
983       !auth->already_asked_for_initial_response)
984     {
985       if (_dbus_string_append (&auth->outgoing,
986                                "DATA\r\n"))
987         {
988           _dbus_verbose ("sending empty challenge asking client for auth identity\n");
989           auth->already_asked_for_initial_response = TRUE;
990           return TRUE;
991         }
992       else
993         return FALSE;
994     }
995
996   auth->desired_identity.pid = -1;
997   auth->desired_identity.uid = -1;
998   auth->desired_identity.gid = -1;
999   
1000   /* If auth->identity is still empty here, then client
1001    * responded with an empty string after we poked it for
1002    * an initial response. This means to try to auth the
1003    * identity provided in the credentials.
1004    */
1005   if (_dbus_string_get_length (&auth->identity) == 0)
1006     {
1007       auth->desired_identity.uid = auth->credentials.uid;
1008     }
1009   else
1010     {
1011       if (!_dbus_credentials_from_uid_string (&auth->identity,
1012                                               &auth->desired_identity))
1013         {
1014           _dbus_verbose ("could not get credentials from uid string\n");
1015           return send_rejected (auth);
1016         }
1017     }
1018
1019   if (auth->desired_identity.uid < 0)
1020     {
1021       _dbus_verbose ("desired UID %d is no good\n", auth->desired_identity.uid);
1022       return send_rejected (auth);
1023     }
1024   
1025   if (_dbus_credentials_match (&auth->desired_identity,
1026                                &auth->credentials))
1027     {
1028       /* client has authenticated */      
1029       if (!_dbus_string_append (&auth->outgoing,
1030                                 "OK\r\n"))
1031         return FALSE;
1032
1033       _dbus_verbose ("authenticated client with UID %d matching socket credentials UID %d\n",
1034                      auth->desired_identity.uid,
1035                      auth->credentials.uid);
1036       
1037       auth->authorized_identity.uid = auth->desired_identity.uid;
1038       
1039       auth->authenticated_pending_begin = TRUE;
1040       
1041       return TRUE;
1042     }
1043   else
1044     {
1045       _dbus_verbose ("credentials uid=%d gid=%d do not allow uid=%d gid=%d\n",
1046                      auth->credentials.uid, auth->credentials.gid,
1047                      auth->desired_identity.uid, auth->desired_identity.gid);
1048       return send_rejected (auth);
1049     }
1050 }
1051
1052 static void
1053 handle_server_shutdown_external_mech (DBusAuth *auth)
1054 {
1055
1056 }
1057
1058 static dbus_bool_t
1059 handle_client_initial_response_external_mech (DBusAuth         *auth,
1060                                               DBusString       *response)
1061 {
1062   /* We always append our UID as an initial response, so the server
1063    * doesn't have to send back an empty challenge to check whether we
1064    * want to specify an identity. i.e. this avoids a round trip that
1065    * the spec for the EXTERNAL mechanism otherwise requires.
1066    */
1067   DBusString plaintext;
1068
1069   if (!_dbus_string_init (&plaintext, _DBUS_INT_MAX))
1070     return FALSE;
1071   
1072   if (!_dbus_string_append_our_uid (&plaintext))
1073     goto failed;
1074
1075   if (!_dbus_string_base64_encode (&plaintext, 0,
1076                                    response,
1077                                    _dbus_string_get_length (response)))
1078     goto failed;
1079
1080   _dbus_string_free (&plaintext);
1081   
1082   return TRUE;
1083
1084  failed:
1085   _dbus_string_free (&plaintext);
1086   return FALSE;  
1087 }
1088
1089 static dbus_bool_t
1090 handle_client_data_external_mech (DBusAuth         *auth,
1091                                   const DBusString *data)
1092 {
1093   
1094   return TRUE;
1095 }
1096
1097 static void
1098 handle_client_shutdown_external_mech (DBusAuth *auth)
1099 {
1100
1101 }
1102
1103 /* Put mechanisms here in order of preference.
1104  * What I eventually want to have is:
1105  *
1106  *  - a mechanism that checks UNIX domain socket credentials
1107  *  - a simple magic cookie mechanism like X11 or ICE
1108  *  - mechanisms that chain to Cyrus SASL, so we can use anything it
1109  *    offers such as Kerberos, X509, whatever.
1110  * 
1111  */
1112 static const DBusAuthMechanismHandler
1113 all_mechanisms[] = {
1114   { "EXTERNAL",
1115     handle_server_data_external_mech,
1116     NULL, NULL,
1117     handle_server_shutdown_external_mech,
1118     handle_client_initial_response_external_mech,
1119     handle_client_data_external_mech,
1120     NULL, NULL,
1121     handle_client_shutdown_external_mech },
1122   { "DBUS_COOKIE_SHA1",
1123     handle_server_data_cookie_sha1_mech,
1124     NULL, NULL,
1125     handle_server_shutdown_cookie_sha1_mech,
1126     handle_client_initial_response_cookie_sha1_mech,
1127     handle_client_data_cookie_sha1_mech,
1128     NULL, NULL,
1129     handle_client_shutdown_cookie_sha1_mech },
1130   { NULL, NULL }
1131 };
1132
1133 static const DBusAuthMechanismHandler*
1134 find_mech (const DBusString *name)
1135 {
1136   int i;
1137   
1138   i = 0;
1139   while (all_mechanisms[i].mechanism != NULL)
1140     {
1141       if (_dbus_string_equal_c_str (name,
1142                                     all_mechanisms[i].mechanism))
1143
1144         return &all_mechanisms[i];
1145       
1146       ++i;
1147     }
1148   
1149   return NULL;
1150 }
1151
1152 static dbus_bool_t
1153 send_rejected (DBusAuth *auth)
1154 {
1155   DBusString command;
1156   DBusAuthServer *server_auth;
1157   int i;
1158   
1159   if (!_dbus_string_init (&command, _DBUS_INT_MAX))
1160     return FALSE;
1161   
1162   if (!_dbus_string_append (&command,
1163                             "REJECTED"))
1164     goto nomem;
1165
1166   i = 0;
1167   while (all_mechanisms[i].mechanism != NULL)
1168     {
1169       if (!_dbus_string_append (&command,
1170                                 " "))
1171         goto nomem;
1172
1173       if (!_dbus_string_append (&command,
1174                                 all_mechanisms[i].mechanism))
1175         goto nomem;
1176       
1177       ++i;
1178     }
1179   
1180   if (!_dbus_string_append (&command, "\r\n"))
1181     goto nomem;
1182
1183   if (!_dbus_string_copy (&command, 0, &auth->outgoing,
1184                           _dbus_string_get_length (&auth->outgoing)))
1185     goto nomem;
1186
1187   shutdown_mech (auth);
1188   
1189   _dbus_assert (DBUS_AUTH_IS_SERVER (auth));
1190   server_auth = DBUS_AUTH_SERVER (auth);
1191   server_auth->failures += 1;
1192
1193   _dbus_string_free (&command);
1194   
1195   return TRUE;
1196
1197  nomem:
1198   _dbus_string_free (&command);
1199   return FALSE;
1200 }
1201
1202 static dbus_bool_t
1203 process_auth (DBusAuth         *auth,
1204               const DBusString *command,
1205               const DBusString *args)
1206 {
1207   if (auth->mech)
1208     {
1209       /* We are already using a mechanism, client is on crack */
1210       if (!_dbus_string_append (&auth->outgoing,
1211                                 "ERROR \"Sent AUTH while another AUTH in progress\"\r\n"))
1212         return FALSE;
1213
1214       return TRUE;
1215     }
1216   else if (_dbus_string_get_length (args) == 0)
1217     {
1218       /* No args to the auth, send mechanisms */
1219       if (!send_rejected (auth))
1220         return FALSE;
1221
1222       return TRUE;
1223     }
1224   else
1225     {
1226       int i;
1227       DBusString mech;
1228       DBusString base64_response;
1229       DBusString decoded_response;
1230       
1231       _dbus_string_find_blank (args, 0, &i);
1232
1233       if (!_dbus_string_init (&mech, _DBUS_INT_MAX))
1234         return FALSE;
1235
1236       if (!_dbus_string_init (&base64_response, _DBUS_INT_MAX))
1237         {
1238           _dbus_string_free (&mech);
1239           return FALSE;
1240         }
1241
1242       if (!_dbus_string_init (&decoded_response, _DBUS_INT_MAX))
1243         {
1244           _dbus_string_free (&mech);
1245           _dbus_string_free (&base64_response);
1246           return FALSE;
1247         }
1248       
1249       if (!_dbus_string_copy_len (args, 0, i, &mech, 0))
1250         goto failed;
1251
1252       if (!_dbus_string_copy (args, i, &base64_response, 0))
1253         goto failed;
1254
1255       if (!_dbus_string_base64_decode (&base64_response, 0,
1256                                        &decoded_response, 0))
1257         goto failed;
1258
1259       auth->mech = find_mech (&mech);
1260       if (auth->mech != NULL)
1261         {
1262           _dbus_verbose ("Trying mechanism %s with initial response of %d bytes\n",
1263                          auth->mech->mechanism,
1264                          _dbus_string_get_length (&decoded_response));
1265           
1266           if (!(* auth->mech->server_data_func) (auth,
1267                                                  &decoded_response))
1268             goto failed;
1269         }
1270       else
1271         {
1272           /* Unsupported mechanism */
1273           if (!send_rejected (auth))
1274             return FALSE;
1275         }
1276
1277       _dbus_string_free (&mech);      
1278       _dbus_string_free (&base64_response);
1279       _dbus_string_free (&decoded_response);
1280
1281       return TRUE;
1282       
1283     failed:
1284       auth->mech = NULL;
1285       _dbus_string_free (&mech);
1286       _dbus_string_free (&base64_response);
1287       _dbus_string_free (&decoded_response);
1288       return FALSE;
1289     }
1290 }
1291
1292 static dbus_bool_t
1293 process_cancel (DBusAuth         *auth,
1294                 const DBusString *command,
1295                 const DBusString *args)
1296 {
1297   shutdown_mech (auth);
1298   
1299   return TRUE;
1300 }
1301
1302 static dbus_bool_t
1303 process_begin (DBusAuth         *auth,
1304                const DBusString *command,
1305                const DBusString *args)
1306 {
1307   if (auth->authenticated_pending_begin)
1308     auth->authenticated = TRUE;
1309   else
1310     {
1311       auth->need_disconnect = TRUE; /* client trying to send data before auth,
1312                                      * kick it
1313                                      */
1314       shutdown_mech (auth);
1315     }
1316   
1317   return TRUE;
1318 }
1319
1320 static dbus_bool_t
1321 process_data_server (DBusAuth         *auth,
1322                      const DBusString *command,
1323                      const DBusString *args)
1324 {
1325   if (auth->mech != NULL)
1326     {
1327       DBusString decoded;
1328
1329       if (!_dbus_string_init (&decoded, _DBUS_INT_MAX))
1330         return FALSE;
1331
1332       if (!_dbus_string_base64_decode (args, 0, &decoded, 0))
1333         {
1334           _dbus_string_free (&decoded);
1335           return FALSE;
1336         }
1337
1338 #ifdef DBUS_ENABLE_VERBOSE_MODE
1339       if (_dbus_string_validate_ascii (&decoded, 0,
1340                                        _dbus_string_get_length (&decoded)))
1341         {
1342           const char *s;
1343           _dbus_string_get_const_data (&decoded, &s);
1344           _dbus_verbose ("data: '%s'\n", s);
1345         }
1346 #endif
1347       
1348       if (!(* auth->mech->server_data_func) (auth, &decoded))
1349         {
1350           _dbus_string_free (&decoded);
1351           return FALSE;
1352         }
1353
1354       _dbus_string_free (&decoded);
1355     }
1356   else
1357     {
1358       if (!_dbus_string_append (&auth->outgoing,
1359                                 "ERROR \"Not currently in an auth conversation\"\r\n"))
1360         return FALSE;
1361     }
1362   
1363   return TRUE;
1364 }
1365
1366 static dbus_bool_t
1367 process_error_server (DBusAuth         *auth,
1368                       const DBusString *command,
1369                       const DBusString *args)
1370 {
1371   
1372   return TRUE;
1373 }
1374
1375 /* return FALSE if no memory, TRUE if all OK */
1376 static dbus_bool_t
1377 get_word (const DBusString *str,
1378           int              *start,
1379           DBusString       *word)
1380 {
1381   int i;
1382
1383   _dbus_string_skip_blank (str, *start, start);
1384   _dbus_string_find_blank (str, *start, &i);
1385   
1386   if (i > *start)
1387     {
1388       if (!_dbus_string_copy_len (str, *start, i - *start, word, 0))
1389         return FALSE;
1390       
1391       *start = i;
1392     }
1393
1394   return TRUE;
1395 }
1396
1397 static dbus_bool_t
1398 record_mechanisms (DBusAuth         *auth,
1399                    const DBusString *command,
1400                    const DBusString *args)
1401 {
1402   int next;
1403   int len;
1404
1405   if (auth->already_got_mechanisms)
1406     return TRUE;
1407   
1408   len = _dbus_string_get_length (args);
1409   
1410   next = 0;
1411   while (next < len)
1412     {
1413       DBusString m;
1414       const DBusAuthMechanismHandler *mech;
1415       
1416       if (!_dbus_string_init (&m, _DBUS_INT_MAX))
1417         goto nomem;
1418       
1419       if (!get_word (args, &next, &m))
1420         goto nomem;
1421
1422       mech = find_mech (&m);
1423
1424       if (mech != NULL)
1425         {
1426           /* FIXME right now we try mechanisms in the order
1427            * the server lists them; should we do them in
1428            * some more deterministic order?
1429            *
1430            * Probably in all_mechanisms order, our order of
1431            * preference. Of course when the server is us,
1432            * it lists things in that order anyhow.
1433            */
1434
1435           _dbus_verbose ("Adding mechanism %s to list we will try\n",
1436                          mech->mechanism);
1437           
1438           if (!_dbus_list_append (& DBUS_AUTH_CLIENT (auth)->mechs_to_try,
1439                                   (void*) mech))
1440             goto nomem;
1441         }
1442       else
1443         {
1444           const char *s;
1445
1446           _dbus_string_get_const_data (&m, &s);
1447           _dbus_verbose ("Server offered mechanism \"%s\" that we don't know how to use\n",
1448                          s);
1449         }
1450
1451       _dbus_string_free (&m);
1452     }
1453   
1454   auth->already_got_mechanisms = TRUE;
1455   
1456   return TRUE;
1457
1458  nomem:
1459   _dbus_list_clear (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
1460   
1461   return FALSE;
1462 }
1463
1464 static dbus_bool_t
1465 client_try_next_mechanism (DBusAuth *auth)
1466 {
1467   const DBusAuthMechanismHandler *mech;
1468   DBusString auth_command;
1469
1470   if (DBUS_AUTH_CLIENT (auth)->mechs_to_try == NULL)
1471     return FALSE;
1472
1473   mech = DBUS_AUTH_CLIENT (auth)->mechs_to_try->data;
1474
1475   if (!_dbus_string_init (&auth_command, _DBUS_INT_MAX))
1476     return FALSE;
1477       
1478   if (!_dbus_string_append (&auth_command,
1479                             "AUTH "))
1480     {
1481       _dbus_string_free (&auth_command);
1482       return FALSE;
1483     }  
1484   
1485   if (!_dbus_string_append (&auth_command,
1486                             mech->mechanism))
1487     {
1488       _dbus_string_free (&auth_command);
1489       return FALSE;
1490     }
1491
1492   if (mech->client_initial_response_func != NULL)
1493     {
1494       if (!_dbus_string_append (&auth_command, " "))
1495         {
1496           _dbus_string_free (&auth_command);
1497           return FALSE;
1498         }
1499       
1500       if (!(* mech->client_initial_response_func) (auth, &auth_command))
1501         {
1502           _dbus_string_free (&auth_command);
1503           return FALSE;
1504         }
1505     }
1506   
1507   if (!_dbus_string_append (&auth_command,
1508                             "\r\n"))
1509     {
1510       _dbus_string_free (&auth_command);
1511       return FALSE;
1512     }
1513
1514   if (!_dbus_string_copy (&auth_command, 0,
1515                           &auth->outgoing,
1516                           _dbus_string_get_length (&auth->outgoing)))
1517     {
1518       _dbus_string_free (&auth_command);
1519       return FALSE;
1520     }
1521
1522   auth->mech = mech;      
1523   _dbus_list_pop_first (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
1524
1525   _dbus_verbose ("Trying mechanism %s\n",
1526                  auth->mech->mechanism);
1527
1528   _dbus_string_free (&auth_command);
1529   
1530   return TRUE;
1531 }
1532
1533 static dbus_bool_t
1534 process_rejected (DBusAuth         *auth,
1535                   const DBusString *command,
1536                   const DBusString *args)
1537 {
1538   shutdown_mech (auth);
1539   
1540   if (!auth->already_got_mechanisms)
1541     {
1542       if (!record_mechanisms (auth, command, args))
1543         return FALSE;
1544     }
1545   
1546   if (DBUS_AUTH_CLIENT (auth)->mechs_to_try != NULL)
1547     {
1548       client_try_next_mechanism (auth);
1549     }
1550   else
1551     {
1552       /* Give up */
1553       auth->need_disconnect = TRUE;
1554     }
1555   
1556   return TRUE;
1557 }
1558
1559 static dbus_bool_t
1560 process_ok (DBusAuth         *auth,
1561             const DBusString *command,
1562             const DBusString *args)
1563 {
1564   if (!_dbus_string_append (&auth->outgoing,
1565                             "BEGIN\r\n"))
1566     return FALSE;
1567   
1568   auth->authenticated_pending_output = TRUE;
1569   
1570   return TRUE;
1571 }
1572
1573 static dbus_bool_t
1574 process_data_client (DBusAuth         *auth,
1575                      const DBusString *command,
1576                      const DBusString *args)
1577 {
1578   if (auth->mech != NULL)
1579     {
1580       DBusString decoded;
1581
1582       if (!_dbus_string_init (&decoded, _DBUS_INT_MAX))
1583         return FALSE;
1584
1585       if (!_dbus_string_base64_decode (args, 0, &decoded, 0))
1586         {
1587           _dbus_string_free (&decoded);
1588           return FALSE;
1589         }
1590
1591 #ifdef DBUS_ENABLE_VERBOSE_MODE
1592       if (_dbus_string_validate_ascii (&decoded, 0,
1593                                        _dbus_string_get_length (&decoded)))
1594         {
1595           const char *s;
1596           _dbus_string_get_const_data (&decoded, &s);
1597           _dbus_verbose ("data: '%s'\n", s);
1598         }
1599 #endif
1600       
1601       if (!(* auth->mech->client_data_func) (auth, &decoded))
1602         {
1603           _dbus_string_free (&decoded);
1604           return FALSE;
1605         }
1606
1607       _dbus_string_free (&decoded);
1608     }
1609   else
1610     {
1611       if (!_dbus_string_append (&auth->outgoing,
1612                                 "ERROR \"Got DATA when not in an auth exchange\"\r\n"))
1613         return FALSE;
1614     }
1615   
1616   return TRUE;
1617 }
1618
1619 static dbus_bool_t
1620 process_error_client (DBusAuth         *auth,
1621                       const DBusString *command,
1622                       const DBusString *args)
1623 {
1624   return TRUE;
1625 }
1626
1627 static dbus_bool_t
1628 process_unknown (DBusAuth         *auth,
1629                  const DBusString *command,
1630                  const DBusString *args)
1631 {
1632   if (!_dbus_string_append (&auth->outgoing,
1633                             "ERROR \"Unknown command\"\r\n"))
1634     return FALSE;
1635
1636   return TRUE;
1637 }
1638
1639 /* returns whether to call it again right away */
1640 static dbus_bool_t
1641 process_command (DBusAuth *auth)
1642 {
1643   DBusString command;
1644   DBusString args;
1645   int eol;
1646   int i, j;
1647   dbus_bool_t retval;
1648
1649   /* _dbus_verbose ("  trying process_command()\n"); */
1650   
1651   retval = FALSE;
1652   
1653   eol = 0;
1654   if (!_dbus_string_find (&auth->incoming, 0, "\r\n", &eol))
1655     return FALSE;
1656   
1657   if (!_dbus_string_init (&command, _DBUS_INT_MAX))
1658     {
1659       auth->needed_memory = TRUE;
1660       return FALSE;
1661     }
1662
1663   if (!_dbus_string_init (&args, _DBUS_INT_MAX))
1664     {
1665       auth->needed_memory = TRUE;
1666       return FALSE;
1667     }
1668   
1669   if (eol > _DBUS_ONE_MEGABYTE)
1670     {
1671       /* This is a giant line, someone is trying to hose us. */
1672       if (!_dbus_string_append (&auth->outgoing, "ERROR \"Command too long\"\r\n"))
1673         goto out;
1674       else
1675         goto next_command;
1676     }
1677
1678   if (!_dbus_string_copy_len (&auth->incoming, 0, eol, &command, 0))
1679     goto out;
1680
1681   if (!_dbus_string_validate_ascii (&command, 0,
1682                                     _dbus_string_get_length (&command)))
1683     {
1684       _dbus_verbose ("Command contained non-ASCII chars or embedded nul\n");
1685       if (!_dbus_string_append (&auth->outgoing, "ERROR \"Command contained non-ASCII\"\r\n"))
1686         goto out;
1687       else
1688         goto next_command;
1689     }
1690   
1691   {
1692     const char *q;
1693     _dbus_string_get_const_data (&command, &q);
1694     _dbus_verbose ("got command \"%s\"\n", q);
1695   }
1696   
1697   _dbus_string_find_blank (&command, 0, &i);
1698   _dbus_string_skip_blank (&command, i, &j);
1699
1700   if (j > i)
1701     _dbus_string_delete (&command, i, j - i);
1702   
1703   if (!_dbus_string_move (&command, i, &args, 0))
1704     goto out;
1705   
1706   i = 0;
1707   while (auth->handlers[i].command != NULL)
1708     {
1709       if (_dbus_string_equal_c_str (&command,
1710                                     auth->handlers[i].command))
1711         {
1712           _dbus_verbose ("Processing auth command %s\n",
1713                          auth->handlers[i].command);
1714           
1715           if (!(* auth->handlers[i].func) (auth, &command, &args))
1716             goto out;
1717
1718           break;
1719         }
1720       ++i;
1721     }
1722
1723   if (auth->handlers[i].command == NULL)
1724     {
1725       if (!process_unknown (auth, &command, &args))
1726         goto out;
1727     }
1728
1729  next_command:
1730   
1731   /* We've succeeded in processing the whole command so drop it out
1732    * of the incoming buffer and return TRUE to try another command.
1733    */
1734
1735   _dbus_string_delete (&auth->incoming, 0, eol);
1736   
1737   /* kill the \r\n */
1738   _dbus_string_delete (&auth->incoming, 0, 2);
1739
1740   retval = TRUE;
1741   
1742  out:
1743   _dbus_string_free (&args);
1744   _dbus_string_free (&command);
1745
1746   if (!retval)
1747     auth->needed_memory = TRUE;
1748   else
1749     auth->needed_memory = FALSE;
1750   
1751   return retval;
1752 }
1753
1754
1755 /** @} */
1756
1757 /**
1758  * @addtogroup DBusAuth
1759  * @{
1760  */
1761
1762 /**
1763  * Creates a new auth conversation object for the server side.
1764  * See doc/dbus-sasl-profile.txt for full details on what
1765  * this object does.
1766  *
1767  * @returns the new object or #NULL if no memory
1768  */
1769 DBusAuth*
1770 _dbus_auth_server_new (void)
1771 {
1772   DBusAuth *auth;
1773   DBusAuthServer *server_auth;
1774
1775   auth = _dbus_auth_new (sizeof (DBusAuthServer));
1776   if (auth == NULL)
1777     return NULL;
1778
1779   auth->handlers = server_handlers;
1780
1781   server_auth = DBUS_AUTH_SERVER (auth);
1782
1783   /* perhaps this should be per-mechanism with a lower
1784    * max
1785    */
1786   server_auth->failures = 0;
1787   server_auth->max_failures = 6;
1788   
1789   return auth;
1790 }
1791
1792 /**
1793  * Creates a new auth conversation object for the client side.
1794  * See doc/dbus-sasl-profile.txt for full details on what
1795  * this object does.
1796  *
1797  * @returns the new object or #NULL if no memory
1798  */
1799 DBusAuth*
1800 _dbus_auth_client_new (void)
1801 {
1802   DBusAuth *auth;
1803
1804   auth = _dbus_auth_new (sizeof (DBusAuthClient));
1805   if (auth == NULL)
1806     return NULL;
1807
1808   auth->handlers = client_handlers;
1809
1810   /* Add a default mechanism to try */
1811   if (!_dbus_list_append (& DBUS_AUTH_CLIENT (auth)->mechs_to_try,
1812                           (void*) &all_mechanisms[0]))
1813     {
1814       _dbus_auth_unref (auth);
1815       return NULL;
1816     }
1817
1818   /* Now try the mechanism we just added */
1819   if (!client_try_next_mechanism (auth))
1820     {
1821       _dbus_auth_unref (auth);
1822       return NULL;
1823     }
1824   
1825   return auth;
1826 }
1827
1828 /**
1829  * Increments the refcount of an auth object.
1830  *
1831  * @param auth the auth conversation
1832  */
1833 void
1834 _dbus_auth_ref (DBusAuth *auth)
1835 {
1836   _dbus_assert (auth != NULL);
1837   
1838   auth->refcount += 1;
1839 }
1840
1841 /**
1842  * Decrements the refcount of an auth object.
1843  *
1844  * @param auth the auth conversation
1845  */
1846 void
1847 _dbus_auth_unref (DBusAuth *auth)
1848 {
1849   _dbus_assert (auth != NULL);
1850   _dbus_assert (auth->refcount > 0);
1851
1852   auth->refcount -= 1;
1853   if (auth->refcount == 0)
1854     {
1855       shutdown_mech (auth);
1856
1857       if (DBUS_AUTH_IS_CLIENT (auth))
1858         {
1859           _dbus_list_clear (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
1860         }
1861
1862       if (auth->keyring)
1863         _dbus_keyring_unref (auth->keyring);
1864       
1865       _dbus_string_free (&auth->identity);
1866       _dbus_string_free (&auth->incoming);
1867       _dbus_string_free (&auth->outgoing);
1868       dbus_free (auth);
1869     }
1870 }
1871
1872 /**
1873  * @param auth the auth conversation object
1874  * @returns #TRUE if we're in a final state
1875  */
1876 #define DBUS_AUTH_IN_END_STATE(auth) ((auth)->need_disconnect || (auth)->authenticated)
1877
1878 /**
1879  * Analyzes buffered input and moves the auth conversation forward,
1880  * returning the new state of the auth conversation.
1881  *
1882  * @param auth the auth conversation
1883  * @returns the new state
1884  */
1885 DBusAuthState
1886 _dbus_auth_do_work (DBusAuth *auth)
1887 {
1888   auth->needed_memory = FALSE;
1889
1890   /* Max amount we'll buffer up before deciding someone's on crack */
1891 #define MAX_BUFFER (16 * _DBUS_ONE_KILOBYTE)
1892
1893   do
1894     {
1895       if (DBUS_AUTH_IN_END_STATE (auth))
1896         break;
1897       
1898       if (_dbus_string_get_length (&auth->incoming) > MAX_BUFFER ||
1899           _dbus_string_get_length (&auth->outgoing) > MAX_BUFFER)
1900         {
1901           auth->need_disconnect = TRUE;
1902           _dbus_verbose ("Disconnecting due to excessive data buffered in auth phase\n");
1903           break;
1904         }
1905
1906       if (auth->mech == NULL &&
1907           auth->already_got_mechanisms &&
1908           DBUS_AUTH_CLIENT (auth)->mechs_to_try == NULL)
1909         {
1910           auth->need_disconnect = TRUE;
1911           _dbus_verbose ("Disconnecting because we are out of mechanisms to try using\n");
1912           break;
1913         }
1914     }
1915   while (process_command (auth));
1916
1917   if (DBUS_AUTH_IS_SERVER (auth) &&
1918       DBUS_AUTH_SERVER (auth)->failures >=
1919       DBUS_AUTH_SERVER (auth)->max_failures)
1920     auth->need_disconnect = TRUE;
1921
1922   if (auth->need_disconnect)
1923     return DBUS_AUTH_STATE_NEED_DISCONNECT;
1924   else if (auth->authenticated)
1925     {
1926       if (_dbus_string_get_length (&auth->incoming) > 0)
1927         return DBUS_AUTH_STATE_AUTHENTICATED_WITH_UNUSED_BYTES;
1928       else
1929         return DBUS_AUTH_STATE_AUTHENTICATED;
1930     }
1931   else if (auth->needed_memory)
1932     return DBUS_AUTH_STATE_WAITING_FOR_MEMORY;
1933   else if (_dbus_string_get_length (&auth->outgoing) > 0)
1934     return DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND;
1935   else
1936     return DBUS_AUTH_STATE_WAITING_FOR_INPUT;
1937 }
1938
1939 /**
1940  * Gets bytes that need to be sent to the peer we're conversing with.
1941  * After writing some bytes, _dbus_auth_bytes_sent() must be called
1942  * to notify the auth object that they were written.
1943  *
1944  * @param auth the auth conversation
1945  * @param str return location for a ref to the buffer to send
1946  * @returns #FALSE if nothing to send
1947  */
1948 dbus_bool_t
1949 _dbus_auth_get_bytes_to_send (DBusAuth          *auth,
1950                               const DBusString **str)
1951 {
1952   _dbus_assert (auth != NULL);
1953   _dbus_assert (str != NULL);
1954
1955   *str = NULL;
1956   
1957   if (DBUS_AUTH_IN_END_STATE (auth))
1958     return FALSE;
1959
1960   if (_dbus_string_get_length (&auth->outgoing) == 0)
1961     return FALSE;
1962
1963   *str = &auth->outgoing;
1964
1965   return TRUE;
1966 }
1967
1968 /**
1969  * Notifies the auth conversation object that
1970  * the given number of bytes of the outgoing buffer
1971  * have been written out.
1972  *
1973  * @param auth the auth conversation
1974  * @param bytes_sent number of bytes written out
1975  */
1976 void
1977 _dbus_auth_bytes_sent (DBusAuth *auth,
1978                        int       bytes_sent)
1979 {
1980   {
1981     const char *s;
1982     _dbus_string_get_const_data (&auth->outgoing, &s);
1983     _dbus_verbose ("Sent %d bytes of: %s\n", bytes_sent, s);
1984   }
1985   
1986   _dbus_string_delete (&auth->outgoing,
1987                        0, bytes_sent);
1988   
1989   if (auth->authenticated_pending_output &&
1990       _dbus_string_get_length (&auth->outgoing) == 0)
1991     auth->authenticated = TRUE;
1992 }
1993
1994 /**
1995  * Stores bytes received from the peer we're conversing with.
1996  *
1997  * @param auth the auth conversation
1998  * @param str the received bytes.
1999  * @returns #FALSE if not enough memory to store the bytes or we were already authenticated.
2000  */
2001 dbus_bool_t
2002 _dbus_auth_bytes_received (DBusAuth   *auth,
2003                            const DBusString *str)
2004 {
2005   _dbus_assert (auth != NULL);
2006   _dbus_assert (str != NULL);
2007   
2008   if (DBUS_AUTH_IN_END_STATE (auth))
2009     return FALSE;
2010
2011   auth->needed_memory = FALSE;
2012   
2013   if (!_dbus_string_copy (str, 0,
2014                           &auth->incoming,
2015                           _dbus_string_get_length (&auth->incoming)))
2016     {
2017       auth->needed_memory = TRUE;
2018       return FALSE;
2019     }
2020
2021   _dbus_auth_do_work (auth);
2022   
2023   return TRUE;
2024 }
2025
2026 /**
2027  * Returns leftover bytes that were not used as part of the auth
2028  * conversation.  These bytes will be part of the message stream
2029  * instead. This function may not be called until authentication has
2030  * succeeded.
2031  *
2032  * @param auth the auth conversation
2033  * @param str string to append the unused bytes to
2034  * @returns #FALSE if not enough memory to return the bytes
2035  */
2036 dbus_bool_t
2037 _dbus_auth_get_unused_bytes (DBusAuth   *auth,
2038                              DBusString *str)
2039 {
2040   if (!DBUS_AUTH_IN_END_STATE (auth))
2041     return FALSE;
2042   
2043   if (!_dbus_string_move (&auth->incoming,
2044                           0, str,
2045                           _dbus_string_get_length (str)))
2046     return FALSE;
2047
2048   return TRUE;
2049 }
2050
2051 /**
2052  * Called post-authentication, indicates whether we need to encode
2053  * the message stream with _dbus_auth_encode_data() prior to
2054  * sending it to the peer.
2055  *
2056  * @param auth the auth conversation
2057  * @returns #TRUE if we need to encode the stream
2058  */
2059 dbus_bool_t
2060 _dbus_auth_needs_encoding (DBusAuth *auth)
2061 {
2062   if (!auth->authenticated)
2063     return FALSE;
2064   
2065   if (auth->mech != NULL)
2066     {
2067       if (DBUS_AUTH_IS_CLIENT (auth))
2068         return auth->mech->client_encode_func != NULL;
2069       else
2070         return auth->mech->server_encode_func != NULL;
2071     }
2072   else
2073     return FALSE;
2074 }
2075
2076 /**
2077  * Called post-authentication, encodes a block of bytes for sending to
2078  * the peer. If no encoding was negotiated, just copies the bytes
2079  * (you can avoid this by checking _dbus_auth_needs_encoding()).
2080  *
2081  * @param auth the auth conversation
2082  * @param plaintext the plain text data
2083  * @param encoded initialized string to where encoded data is appended
2084  * @returns #TRUE if we had enough memory and successfully encoded
2085  */
2086 dbus_bool_t
2087 _dbus_auth_encode_data (DBusAuth         *auth,
2088                         const DBusString *plaintext,
2089                         DBusString       *encoded)
2090 {
2091   _dbus_assert (plaintext != encoded);
2092   
2093   if (!auth->authenticated)
2094     return FALSE;
2095   
2096   if (_dbus_auth_needs_encoding (auth))
2097     {
2098       if (DBUS_AUTH_IS_CLIENT (auth))
2099         return (* auth->mech->client_encode_func) (auth, plaintext, encoded);
2100       else
2101         return (* auth->mech->server_encode_func) (auth, plaintext, encoded);
2102     }
2103   else
2104     {
2105       return _dbus_string_copy (plaintext, 0, encoded,
2106                                 _dbus_string_get_length (encoded));
2107     }
2108 }
2109
2110 /**
2111  * Called post-authentication, indicates whether we need to decode
2112  * the message stream with _dbus_auth_decode_data() after
2113  * receiving it from the peer.
2114  *
2115  * @param auth the auth conversation
2116  * @returns #TRUE if we need to encode the stream
2117  */
2118 dbus_bool_t
2119 _dbus_auth_needs_decoding (DBusAuth *auth)
2120 {
2121   if (!auth->authenticated)
2122     return FALSE;
2123     
2124   if (auth->mech != NULL)
2125     {
2126       if (DBUS_AUTH_IS_CLIENT (auth))
2127         return auth->mech->client_decode_func != NULL;
2128       else
2129         return auth->mech->server_decode_func != NULL;
2130     }
2131   else
2132     return FALSE;
2133 }
2134
2135
2136 /**
2137  * Called post-authentication, decodes a block of bytes received from
2138  * the peer. If no encoding was negotiated, just copies the bytes (you
2139  * can avoid this by checking _dbus_auth_needs_decoding()).
2140  *
2141  * @todo We need to be able to distinguish "out of memory" error
2142  * from "the data is hosed" error.
2143  *
2144  * @param auth the auth conversation
2145  * @param encoded the encoded data
2146  * @param plaintext initialized string where decoded data is appended
2147  * @returns #TRUE if we had enough memory and successfully decoded
2148  */
2149 dbus_bool_t
2150 _dbus_auth_decode_data (DBusAuth         *auth,
2151                         const DBusString *encoded,
2152                         DBusString       *plaintext)
2153 {
2154   _dbus_assert (plaintext != encoded);
2155   
2156   if (!auth->authenticated)
2157     return FALSE;
2158   
2159   if (_dbus_auth_needs_decoding (auth))
2160     {
2161       if (DBUS_AUTH_IS_CLIENT (auth))
2162         return (* auth->mech->client_decode_func) (auth, encoded, plaintext);
2163       else
2164         return (* auth->mech->server_decode_func) (auth, encoded, plaintext);
2165     }
2166   else
2167     {
2168       return _dbus_string_copy (encoded, 0, plaintext,
2169                                 _dbus_string_get_length (plaintext));
2170     }
2171 }
2172
2173 /**
2174  * Sets credentials received via reliable means from the operating
2175  * system.
2176  *
2177  * @param auth the auth conversation
2178  * @param credentials the credentials received
2179  */
2180 void
2181 _dbus_auth_set_credentials (DBusAuth               *auth,
2182                             const DBusCredentials  *credentials)
2183 {
2184   auth->credentials = *credentials;
2185 }
2186
2187 /**
2188  * Gets the identity we authorized the client as.  Apps may have
2189  * different policies as to what identities they allow.
2190  *
2191  * @param auth the auth conversation
2192  * @param credentials the credentials we've authorized
2193  */
2194 void
2195 _dbus_auth_get_identity (DBusAuth               *auth,
2196                          DBusCredentials        *credentials)
2197 {
2198   if (auth->authenticated)
2199     {
2200       *credentials = auth->authorized_identity;
2201     }
2202   else
2203     {
2204       credentials->pid = -1;
2205       credentials->uid = -1;
2206       credentials->gid = -1;
2207     }
2208 }
2209
2210 /**
2211  * Sets the "authentication context" which scopes cookies
2212  * with the DBUS_COOKIE_SHA1 auth mechanism for example.
2213  *
2214  * @param auth the auth conversation
2215  * @param context the context
2216  * @returns #FALSE if no memory
2217  */
2218 dbus_bool_t
2219 _dbus_auth_set_context (DBusAuth               *auth,
2220                         const DBusString       *context)
2221 {
2222   return _dbus_string_replace_len (context, 0, _dbus_string_get_length (context),
2223                                    &auth->context, 0, _dbus_string_get_length (context));
2224 }
2225
2226 /** @} */
2227
2228 #ifdef DBUS_BUILD_TESTS
2229 #include "dbus-test.h"
2230 #include "dbus-auth-script.h"
2231 #include <stdio.h>
2232
2233 static dbus_bool_t
2234 process_test_subdir (const DBusString          *test_base_dir,
2235                      const char                *subdir)
2236 {
2237   DBusString test_directory;
2238   DBusString filename;
2239   DBusDirIter *dir;
2240   dbus_bool_t retval;
2241   DBusResultCode result;
2242
2243   retval = FALSE;
2244   dir = NULL;
2245   
2246   if (!_dbus_string_init (&test_directory, _DBUS_INT_MAX))
2247     _dbus_assert_not_reached ("didn't allocate test_directory\n");
2248
2249   _dbus_string_init_const (&filename, subdir);
2250   
2251   if (!_dbus_string_copy (test_base_dir, 0,
2252                           &test_directory, 0))
2253     _dbus_assert_not_reached ("couldn't copy test_base_dir to test_directory");
2254   
2255   if (!_dbus_concat_dir_and_file (&test_directory, &filename))    
2256     _dbus_assert_not_reached ("couldn't allocate full path");
2257
2258   _dbus_string_free (&filename);
2259   if (!_dbus_string_init (&filename, _DBUS_INT_MAX))
2260     _dbus_assert_not_reached ("didn't allocate filename string\n");
2261   
2262   dir = _dbus_directory_open (&test_directory, &result);
2263   if (dir == NULL)
2264     {
2265       const char *s;
2266       _dbus_string_get_const_data (&test_directory, &s);
2267       _dbus_warn ("Could not open %s: %s\n", s,
2268                   dbus_result_to_string (result));
2269       goto failed;
2270     }
2271
2272   printf ("Testing:\n");
2273   
2274   result = DBUS_RESULT_SUCCESS;
2275  next:
2276   while (_dbus_directory_get_next_file (dir, &filename, &result))
2277     {
2278       DBusString full_path;
2279       
2280       if (!_dbus_string_init (&full_path, _DBUS_INT_MAX))
2281         _dbus_assert_not_reached ("couldn't init string");
2282
2283       if (!_dbus_string_copy (&test_directory, 0, &full_path, 0))
2284         _dbus_assert_not_reached ("couldn't copy dir to full_path");
2285
2286       if (!_dbus_concat_dir_and_file (&full_path, &filename))
2287         _dbus_assert_not_reached ("couldn't concat file to dir");
2288
2289       if (!_dbus_string_ends_with_c_str (&filename, ".auth-script"))
2290         {
2291           const char *filename_c;
2292           _dbus_string_get_const_data (&filename, &filename_c);
2293           _dbus_verbose ("Skipping non-.auth-script file %s\n",
2294                          filename_c);
2295           _dbus_string_free (&full_path);
2296           goto next;
2297         }
2298
2299       {
2300         const char *s;
2301         _dbus_string_get_const_data (&filename, &s);
2302         printf ("    %s\n", s);
2303       }
2304       
2305       if (!_dbus_auth_script_run (&full_path))
2306         {
2307           _dbus_string_free (&full_path);
2308           goto failed;
2309         }
2310       else
2311         _dbus_string_free (&full_path);
2312     }
2313
2314   if (result != DBUS_RESULT_SUCCESS)
2315     {
2316       const char *s;
2317       _dbus_string_get_const_data (&test_directory, &s);
2318       _dbus_warn ("Could not get next file in %s: %s\n",
2319                   s, dbus_result_to_string (result));
2320       goto failed;
2321     }
2322     
2323   retval = TRUE;
2324   
2325  failed:
2326
2327   if (dir)
2328     _dbus_directory_close (dir);
2329   _dbus_string_free (&test_directory);
2330   _dbus_string_free (&filename);
2331
2332   return retval;
2333 }
2334
2335 static dbus_bool_t
2336 process_test_dirs (const char *test_data_dir)
2337 {
2338   DBusString test_directory;
2339   dbus_bool_t retval;
2340
2341   retval = FALSE;
2342   
2343   _dbus_string_init_const (&test_directory, test_data_dir);
2344
2345   if (!process_test_subdir (&test_directory, "auth"))
2346     goto failed;
2347
2348   retval = TRUE;
2349   
2350  failed:
2351
2352   _dbus_string_free (&test_directory);
2353   
2354   return retval;
2355 }
2356
2357 dbus_bool_t
2358 _dbus_auth_test (const char *test_data_dir)
2359 {
2360   
2361   if (test_data_dir == NULL)
2362     return TRUE;
2363   
2364   if (!process_test_dirs (test_data_dir))
2365     return FALSE;
2366
2367   return TRUE;
2368 }
2369
2370 #endif /* DBUS_BUILD_TESTS */