2003-03-12 Havoc Pennington <hp@redhat.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_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_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_ERROR_IS_SET (&error);
848
849               _dbus_verbose ("Error loading keyring: %s\n",
850                              error.message);
851               
852               if (_dbus_string_append (&auth->outgoing,
853                                        "ERROR \"Could not load cookie file\"\r\n"))
854                 retval = TRUE; /* retval is only about mem */
855               
856               dbus_error_free (&error);
857               goto out_3;
858             }
859         }
860       else
861         {
862           _dbus_assert (!dbus_error_is_set (&error));
863         }
864     }
865   
866   _dbus_assert (auth->keyring != NULL);
867   
868   if (!_dbus_string_init (&tmp, _DBUS_INT_MAX))
869     goto out_3;
870   
871   if (!_dbus_generate_random_bytes (&tmp, N_CHALLENGE_BYTES))
872     goto out_4;
873
874   if (!_dbus_string_init (&client_challenge, _DBUS_INT_MAX))
875     goto out_4;
876
877   if (!_dbus_string_hex_encode (&tmp, 0, &client_challenge, 0))
878     goto out_5;
879
880   if (!_dbus_string_init (&correct_hash, _DBUS_INT_MAX))
881     goto out_6;
882   
883   if (!sha1_compute_hash (auth, val,
884                           &server_challenge,
885                           &client_challenge,
886                           &correct_hash))
887     goto out_6;
888
889   if (_dbus_string_get_length (&correct_hash) == 0)
890     {
891       /* couldn't find the cookie ID or something */
892       if (_dbus_string_append (&auth->outgoing,
893                                "ERROR \"Don't have the requested cookie ID\"\r\n"))
894         retval = TRUE;
895       goto out_6;
896     }
897   
898   _dbus_string_set_length (&tmp, 0);
899   
900   if (!_dbus_string_copy (&client_challenge, 0, &tmp,
901                           _dbus_string_get_length (&tmp)))
902     goto out_6;
903
904   if (!_dbus_string_append (&tmp, " "))
905     goto out_6;
906
907   if (!_dbus_string_copy (&correct_hash, 0, &tmp,
908                           _dbus_string_get_length (&tmp)))
909     goto out_6;
910
911   old_len = _dbus_string_get_length (&auth->outgoing);
912   if (!_dbus_string_append (&auth->outgoing, "DATA "))
913     goto out_6;
914
915   if (!_dbus_string_base64_encode (&tmp, 0,
916                                    &auth->outgoing,
917                                    _dbus_string_get_length (&auth->outgoing)))
918     {
919       _dbus_string_set_length (&auth->outgoing, old_len);
920       goto out_6;
921     }
922
923   if (!_dbus_string_append (&auth->outgoing, "\r\n"))
924     {
925       _dbus_string_set_length (&auth->outgoing, old_len);
926       goto out_6;
927     }
928   
929   retval = TRUE;
930
931  out_6:
932   _dbus_string_zero (&correct_hash);
933   _dbus_string_free (&correct_hash);
934  out_5:
935   _dbus_string_free (&client_challenge);
936  out_4:
937   _dbus_string_zero (&tmp);
938   _dbus_string_free (&tmp);
939  out_3:
940   _dbus_string_free (&server_challenge);
941  out_2:
942   _dbus_string_free (&cookie_id_str);
943  out_1:
944   _dbus_string_free (&context);
945  out_0:
946   return retval;
947 }
948
949 static void
950 handle_client_shutdown_cookie_sha1_mech (DBusAuth *auth)
951 {
952   auth->cookie_id = -1;  
953   _dbus_string_set_length (&auth->challenge, 0);
954 }
955
956 static dbus_bool_t
957 handle_server_data_external_mech (DBusAuth         *auth,
958                                   const DBusString *data)
959 {
960   if (auth->credentials.uid < 0)
961     {
962       _dbus_verbose ("no credentials, mechanism EXTERNAL can't authenticate\n");
963       return send_rejected (auth);
964     }
965   
966   if (_dbus_string_get_length (data) > 0)
967     {
968       if (_dbus_string_get_length (&auth->identity) > 0)
969         {
970           /* Tried to send two auth identities, wtf */
971           _dbus_verbose ("client tried to send auth identity, but we already have one\n");
972           return send_rejected (auth);
973         }
974       else
975         {
976           /* this is our auth identity */
977           if (!_dbus_string_copy (data, 0, &auth->identity, 0))
978             return FALSE;
979         }
980     }
981
982   /* Poke client for an auth identity, if none given */
983   if (_dbus_string_get_length (&auth->identity) == 0 &&
984       !auth->already_asked_for_initial_response)
985     {
986       if (_dbus_string_append (&auth->outgoing,
987                                "DATA\r\n"))
988         {
989           _dbus_verbose ("sending empty challenge asking client for auth identity\n");
990           auth->already_asked_for_initial_response = TRUE;
991           return TRUE;
992         }
993       else
994         return FALSE;
995     }
996
997   auth->desired_identity.pid = -1;
998   auth->desired_identity.uid = -1;
999   auth->desired_identity.gid = -1;
1000   
1001   /* If auth->identity is still empty here, then client
1002    * responded with an empty string after we poked it for
1003    * an initial response. This means to try to auth the
1004    * identity provided in the credentials.
1005    */
1006   if (_dbus_string_get_length (&auth->identity) == 0)
1007     {
1008       auth->desired_identity.uid = auth->credentials.uid;
1009     }
1010   else
1011     {
1012       if (!_dbus_credentials_from_uid_string (&auth->identity,
1013                                               &auth->desired_identity))
1014         {
1015           _dbus_verbose ("could not get credentials from uid string\n");
1016           return send_rejected (auth);
1017         }
1018     }
1019
1020   if (auth->desired_identity.uid < 0)
1021     {
1022       _dbus_verbose ("desired UID %d is no good\n", auth->desired_identity.uid);
1023       return send_rejected (auth);
1024     }
1025   
1026   if (_dbus_credentials_match (&auth->desired_identity,
1027                                &auth->credentials))
1028     {
1029       /* client has authenticated */      
1030       if (!_dbus_string_append (&auth->outgoing,
1031                                 "OK\r\n"))
1032         return FALSE;
1033
1034       _dbus_verbose ("authenticated client with UID %d matching socket credentials UID %d\n",
1035                      auth->desired_identity.uid,
1036                      auth->credentials.uid);
1037       
1038       auth->authorized_identity.uid = auth->desired_identity.uid;
1039       
1040       auth->authenticated_pending_begin = TRUE;
1041       
1042       return TRUE;
1043     }
1044   else
1045     {
1046       _dbus_verbose ("credentials uid=%d gid=%d do not allow uid=%d gid=%d\n",
1047                      auth->credentials.uid, auth->credentials.gid,
1048                      auth->desired_identity.uid, auth->desired_identity.gid);
1049       return send_rejected (auth);
1050     }
1051 }
1052
1053 static void
1054 handle_server_shutdown_external_mech (DBusAuth *auth)
1055 {
1056
1057 }
1058
1059 static dbus_bool_t
1060 handle_client_initial_response_external_mech (DBusAuth         *auth,
1061                                               DBusString       *response)
1062 {
1063   /* We always append our UID as an initial response, so the server
1064    * doesn't have to send back an empty challenge to check whether we
1065    * want to specify an identity. i.e. this avoids a round trip that
1066    * the spec for the EXTERNAL mechanism otherwise requires.
1067    */
1068   DBusString plaintext;
1069
1070   if (!_dbus_string_init (&plaintext, _DBUS_INT_MAX))
1071     return FALSE;
1072   
1073   if (!_dbus_string_append_our_uid (&plaintext))
1074     goto failed;
1075
1076   if (!_dbus_string_base64_encode (&plaintext, 0,
1077                                    response,
1078                                    _dbus_string_get_length (response)))
1079     goto failed;
1080
1081   _dbus_string_free (&plaintext);
1082   
1083   return TRUE;
1084
1085  failed:
1086   _dbus_string_free (&plaintext);
1087   return FALSE;  
1088 }
1089
1090 static dbus_bool_t
1091 handle_client_data_external_mech (DBusAuth         *auth,
1092                                   const DBusString *data)
1093 {
1094   
1095   return TRUE;
1096 }
1097
1098 static void
1099 handle_client_shutdown_external_mech (DBusAuth *auth)
1100 {
1101
1102 }
1103
1104 /* Put mechanisms here in order of preference.
1105  * What I eventually want to have is:
1106  *
1107  *  - a mechanism that checks UNIX domain socket credentials
1108  *  - a simple magic cookie mechanism like X11 or ICE
1109  *  - mechanisms that chain to Cyrus SASL, so we can use anything it
1110  *    offers such as Kerberos, X509, whatever.
1111  * 
1112  */
1113 static const DBusAuthMechanismHandler
1114 all_mechanisms[] = {
1115   { "EXTERNAL",
1116     handle_server_data_external_mech,
1117     NULL, NULL,
1118     handle_server_shutdown_external_mech,
1119     handle_client_initial_response_external_mech,
1120     handle_client_data_external_mech,
1121     NULL, NULL,
1122     handle_client_shutdown_external_mech },
1123   { "DBUS_COOKIE_SHA1",
1124     handle_server_data_cookie_sha1_mech,
1125     NULL, NULL,
1126     handle_server_shutdown_cookie_sha1_mech,
1127     handle_client_initial_response_cookie_sha1_mech,
1128     handle_client_data_cookie_sha1_mech,
1129     NULL, NULL,
1130     handle_client_shutdown_cookie_sha1_mech },
1131   { NULL, NULL }
1132 };
1133
1134 static const DBusAuthMechanismHandler*
1135 find_mech (const DBusString *name)
1136 {
1137   int i;
1138   
1139   i = 0;
1140   while (all_mechanisms[i].mechanism != NULL)
1141     {
1142       if (_dbus_string_equal_c_str (name,
1143                                     all_mechanisms[i].mechanism))
1144
1145         return &all_mechanisms[i];
1146       
1147       ++i;
1148     }
1149   
1150   return NULL;
1151 }
1152
1153 static dbus_bool_t
1154 send_rejected (DBusAuth *auth)
1155 {
1156   DBusString command;
1157   DBusAuthServer *server_auth;
1158   int i;
1159   
1160   if (!_dbus_string_init (&command, _DBUS_INT_MAX))
1161     return FALSE;
1162   
1163   if (!_dbus_string_append (&command,
1164                             "REJECTED"))
1165     goto nomem;
1166
1167   i = 0;
1168   while (all_mechanisms[i].mechanism != NULL)
1169     {
1170       if (!_dbus_string_append (&command,
1171                                 " "))
1172         goto nomem;
1173
1174       if (!_dbus_string_append (&command,
1175                                 all_mechanisms[i].mechanism))
1176         goto nomem;
1177       
1178       ++i;
1179     }
1180   
1181   if (!_dbus_string_append (&command, "\r\n"))
1182     goto nomem;
1183
1184   if (!_dbus_string_copy (&command, 0, &auth->outgoing,
1185                           _dbus_string_get_length (&auth->outgoing)))
1186     goto nomem;
1187
1188   shutdown_mech (auth);
1189   
1190   _dbus_assert (DBUS_AUTH_IS_SERVER (auth));
1191   server_auth = DBUS_AUTH_SERVER (auth);
1192   server_auth->failures += 1;
1193
1194   _dbus_string_free (&command);
1195   
1196   return TRUE;
1197
1198  nomem:
1199   _dbus_string_free (&command);
1200   return FALSE;
1201 }
1202
1203 static dbus_bool_t
1204 process_auth (DBusAuth         *auth,
1205               const DBusString *command,
1206               const DBusString *args)
1207 {
1208   if (auth->mech)
1209     {
1210       /* We are already using a mechanism, client is on crack */
1211       if (!_dbus_string_append (&auth->outgoing,
1212                                 "ERROR \"Sent AUTH while another AUTH in progress\"\r\n"))
1213         return FALSE;
1214
1215       return TRUE;
1216     }
1217   else if (_dbus_string_get_length (args) == 0)
1218     {
1219       /* No args to the auth, send mechanisms */
1220       if (!send_rejected (auth))
1221         return FALSE;
1222
1223       return TRUE;
1224     }
1225   else
1226     {
1227       int i;
1228       DBusString mech;
1229       DBusString base64_response;
1230       DBusString decoded_response;
1231       
1232       _dbus_string_find_blank (args, 0, &i);
1233
1234       if (!_dbus_string_init (&mech, _DBUS_INT_MAX))
1235         return FALSE;
1236
1237       if (!_dbus_string_init (&base64_response, _DBUS_INT_MAX))
1238         {
1239           _dbus_string_free (&mech);
1240           return FALSE;
1241         }
1242
1243       if (!_dbus_string_init (&decoded_response, _DBUS_INT_MAX))
1244         {
1245           _dbus_string_free (&mech);
1246           _dbus_string_free (&base64_response);
1247           return FALSE;
1248         }
1249       
1250       if (!_dbus_string_copy_len (args, 0, i, &mech, 0))
1251         goto failed;
1252
1253       if (!_dbus_string_copy (args, i, &base64_response, 0))
1254         goto failed;
1255
1256       if (!_dbus_string_base64_decode (&base64_response, 0,
1257                                        &decoded_response, 0))
1258         goto failed;
1259
1260       auth->mech = find_mech (&mech);
1261       if (auth->mech != NULL)
1262         {
1263           _dbus_verbose ("Trying mechanism %s with initial response of %d bytes\n",
1264                          auth->mech->mechanism,
1265                          _dbus_string_get_length (&decoded_response));
1266           
1267           if (!(* auth->mech->server_data_func) (auth,
1268                                                  &decoded_response))
1269             goto failed;
1270         }
1271       else
1272         {
1273           /* Unsupported mechanism */
1274           if (!send_rejected (auth))
1275             return FALSE;
1276         }
1277
1278       _dbus_string_free (&mech);      
1279       _dbus_string_free (&base64_response);
1280       _dbus_string_free (&decoded_response);
1281
1282       return TRUE;
1283       
1284     failed:
1285       auth->mech = NULL;
1286       _dbus_string_free (&mech);
1287       _dbus_string_free (&base64_response);
1288       _dbus_string_free (&decoded_response);
1289       return FALSE;
1290     }
1291 }
1292
1293 static dbus_bool_t
1294 process_cancel (DBusAuth         *auth,
1295                 const DBusString *command,
1296                 const DBusString *args)
1297 {
1298   shutdown_mech (auth);
1299   
1300   return TRUE;
1301 }
1302
1303 static dbus_bool_t
1304 process_begin (DBusAuth         *auth,
1305                const DBusString *command,
1306                const DBusString *args)
1307 {
1308   if (auth->authenticated_pending_begin)
1309     auth->authenticated = TRUE;
1310   else
1311     {
1312       auth->need_disconnect = TRUE; /* client trying to send data before auth,
1313                                      * kick it
1314                                      */
1315       shutdown_mech (auth);
1316     }
1317   
1318   return TRUE;
1319 }
1320
1321 static dbus_bool_t
1322 process_data_server (DBusAuth         *auth,
1323                      const DBusString *command,
1324                      const DBusString *args)
1325 {
1326   if (auth->mech != NULL)
1327     {
1328       DBusString decoded;
1329
1330       if (!_dbus_string_init (&decoded, _DBUS_INT_MAX))
1331         return FALSE;
1332
1333       if (!_dbus_string_base64_decode (args, 0, &decoded, 0))
1334         {
1335           _dbus_string_free (&decoded);
1336           return FALSE;
1337         }
1338
1339 #ifdef DBUS_ENABLE_VERBOSE_MODE
1340       if (_dbus_string_validate_ascii (&decoded, 0,
1341                                        _dbus_string_get_length (&decoded)))
1342         {
1343           const char *s;
1344           _dbus_string_get_const_data (&decoded, &s);
1345           _dbus_verbose ("data: '%s'\n", s);
1346         }
1347 #endif
1348       
1349       if (!(* auth->mech->server_data_func) (auth, &decoded))
1350         {
1351           _dbus_string_free (&decoded);
1352           return FALSE;
1353         }
1354
1355       _dbus_string_free (&decoded);
1356     }
1357   else
1358     {
1359       if (!_dbus_string_append (&auth->outgoing,
1360                                 "ERROR \"Not currently in an auth conversation\"\r\n"))
1361         return FALSE;
1362     }
1363   
1364   return TRUE;
1365 }
1366
1367 static dbus_bool_t
1368 process_error_server (DBusAuth         *auth,
1369                       const DBusString *command,
1370                       const DBusString *args)
1371 {
1372   
1373   return TRUE;
1374 }
1375
1376 /* return FALSE if no memory, TRUE if all OK */
1377 static dbus_bool_t
1378 get_word (const DBusString *str,
1379           int              *start,
1380           DBusString       *word)
1381 {
1382   int i;
1383
1384   _dbus_string_skip_blank (str, *start, start);
1385   _dbus_string_find_blank (str, *start, &i);
1386   
1387   if (i > *start)
1388     {
1389       if (!_dbus_string_copy_len (str, *start, i - *start, word, 0))
1390         return FALSE;
1391       
1392       *start = i;
1393     }
1394
1395   return TRUE;
1396 }
1397
1398 static dbus_bool_t
1399 record_mechanisms (DBusAuth         *auth,
1400                    const DBusString *command,
1401                    const DBusString *args)
1402 {
1403   int next;
1404   int len;
1405
1406   if (auth->already_got_mechanisms)
1407     return TRUE;
1408   
1409   len = _dbus_string_get_length (args);
1410   
1411   next = 0;
1412   while (next < len)
1413     {
1414       DBusString m;
1415       const DBusAuthMechanismHandler *mech;
1416       
1417       if (!_dbus_string_init (&m, _DBUS_INT_MAX))
1418         goto nomem;
1419       
1420       if (!get_word (args, &next, &m))
1421         goto nomem;
1422
1423       mech = find_mech (&m);
1424
1425       if (mech != NULL)
1426         {
1427           /* FIXME right now we try mechanisms in the order
1428            * the server lists them; should we do them in
1429            * some more deterministic order?
1430            *
1431            * Probably in all_mechanisms order, our order of
1432            * preference. Of course when the server is us,
1433            * it lists things in that order anyhow.
1434            */
1435
1436           _dbus_verbose ("Adding mechanism %s to list we will try\n",
1437                          mech->mechanism);
1438           
1439           if (!_dbus_list_append (& DBUS_AUTH_CLIENT (auth)->mechs_to_try,
1440                                   (void*) mech))
1441             goto nomem;
1442         }
1443       else
1444         {
1445           const char *s;
1446
1447           _dbus_string_get_const_data (&m, &s);
1448           _dbus_verbose ("Server offered mechanism \"%s\" that we don't know how to use\n",
1449                          s);
1450         }
1451
1452       _dbus_string_free (&m);
1453     }
1454   
1455   auth->already_got_mechanisms = TRUE;
1456   
1457   return TRUE;
1458
1459  nomem:
1460   _dbus_list_clear (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
1461   
1462   return FALSE;
1463 }
1464
1465 static dbus_bool_t
1466 client_try_next_mechanism (DBusAuth *auth)
1467 {
1468   const DBusAuthMechanismHandler *mech;
1469   DBusString auth_command;
1470
1471   if (DBUS_AUTH_CLIENT (auth)->mechs_to_try == NULL)
1472     return FALSE;
1473
1474   mech = DBUS_AUTH_CLIENT (auth)->mechs_to_try->data;
1475
1476   if (!_dbus_string_init (&auth_command, _DBUS_INT_MAX))
1477     return FALSE;
1478       
1479   if (!_dbus_string_append (&auth_command,
1480                             "AUTH "))
1481     {
1482       _dbus_string_free (&auth_command);
1483       return FALSE;
1484     }  
1485   
1486   if (!_dbus_string_append (&auth_command,
1487                             mech->mechanism))
1488     {
1489       _dbus_string_free (&auth_command);
1490       return FALSE;
1491     }
1492
1493   if (mech->client_initial_response_func != NULL)
1494     {
1495       if (!_dbus_string_append (&auth_command, " "))
1496         {
1497           _dbus_string_free (&auth_command);
1498           return FALSE;
1499         }
1500       
1501       if (!(* mech->client_initial_response_func) (auth, &auth_command))
1502         {
1503           _dbus_string_free (&auth_command);
1504           return FALSE;
1505         }
1506     }
1507   
1508   if (!_dbus_string_append (&auth_command,
1509                             "\r\n"))
1510     {
1511       _dbus_string_free (&auth_command);
1512       return FALSE;
1513     }
1514
1515   if (!_dbus_string_copy (&auth_command, 0,
1516                           &auth->outgoing,
1517                           _dbus_string_get_length (&auth->outgoing)))
1518     {
1519       _dbus_string_free (&auth_command);
1520       return FALSE;
1521     }
1522
1523   auth->mech = mech;      
1524   _dbus_list_pop_first (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
1525
1526   _dbus_verbose ("Trying mechanism %s\n",
1527                  auth->mech->mechanism);
1528
1529   _dbus_string_free (&auth_command);
1530   
1531   return TRUE;
1532 }
1533
1534 static dbus_bool_t
1535 process_rejected (DBusAuth         *auth,
1536                   const DBusString *command,
1537                   const DBusString *args)
1538 {
1539   shutdown_mech (auth);
1540   
1541   if (!auth->already_got_mechanisms)
1542     {
1543       if (!record_mechanisms (auth, command, args))
1544         return FALSE;
1545     }
1546   
1547   if (DBUS_AUTH_CLIENT (auth)->mechs_to_try != NULL)
1548     {
1549       client_try_next_mechanism (auth);
1550     }
1551   else
1552     {
1553       /* Give up */
1554       auth->need_disconnect = TRUE;
1555     }
1556   
1557   return TRUE;
1558 }
1559
1560 static dbus_bool_t
1561 process_ok (DBusAuth         *auth,
1562             const DBusString *command,
1563             const DBusString *args)
1564 {
1565   if (!_dbus_string_append (&auth->outgoing,
1566                             "BEGIN\r\n"))
1567     return FALSE;
1568   
1569   auth->authenticated_pending_output = TRUE;
1570   
1571   return TRUE;
1572 }
1573
1574 static dbus_bool_t
1575 process_data_client (DBusAuth         *auth,
1576                      const DBusString *command,
1577                      const DBusString *args)
1578 {
1579   if (auth->mech != NULL)
1580     {
1581       DBusString decoded;
1582
1583       if (!_dbus_string_init (&decoded, _DBUS_INT_MAX))
1584         return FALSE;
1585
1586       if (!_dbus_string_base64_decode (args, 0, &decoded, 0))
1587         {
1588           _dbus_string_free (&decoded);
1589           return FALSE;
1590         }
1591
1592 #ifdef DBUS_ENABLE_VERBOSE_MODE
1593       if (_dbus_string_validate_ascii (&decoded, 0,
1594                                        _dbus_string_get_length (&decoded)))
1595         {
1596           const char *s;
1597           _dbus_string_get_const_data (&decoded, &s);
1598           _dbus_verbose ("data: '%s'\n", s);
1599         }
1600 #endif
1601       
1602       if (!(* auth->mech->client_data_func) (auth, &decoded))
1603         {
1604           _dbus_string_free (&decoded);
1605           return FALSE;
1606         }
1607
1608       _dbus_string_free (&decoded);
1609     }
1610   else
1611     {
1612       if (!_dbus_string_append (&auth->outgoing,
1613                                 "ERROR \"Got DATA when not in an auth exchange\"\r\n"))
1614         return FALSE;
1615     }
1616   
1617   return TRUE;
1618 }
1619
1620 static dbus_bool_t
1621 process_error_client (DBusAuth         *auth,
1622                       const DBusString *command,
1623                       const DBusString *args)
1624 {
1625   return TRUE;
1626 }
1627
1628 static dbus_bool_t
1629 process_unknown (DBusAuth         *auth,
1630                  const DBusString *command,
1631                  const DBusString *args)
1632 {
1633   if (!_dbus_string_append (&auth->outgoing,
1634                             "ERROR \"Unknown command\"\r\n"))
1635     return FALSE;
1636
1637   return TRUE;
1638 }
1639
1640 /* returns whether to call it again right away */
1641 static dbus_bool_t
1642 process_command (DBusAuth *auth)
1643 {
1644   DBusString command;
1645   DBusString args;
1646   int eol;
1647   int i, j;
1648   dbus_bool_t retval;
1649
1650   /* _dbus_verbose ("  trying process_command()\n"); */
1651   
1652   retval = FALSE;
1653   
1654   eol = 0;
1655   if (!_dbus_string_find (&auth->incoming, 0, "\r\n", &eol))
1656     return FALSE;
1657   
1658   if (!_dbus_string_init (&command, _DBUS_INT_MAX))
1659     {
1660       auth->needed_memory = TRUE;
1661       return FALSE;
1662     }
1663
1664   if (!_dbus_string_init (&args, _DBUS_INT_MAX))
1665     {
1666       auth->needed_memory = TRUE;
1667       return FALSE;
1668     }
1669   
1670   if (eol > _DBUS_ONE_MEGABYTE)
1671     {
1672       /* This is a giant line, someone is trying to hose us. */
1673       if (!_dbus_string_append (&auth->outgoing, "ERROR \"Command too long\"\r\n"))
1674         goto out;
1675       else
1676         goto next_command;
1677     }
1678
1679   if (!_dbus_string_copy_len (&auth->incoming, 0, eol, &command, 0))
1680     goto out;
1681
1682   if (!_dbus_string_validate_ascii (&command, 0,
1683                                     _dbus_string_get_length (&command)))
1684     {
1685       _dbus_verbose ("Command contained non-ASCII chars or embedded nul\n");
1686       if (!_dbus_string_append (&auth->outgoing, "ERROR \"Command contained non-ASCII\"\r\n"))
1687         goto out;
1688       else
1689         goto next_command;
1690     }
1691   
1692   {
1693     const char *q;
1694     _dbus_string_get_const_data (&command, &q);
1695     _dbus_verbose ("got command \"%s\"\n", q);
1696   }
1697   
1698   _dbus_string_find_blank (&command, 0, &i);
1699   _dbus_string_skip_blank (&command, i, &j);
1700
1701   if (j > i)
1702     _dbus_string_delete (&command, i, j - i);
1703   
1704   if (!_dbus_string_move (&command, i, &args, 0))
1705     goto out;
1706   
1707   i = 0;
1708   while (auth->handlers[i].command != NULL)
1709     {
1710       if (_dbus_string_equal_c_str (&command,
1711                                     auth->handlers[i].command))
1712         {
1713           _dbus_verbose ("Processing auth command %s\n",
1714                          auth->handlers[i].command);
1715           
1716           if (!(* auth->handlers[i].func) (auth, &command, &args))
1717             goto out;
1718
1719           break;
1720         }
1721       ++i;
1722     }
1723
1724   if (auth->handlers[i].command == NULL)
1725     {
1726       if (!process_unknown (auth, &command, &args))
1727         goto out;
1728     }
1729
1730  next_command:
1731   
1732   /* We've succeeded in processing the whole command so drop it out
1733    * of the incoming buffer and return TRUE to try another command.
1734    */
1735
1736   _dbus_string_delete (&auth->incoming, 0, eol);
1737   
1738   /* kill the \r\n */
1739   _dbus_string_delete (&auth->incoming, 0, 2);
1740
1741   retval = TRUE;
1742   
1743  out:
1744   _dbus_string_free (&args);
1745   _dbus_string_free (&command);
1746
1747   if (!retval)
1748     auth->needed_memory = TRUE;
1749   else
1750     auth->needed_memory = FALSE;
1751   
1752   return retval;
1753 }
1754
1755
1756 /** @} */
1757
1758 /**
1759  * @addtogroup DBusAuth
1760  * @{
1761  */
1762
1763 /**
1764  * Creates a new auth conversation object for the server side.
1765  * See doc/dbus-sasl-profile.txt for full details on what
1766  * this object does.
1767  *
1768  * @returns the new object or #NULL if no memory
1769  */
1770 DBusAuth*
1771 _dbus_auth_server_new (void)
1772 {
1773   DBusAuth *auth;
1774   DBusAuthServer *server_auth;
1775
1776   auth = _dbus_auth_new (sizeof (DBusAuthServer));
1777   if (auth == NULL)
1778     return NULL;
1779
1780   auth->handlers = server_handlers;
1781
1782   server_auth = DBUS_AUTH_SERVER (auth);
1783
1784   /* perhaps this should be per-mechanism with a lower
1785    * max
1786    */
1787   server_auth->failures = 0;
1788   server_auth->max_failures = 6;
1789   
1790   return auth;
1791 }
1792
1793 /**
1794  * Creates a new auth conversation object for the client side.
1795  * See doc/dbus-sasl-profile.txt for full details on what
1796  * this object does.
1797  *
1798  * @returns the new object or #NULL if no memory
1799  */
1800 DBusAuth*
1801 _dbus_auth_client_new (void)
1802 {
1803   DBusAuth *auth;
1804
1805   auth = _dbus_auth_new (sizeof (DBusAuthClient));
1806   if (auth == NULL)
1807     return NULL;
1808
1809   auth->handlers = client_handlers;
1810
1811   /* Add a default mechanism to try */
1812   if (!_dbus_list_append (& DBUS_AUTH_CLIENT (auth)->mechs_to_try,
1813                           (void*) &all_mechanisms[0]))
1814     {
1815       _dbus_auth_unref (auth);
1816       return NULL;
1817     }
1818
1819   /* Now try the mechanism we just added */
1820   if (!client_try_next_mechanism (auth))
1821     {
1822       _dbus_auth_unref (auth);
1823       return NULL;
1824     }
1825   
1826   return auth;
1827 }
1828
1829 /**
1830  * Increments the refcount of an auth object.
1831  *
1832  * @param auth the auth conversation
1833  */
1834 void
1835 _dbus_auth_ref (DBusAuth *auth)
1836 {
1837   _dbus_assert (auth != NULL);
1838   
1839   auth->refcount += 1;
1840 }
1841
1842 /**
1843  * Decrements the refcount of an auth object.
1844  *
1845  * @param auth the auth conversation
1846  */
1847 void
1848 _dbus_auth_unref (DBusAuth *auth)
1849 {
1850   _dbus_assert (auth != NULL);
1851   _dbus_assert (auth->refcount > 0);
1852
1853   auth->refcount -= 1;
1854   if (auth->refcount == 0)
1855     {
1856       shutdown_mech (auth);
1857
1858       if (DBUS_AUTH_IS_CLIENT (auth))
1859         {
1860           _dbus_list_clear (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
1861         }
1862
1863       if (auth->keyring)
1864         _dbus_keyring_unref (auth->keyring);
1865       
1866       _dbus_string_free (&auth->identity);
1867       _dbus_string_free (&auth->incoming);
1868       _dbus_string_free (&auth->outgoing);
1869       dbus_free (auth);
1870     }
1871 }
1872
1873 /**
1874  * @param auth the auth conversation object
1875  * @returns #TRUE if we're in a final state
1876  */
1877 #define DBUS_AUTH_IN_END_STATE(auth) ((auth)->need_disconnect || (auth)->authenticated)
1878
1879 /**
1880  * Analyzes buffered input and moves the auth conversation forward,
1881  * returning the new state of the auth conversation.
1882  *
1883  * @param auth the auth conversation
1884  * @returns the new state
1885  */
1886 DBusAuthState
1887 _dbus_auth_do_work (DBusAuth *auth)
1888 {
1889   auth->needed_memory = FALSE;
1890
1891   /* Max amount we'll buffer up before deciding someone's on crack */
1892 #define MAX_BUFFER (16 * _DBUS_ONE_KILOBYTE)
1893
1894   do
1895     {
1896       if (DBUS_AUTH_IN_END_STATE (auth))
1897         break;
1898       
1899       if (_dbus_string_get_length (&auth->incoming) > MAX_BUFFER ||
1900           _dbus_string_get_length (&auth->outgoing) > MAX_BUFFER)
1901         {
1902           auth->need_disconnect = TRUE;
1903           _dbus_verbose ("Disconnecting due to excessive data buffered in auth phase\n");
1904           break;
1905         }
1906
1907       if (auth->mech == NULL &&
1908           auth->already_got_mechanisms &&
1909           DBUS_AUTH_CLIENT (auth)->mechs_to_try == NULL)
1910         {
1911           auth->need_disconnect = TRUE;
1912           _dbus_verbose ("Disconnecting because we are out of mechanisms to try using\n");
1913           break;
1914         }
1915     }
1916   while (process_command (auth));
1917
1918   if (DBUS_AUTH_IS_SERVER (auth) &&
1919       DBUS_AUTH_SERVER (auth)->failures >=
1920       DBUS_AUTH_SERVER (auth)->max_failures)
1921     auth->need_disconnect = TRUE;
1922
1923   if (auth->need_disconnect)
1924     return DBUS_AUTH_STATE_NEED_DISCONNECT;
1925   else if (auth->authenticated)
1926     {
1927       if (_dbus_string_get_length (&auth->incoming) > 0)
1928         return DBUS_AUTH_STATE_AUTHENTICATED_WITH_UNUSED_BYTES;
1929       else
1930         return DBUS_AUTH_STATE_AUTHENTICATED;
1931     }
1932   else if (auth->needed_memory)
1933     return DBUS_AUTH_STATE_WAITING_FOR_MEMORY;
1934   else if (_dbus_string_get_length (&auth->outgoing) > 0)
1935     return DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND;
1936   else
1937     return DBUS_AUTH_STATE_WAITING_FOR_INPUT;
1938 }
1939
1940 /**
1941  * Gets bytes that need to be sent to the peer we're conversing with.
1942  * After writing some bytes, _dbus_auth_bytes_sent() must be called
1943  * to notify the auth object that they were written.
1944  *
1945  * @param auth the auth conversation
1946  * @param str return location for a ref to the buffer to send
1947  * @returns #FALSE if nothing to send
1948  */
1949 dbus_bool_t
1950 _dbus_auth_get_bytes_to_send (DBusAuth          *auth,
1951                               const DBusString **str)
1952 {
1953   _dbus_assert (auth != NULL);
1954   _dbus_assert (str != NULL);
1955
1956   *str = NULL;
1957   
1958   if (DBUS_AUTH_IN_END_STATE (auth))
1959     return FALSE;
1960
1961   if (_dbus_string_get_length (&auth->outgoing) == 0)
1962     return FALSE;
1963
1964   *str = &auth->outgoing;
1965
1966   return TRUE;
1967 }
1968
1969 /**
1970  * Notifies the auth conversation object that
1971  * the given number of bytes of the outgoing buffer
1972  * have been written out.
1973  *
1974  * @param auth the auth conversation
1975  * @param bytes_sent number of bytes written out
1976  */
1977 void
1978 _dbus_auth_bytes_sent (DBusAuth *auth,
1979                        int       bytes_sent)
1980 {
1981   {
1982     const char *s;
1983     _dbus_string_get_const_data (&auth->outgoing, &s);
1984     _dbus_verbose ("Sent %d bytes of: %s\n", bytes_sent, s);
1985   }
1986   
1987   _dbus_string_delete (&auth->outgoing,
1988                        0, bytes_sent);
1989   
1990   if (auth->authenticated_pending_output &&
1991       _dbus_string_get_length (&auth->outgoing) == 0)
1992     auth->authenticated = TRUE;
1993 }
1994
1995 /**
1996  * Stores bytes received from the peer we're conversing with.
1997  *
1998  * @param auth the auth conversation
1999  * @param str the received bytes.
2000  * @returns #FALSE if not enough memory to store the bytes or we were already authenticated.
2001  */
2002 dbus_bool_t
2003 _dbus_auth_bytes_received (DBusAuth   *auth,
2004                            const DBusString *str)
2005 {
2006   _dbus_assert (auth != NULL);
2007   _dbus_assert (str != NULL);
2008   
2009   if (DBUS_AUTH_IN_END_STATE (auth))
2010     return FALSE;
2011
2012   auth->needed_memory = FALSE;
2013   
2014   if (!_dbus_string_copy (str, 0,
2015                           &auth->incoming,
2016                           _dbus_string_get_length (&auth->incoming)))
2017     {
2018       auth->needed_memory = TRUE;
2019       return FALSE;
2020     }
2021
2022   _dbus_auth_do_work (auth);
2023   
2024   return TRUE;
2025 }
2026
2027 /**
2028  * Returns leftover bytes that were not used as part of the auth
2029  * conversation.  These bytes will be part of the message stream
2030  * instead. This function may not be called until authentication has
2031  * succeeded.
2032  *
2033  * @param auth the auth conversation
2034  * @param str string to append the unused bytes to
2035  * @returns #FALSE if not enough memory to return the bytes
2036  */
2037 dbus_bool_t
2038 _dbus_auth_get_unused_bytes (DBusAuth   *auth,
2039                              DBusString *str)
2040 {
2041   if (!DBUS_AUTH_IN_END_STATE (auth))
2042     return FALSE;
2043   
2044   if (!_dbus_string_move (&auth->incoming,
2045                           0, str,
2046                           _dbus_string_get_length (str)))
2047     return FALSE;
2048
2049   return TRUE;
2050 }
2051
2052 /**
2053  * Called post-authentication, indicates whether we need to encode
2054  * the message stream with _dbus_auth_encode_data() prior to
2055  * sending it to the peer.
2056  *
2057  * @param auth the auth conversation
2058  * @returns #TRUE if we need to encode the stream
2059  */
2060 dbus_bool_t
2061 _dbus_auth_needs_encoding (DBusAuth *auth)
2062 {
2063   if (!auth->authenticated)
2064     return FALSE;
2065   
2066   if (auth->mech != NULL)
2067     {
2068       if (DBUS_AUTH_IS_CLIENT (auth))
2069         return auth->mech->client_encode_func != NULL;
2070       else
2071         return auth->mech->server_encode_func != NULL;
2072     }
2073   else
2074     return FALSE;
2075 }
2076
2077 /**
2078  * Called post-authentication, encodes a block of bytes for sending to
2079  * the peer. If no encoding was negotiated, just copies the bytes
2080  * (you can avoid this by checking _dbus_auth_needs_encoding()).
2081  *
2082  * @param auth the auth conversation
2083  * @param plaintext the plain text data
2084  * @param encoded initialized string to where encoded data is appended
2085  * @returns #TRUE if we had enough memory and successfully encoded
2086  */
2087 dbus_bool_t
2088 _dbus_auth_encode_data (DBusAuth         *auth,
2089                         const DBusString *plaintext,
2090                         DBusString       *encoded)
2091 {
2092   _dbus_assert (plaintext != encoded);
2093   
2094   if (!auth->authenticated)
2095     return FALSE;
2096   
2097   if (_dbus_auth_needs_encoding (auth))
2098     {
2099       if (DBUS_AUTH_IS_CLIENT (auth))
2100         return (* auth->mech->client_encode_func) (auth, plaintext, encoded);
2101       else
2102         return (* auth->mech->server_encode_func) (auth, plaintext, encoded);
2103     }
2104   else
2105     {
2106       return _dbus_string_copy (plaintext, 0, encoded,
2107                                 _dbus_string_get_length (encoded));
2108     }
2109 }
2110
2111 /**
2112  * Called post-authentication, indicates whether we need to decode
2113  * the message stream with _dbus_auth_decode_data() after
2114  * receiving it from the peer.
2115  *
2116  * @param auth the auth conversation
2117  * @returns #TRUE if we need to encode the stream
2118  */
2119 dbus_bool_t
2120 _dbus_auth_needs_decoding (DBusAuth *auth)
2121 {
2122   if (!auth->authenticated)
2123     return FALSE;
2124     
2125   if (auth->mech != NULL)
2126     {
2127       if (DBUS_AUTH_IS_CLIENT (auth))
2128         return auth->mech->client_decode_func != NULL;
2129       else
2130         return auth->mech->server_decode_func != NULL;
2131     }
2132   else
2133     return FALSE;
2134 }
2135
2136
2137 /**
2138  * Called post-authentication, decodes a block of bytes received from
2139  * the peer. If no encoding was negotiated, just copies the bytes (you
2140  * can avoid this by checking _dbus_auth_needs_decoding()).
2141  *
2142  * @todo We need to be able to distinguish "out of memory" error
2143  * from "the data is hosed" error.
2144  *
2145  * @param auth the auth conversation
2146  * @param encoded the encoded data
2147  * @param plaintext initialized string where decoded data is appended
2148  * @returns #TRUE if we had enough memory and successfully decoded
2149  */
2150 dbus_bool_t
2151 _dbus_auth_decode_data (DBusAuth         *auth,
2152                         const DBusString *encoded,
2153                         DBusString       *plaintext)
2154 {
2155   _dbus_assert (plaintext != encoded);
2156   
2157   if (!auth->authenticated)
2158     return FALSE;
2159   
2160   if (_dbus_auth_needs_decoding (auth))
2161     {
2162       if (DBUS_AUTH_IS_CLIENT (auth))
2163         return (* auth->mech->client_decode_func) (auth, encoded, plaintext);
2164       else
2165         return (* auth->mech->server_decode_func) (auth, encoded, plaintext);
2166     }
2167   else
2168     {
2169       return _dbus_string_copy (encoded, 0, plaintext,
2170                                 _dbus_string_get_length (plaintext));
2171     }
2172 }
2173
2174 /**
2175  * Sets credentials received via reliable means from the operating
2176  * system.
2177  *
2178  * @param auth the auth conversation
2179  * @param credentials the credentials received
2180  */
2181 void
2182 _dbus_auth_set_credentials (DBusAuth               *auth,
2183                             const DBusCredentials  *credentials)
2184 {
2185   auth->credentials = *credentials;
2186 }
2187
2188 /**
2189  * Gets the identity we authorized the client as.  Apps may have
2190  * different policies as to what identities they allow.
2191  *
2192  * @param auth the auth conversation
2193  * @param credentials the credentials we've authorized
2194  */
2195 void
2196 _dbus_auth_get_identity (DBusAuth               *auth,
2197                          DBusCredentials        *credentials)
2198 {
2199   if (auth->authenticated)
2200     {
2201       *credentials = auth->authorized_identity;
2202     }
2203   else
2204     {
2205       credentials->pid = -1;
2206       credentials->uid = -1;
2207       credentials->gid = -1;
2208     }
2209 }
2210
2211 /**
2212  * Sets the "authentication context" which scopes cookies
2213  * with the DBUS_COOKIE_SHA1 auth mechanism for example.
2214  *
2215  * @param auth the auth conversation
2216  * @param context the context
2217  * @returns #FALSE if no memory
2218  */
2219 dbus_bool_t
2220 _dbus_auth_set_context (DBusAuth               *auth,
2221                         const DBusString       *context)
2222 {
2223   return _dbus_string_replace_len (context, 0, _dbus_string_get_length (context),
2224                                    &auth->context, 0, _dbus_string_get_length (context));
2225 }
2226
2227 /** @} */
2228
2229 #ifdef DBUS_BUILD_TESTS
2230 #include "dbus-test.h"
2231 #include "dbus-auth-script.h"
2232 #include <stdio.h>
2233
2234 static dbus_bool_t
2235 process_test_subdir (const DBusString          *test_base_dir,
2236                      const char                *subdir)
2237 {
2238   DBusString test_directory;
2239   DBusString filename;
2240   DBusDirIter *dir;
2241   dbus_bool_t retval;
2242   DBusError error;
2243
2244   retval = FALSE;
2245   dir = NULL;
2246   
2247   if (!_dbus_string_init (&test_directory, _DBUS_INT_MAX))
2248     _dbus_assert_not_reached ("didn't allocate test_directory\n");
2249
2250   _dbus_string_init_const (&filename, subdir);
2251   
2252   if (!_dbus_string_copy (test_base_dir, 0,
2253                           &test_directory, 0))
2254     _dbus_assert_not_reached ("couldn't copy test_base_dir to test_directory");
2255   
2256   if (!_dbus_concat_dir_and_file (&test_directory, &filename))    
2257     _dbus_assert_not_reached ("couldn't allocate full path");
2258
2259   _dbus_string_free (&filename);
2260   if (!_dbus_string_init (&filename, _DBUS_INT_MAX))
2261     _dbus_assert_not_reached ("didn't allocate filename string\n");
2262
2263   dbus_error_init (&error);
2264   dir = _dbus_directory_open (&test_directory, &error);
2265   if (dir == NULL)
2266     {
2267       const char *s;
2268       _dbus_string_get_const_data (&test_directory, &s);
2269       _dbus_warn ("Could not open %s: %s\n", s,
2270                   error.message);
2271       dbus_error_free (&error);
2272       goto failed;
2273     }
2274
2275   printf ("Testing:\n");
2276   
2277  next:
2278   while (_dbus_directory_get_next_file (dir, &filename, &error))
2279     {
2280       DBusString full_path;
2281       
2282       if (!_dbus_string_init (&full_path, _DBUS_INT_MAX))
2283         _dbus_assert_not_reached ("couldn't init string");
2284
2285       if (!_dbus_string_copy (&test_directory, 0, &full_path, 0))
2286         _dbus_assert_not_reached ("couldn't copy dir to full_path");
2287
2288       if (!_dbus_concat_dir_and_file (&full_path, &filename))
2289         _dbus_assert_not_reached ("couldn't concat file to dir");
2290
2291       if (!_dbus_string_ends_with_c_str (&filename, ".auth-script"))
2292         {
2293           const char *filename_c;
2294           _dbus_string_get_const_data (&filename, &filename_c);
2295           _dbus_verbose ("Skipping non-.auth-script file %s\n",
2296                          filename_c);
2297           _dbus_string_free (&full_path);
2298           goto next;
2299         }
2300
2301       {
2302         const char *s;
2303         _dbus_string_get_const_data (&filename, &s);
2304         printf ("    %s\n", s);
2305       }
2306       
2307       if (!_dbus_auth_script_run (&full_path))
2308         {
2309           _dbus_string_free (&full_path);
2310           goto failed;
2311         }
2312       else
2313         _dbus_string_free (&full_path);
2314     }
2315
2316   if (dbus_error_is_set (&error))
2317     {
2318       const char *s;
2319       _dbus_string_get_const_data (&test_directory, &s);
2320       _dbus_warn ("Could not get next file in %s: %s\n",
2321                   s, error.message);
2322       dbus_error_free (&error);
2323       goto failed;
2324     }
2325     
2326   retval = TRUE;
2327   
2328  failed:
2329
2330   if (dir)
2331     _dbus_directory_close (dir);
2332   _dbus_string_free (&test_directory);
2333   _dbus_string_free (&filename);
2334
2335   return retval;
2336 }
2337
2338 static dbus_bool_t
2339 process_test_dirs (const char *test_data_dir)
2340 {
2341   DBusString test_directory;
2342   dbus_bool_t retval;
2343
2344   retval = FALSE;
2345   
2346   _dbus_string_init_const (&test_directory, test_data_dir);
2347
2348   if (!process_test_subdir (&test_directory, "auth"))
2349     goto failed;
2350
2351   retval = TRUE;
2352   
2353  failed:
2354
2355   _dbus_string_free (&test_directory);
2356   
2357   return retval;
2358 }
2359
2360 dbus_bool_t
2361 _dbus_auth_test (const char *test_data_dir)
2362 {
2363   
2364   if (test_data_dir == NULL)
2365     return TRUE;
2366   
2367   if (!process_test_dirs (test_data_dir))
2368     return FALSE;
2369
2370   return TRUE;
2371 }
2372
2373 #endif /* DBUS_BUILD_TESTS */