2003-02-13 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
28 /* See doc/dbus-sasl-profile.txt */
29
30 /**
31  * @defgroup DBusAuth Authentication
32  * @ingroup  DBusInternals
33  * @brief DBusAuth object
34  *
35  * DBusAuth manages the authentication negotiation when a connection
36  * is first established, and also manage any encryption used over a
37  * connection.
38  *
39  * The file doc/dbus-sasl-profile.txt documents the network protocol
40  * used for authentication.
41  *
42  * @todo some SASL profiles require sending the empty string as a
43  * challenge/response, but we don't currently allow that in our
44  * protocol.
45  */
46
47 /**
48  * @defgroup DBusAuthInternals Authentication implementation details
49  * @ingroup  DBusInternals
50  * @brief DBusAuth implementation details
51  *
52  * Private details of authentication code.
53  *
54  * @{
55  */
56
57 /**
58  * Processes a command. Returns whether we had enough memory to
59  * complete the operation.
60  */
61 typedef dbus_bool_t (* DBusProcessAuthCommandFunction) (DBusAuth         *auth,
62                                                         const DBusString *command,
63                                                         const DBusString *args);
64
65 typedef struct
66 {
67   const char *command;
68   DBusProcessAuthCommandFunction func;
69 } DBusAuthCommandHandler;
70
71 /**
72  * This function appends an initial client response to the given string
73  */
74 typedef dbus_bool_t (* DBusInitialResponseFunction)  (DBusAuth         *auth,
75                                                       DBusString       *response);
76
77 /**
78  * This function processes a block of data received from the peer.
79  * i.e. handles a DATA command.
80  */
81 typedef dbus_bool_t (* DBusAuthDataFunction)     (DBusAuth         *auth,
82                                                   const DBusString *data);
83
84 /**
85  * This function encodes a block of data from the peer.
86  */
87 typedef dbus_bool_t (* DBusAuthEncodeFunction)   (DBusAuth         *auth,
88                                                   const DBusString *data,
89                                                   DBusString       *encoded);
90
91 /**
92  * This function decodes a block of data from the peer.
93  */
94 typedef dbus_bool_t (* DBusAuthDecodeFunction)   (DBusAuth         *auth,
95                                                   const DBusString *data,
96                                                   DBusString       *decoded);
97
98 /**
99  * This function is called when the mechanism is abandoned.
100  */
101 typedef void        (* DBusAuthShutdownFunction) (DBusAuth       *auth);
102
103 typedef struct
104 {
105   const char *mechanism;
106   DBusAuthDataFunction server_data_func;
107   DBusAuthEncodeFunction server_encode_func;
108   DBusAuthDecodeFunction server_decode_func;
109   DBusAuthShutdownFunction server_shutdown_func;
110   DBusInitialResponseFunction client_initial_response_func;
111   DBusAuthDataFunction client_data_func;
112   DBusAuthEncodeFunction client_encode_func;
113   DBusAuthDecodeFunction client_decode_func;
114   DBusAuthShutdownFunction client_shutdown_func;
115 } DBusAuthMechanismHandler;
116
117 /**
118  * Internal members of DBusAuth.
119  */
120 struct DBusAuth
121 {
122   int refcount;           /**< reference count */
123
124   DBusString incoming;    /**< Incoming data buffer */
125   DBusString outgoing;    /**< Outgoing data buffer */
126   
127   const DBusAuthCommandHandler *handlers; /**< Handlers for commands */
128
129   const DBusAuthMechanismHandler *mech;   /**< Current auth mechanism */
130
131   DBusString identity;                   /**< Current identity we're authorizing
132                                           *   as.
133                                           */
134   
135   DBusCredentials credentials;      /**< Credentials, fields may be -1 */
136
137   DBusCredentials authorized_identity; /**< Credentials that are authorized */
138   
139   unsigned int needed_memory : 1;   /**< We needed memory to continue since last
140                                      * successful getting something done
141                                      */
142   unsigned int need_disconnect : 1; /**< We've given up, time to disconnect */
143   unsigned int authenticated : 1;   /**< We are authenticated */
144   unsigned int authenticated_pending_output : 1; /**< Authenticated once we clear outgoing buffer */
145   unsigned int authenticated_pending_begin : 1;  /**< Authenticated once we get BEGIN */
146   unsigned int already_got_mechanisms : 1;       /**< Client already got mech list */
147   unsigned int already_asked_for_initial_response : 1; /**< Already sent a blank challenge to get an initial response */
148 };
149
150 typedef struct
151 {
152   DBusAuth base;
153
154   DBusList *mechs_to_try; /**< Mechanisms we got from the server that we're going to try using */
155   
156 } DBusAuthClient;
157
158 typedef struct
159 {
160   DBusAuth base;
161
162   int failures;     /**< Number of times client has been rejected */
163   int max_failures; /**< Number of times we reject before disconnect */
164   
165 } DBusAuthServer;
166
167 static dbus_bool_t process_auth         (DBusAuth         *auth,
168                                          const DBusString *command,
169                                          const DBusString *args);
170 static dbus_bool_t process_cancel       (DBusAuth         *auth,
171                                          const DBusString *command,
172                                          const DBusString *args);
173 static dbus_bool_t process_begin        (DBusAuth         *auth,
174                                          const DBusString *command,
175                                          const DBusString *args);
176 static dbus_bool_t process_data_server  (DBusAuth         *auth,
177                                          const DBusString *command,
178                                          const DBusString *args);
179 static dbus_bool_t process_error_server (DBusAuth         *auth,
180                                          const DBusString *command,
181                                          const DBusString *args);
182 static dbus_bool_t process_rejected     (DBusAuth         *auth,
183                                          const DBusString *command,
184                                          const DBusString *args);
185 static dbus_bool_t process_ok           (DBusAuth         *auth,
186                                          const DBusString *command,
187                                          const DBusString *args);
188 static dbus_bool_t process_data_client  (DBusAuth         *auth,
189                                          const DBusString *command,
190                                          const DBusString *args);
191 static dbus_bool_t process_error_client (DBusAuth         *auth,
192                                          const DBusString *command,
193                                          const DBusString *args);
194
195
196 static dbus_bool_t client_try_next_mechanism (DBusAuth *auth);
197 static dbus_bool_t send_rejected             (DBusAuth *auth);
198
199 static DBusAuthCommandHandler
200 server_handlers[] = {
201   { "AUTH", process_auth },
202   { "CANCEL", process_cancel },
203   { "BEGIN", process_begin },
204   { "DATA", process_data_server },
205   { "ERROR", process_error_server },
206   { NULL, NULL }
207 };
208
209 static DBusAuthCommandHandler
210 client_handlers[] = {
211   { "REJECTED", process_rejected },
212   { "OK", process_ok },
213   { "DATA", process_data_client },
214   { "ERROR", process_error_client },
215   { NULL, NULL }
216 };
217
218 /**
219  * @param auth the auth conversation
220  * @returns #TRUE if the conversation is the server side
221  */
222 #define DBUS_AUTH_IS_SERVER(auth) ((auth)->handlers == server_handlers)
223 /**
224  * @param auth the auth conversation
225  * @returns #TRUE if the conversation is the client side
226  */
227 #define DBUS_AUTH_IS_CLIENT(auth) ((auth)->handlers == client_handlers)
228 /**
229  * @param auth the auth conversation
230  * @returns auth cast to DBusAuthClient
231  */
232 #define DBUS_AUTH_CLIENT(auth)    ((DBusAuthClient*)(auth))
233 /**
234  * @param auth the auth conversation
235  * @returns auth cast to DBusAuthServer
236  */
237 #define DBUS_AUTH_SERVER(auth)    ((DBusAuthServer*)(auth))
238
239 static DBusAuth*
240 _dbus_auth_new (int size)
241 {
242   DBusAuth *auth;
243   
244   auth = dbus_malloc0 (size);
245   if (auth == NULL)
246     return NULL;
247   
248   auth->refcount = 1;
249
250   auth->credentials.pid = -1;
251   auth->credentials.uid = -1;
252   auth->credentials.gid = -1;
253
254   auth->authorized_identity.pid = -1;
255   auth->authorized_identity.uid = -1;
256   auth->authorized_identity.gid = -1;
257   
258   /* note that we don't use the max string length feature,
259    * because you can't use that feature if you're going to
260    * try to recover from out-of-memory (it creates
261    * what looks like unrecoverable inability to alloc
262    * more space in the string). But we do handle
263    * overlong buffers in _dbus_auth_do_work().
264    */
265   
266   if (!_dbus_string_init (&auth->incoming, _DBUS_INT_MAX))
267     {
268       dbus_free (auth);
269       return NULL;
270     }
271
272   if (!_dbus_string_init (&auth->outgoing, _DBUS_INT_MAX))
273     {
274       _dbus_string_free (&auth->incoming);
275       dbus_free (auth);
276       return NULL;
277     }
278   
279   if (!_dbus_string_init (&auth->identity, _DBUS_INT_MAX))
280     {
281       _dbus_string_free (&auth->incoming);
282       _dbus_string_free (&auth->outgoing);
283       dbus_free (auth);
284       return NULL;
285     }
286   
287   return auth;
288 }
289
290 static void
291 shutdown_mech (DBusAuth *auth)
292 {
293   /* Cancel any auth */
294   auth->authenticated_pending_begin = FALSE;
295   auth->authenticated = FALSE;
296   auth->already_asked_for_initial_response = FALSE;
297   _dbus_string_set_length (&auth->identity, 0);
298   auth->authorized_identity.pid = -1;
299   auth->authorized_identity.uid = -1;
300   auth->authorized_identity.gid = -1;
301   
302   if (auth->mech != NULL)
303     {
304       _dbus_verbose ("Shutting down mechanism %s\n",
305                      auth->mech->mechanism);
306       
307       if (DBUS_AUTH_IS_CLIENT (auth))
308         (* auth->mech->client_shutdown_func) (auth);
309       else
310         (* auth->mech->server_shutdown_func) (auth);
311       
312       auth->mech = NULL;
313     }
314 }
315
316 static dbus_bool_t
317 handle_server_data_stupid_test_mech (DBusAuth         *auth,
318                                      const DBusString *data)
319 {
320   if (!_dbus_string_append (&auth->outgoing,
321                             "OK\r\n"))
322     return FALSE;
323
324   auth->authenticated_pending_begin = TRUE;
325   
326   return TRUE;
327 }
328
329 static void
330 handle_server_shutdown_stupid_test_mech (DBusAuth *auth)
331 {
332
333 }
334
335 static dbus_bool_t
336 handle_client_data_stupid_test_mech (DBusAuth         *auth,
337                                      const DBusString *data)
338 {
339   
340   return TRUE;
341 }
342
343 static void
344 handle_client_shutdown_stupid_test_mech (DBusAuth *auth)
345 {
346
347 }
348
349 /* the stupid test mech is a base64-encoded string;
350  * all the inefficiency, none of the security!
351  */
352 static dbus_bool_t
353 handle_encode_stupid_test_mech (DBusAuth         *auth,
354                                 const DBusString *plaintext,
355                                 DBusString       *encoded)
356 {
357   if (!_dbus_string_base64_encode (plaintext, 0, encoded,
358                                    _dbus_string_get_length (encoded)))
359     return FALSE;
360   
361   return TRUE;
362 }
363
364 static dbus_bool_t
365 handle_decode_stupid_test_mech (DBusAuth         *auth,
366                                 const DBusString *encoded,
367                                 DBusString       *plaintext)
368 {
369   if (!_dbus_string_base64_decode (encoded, 0, plaintext,
370                                    _dbus_string_get_length (plaintext)))
371     return FALSE;
372   
373   return TRUE;
374 }
375
376 static dbus_bool_t
377 handle_server_data_external_mech (DBusAuth         *auth,
378                                   const DBusString *data)
379 {
380   DBusCredentials desired_identity;
381
382   if (auth->credentials.uid < 0)
383     {
384       _dbus_verbose ("no credentials, mechanism EXTERNAL can't authenticate\n");
385       return send_rejected (auth);
386     }
387   
388   if (_dbus_string_get_length (data) > 0)
389     {
390       if (_dbus_string_get_length (&auth->identity) > 0)
391         {
392           /* Tried to send two auth identities, wtf */
393           _dbus_verbose ("client tried to send auth identity, but we already have one\n");
394           return send_rejected (auth);
395         }
396       else
397         {
398           /* this is our auth identity */
399           if (!_dbus_string_copy (data, 0, &auth->identity, 0))
400             return FALSE;
401         }
402     }
403
404   /* Poke client for an auth identity, if none given */
405   if (_dbus_string_get_length (&auth->identity) == 0 &&
406       !auth->already_asked_for_initial_response)
407     {
408       if (_dbus_string_append (&auth->outgoing,
409                                "DATA\r\n"))
410         {
411           _dbus_verbose ("sending empty challenge asking client for auth identity\n");
412           auth->already_asked_for_initial_response = TRUE;
413           return TRUE;
414         }
415       else
416         return FALSE;
417     }
418
419   desired_identity.pid = -1;
420   desired_identity.uid = -1;
421   desired_identity.gid = -1;
422   
423   /* If auth->identity is still empty here, then client
424    * responded with an empty string after we poked it for
425    * an initial response. This means to try to auth the
426    * identity provided in the credentials.
427    */
428   if (_dbus_string_get_length (&auth->identity) == 0)
429     {
430       desired_identity.uid = auth->credentials.uid;
431     }
432   else
433     {
434       if (!_dbus_credentials_from_uid_string (&auth->identity,
435                                               &desired_identity))
436         {
437           _dbus_verbose ("could not get credentials from uid string\n");
438           return send_rejected (auth);
439         }
440     }
441
442   if (desired_identity.uid < 0)
443     {
444       _dbus_verbose ("desired UID %d is no good\n", desired_identity.uid);
445       return send_rejected (auth);
446     }
447   
448   if (_dbus_credentials_match (&desired_identity,
449                                &auth->credentials))
450     {
451       /* client has authenticated */
452       _dbus_verbose ("authenticated client with UID %d matching socket credentials UID %d\n",
453                      desired_identity.uid,
454                      auth->credentials.uid);
455       
456       if (!_dbus_string_append (&auth->outgoing,
457                                 "OK\r\n"))
458         return FALSE;
459
460       auth->authorized_identity.uid = desired_identity.uid;
461       
462       auth->authenticated_pending_begin = TRUE;
463       
464       return TRUE;
465     }
466   else
467     {
468       _dbus_verbose ("credentials uid=%d gid=%d do not allow uid=%d gid=%d\n",
469                      auth->credentials.uid, auth->credentials.gid,
470                      desired_identity.uid, desired_identity.gid);
471       return send_rejected (auth);
472     }
473 }
474
475 static void
476 handle_server_shutdown_external_mech (DBusAuth *auth)
477 {
478
479 }
480
481 static dbus_bool_t
482 handle_client_initial_response_external_mech (DBusAuth         *auth,
483                                               DBusString       *response)
484 {
485   /* We always append our UID as an initial response, so the server
486    * doesn't have to send back an empty challenge to check whether we
487    * want to specify an identity. i.e. this avoids a round trip that
488    * the spec for the EXTERNAL mechanism otherwise requires.
489    */
490   DBusString plaintext;
491
492   if (!_dbus_string_init (&plaintext, _DBUS_INT_MAX))
493     return FALSE;
494   
495   if (!_dbus_string_append_our_uid (&plaintext))
496     goto failed;
497
498   if (!_dbus_string_base64_encode (&plaintext, 0,
499                                    response,
500                                    _dbus_string_get_length (response)))
501     goto failed;
502
503   _dbus_string_free (&plaintext);
504   
505   return TRUE;
506
507  failed:
508   _dbus_string_free (&plaintext);
509   return FALSE;  
510 }
511
512 static dbus_bool_t
513 handle_client_data_external_mech (DBusAuth         *auth,
514                                   const DBusString *data)
515 {
516   
517   return TRUE;
518 }
519
520 static void
521 handle_client_shutdown_external_mech (DBusAuth *auth)
522 {
523
524 }
525
526 /* Put mechanisms here in order of preference.
527  * What I eventually want to have is:
528  *
529  *  - a mechanism that checks UNIX domain socket credentials
530  *  - a simple magic cookie mechanism like X11 or ICE
531  *  - mechanisms that chain to Cyrus SASL, so we can use anything it
532  *    offers such as Kerberos, X509, whatever.
533  * 
534  */
535 static const DBusAuthMechanismHandler
536 all_mechanisms[] = {
537   { "EXTERNAL",
538     handle_server_data_external_mech,
539     NULL, NULL,
540     handle_server_shutdown_external_mech,
541     handle_client_initial_response_external_mech,
542     handle_client_data_external_mech,
543     NULL, NULL,
544     handle_client_shutdown_external_mech },
545   /* Obviously this has to die for production use */
546   { "DBUS_STUPID_TEST_MECH",
547     handle_server_data_stupid_test_mech,
548     handle_encode_stupid_test_mech,
549     handle_decode_stupid_test_mech,
550     handle_server_shutdown_stupid_test_mech,
551     NULL,
552     handle_client_data_stupid_test_mech,
553     handle_encode_stupid_test_mech,
554     handle_decode_stupid_test_mech,
555     handle_client_shutdown_stupid_test_mech },
556   { NULL, NULL }
557 };
558
559 static const DBusAuthMechanismHandler*
560 find_mech (const DBusString *name)
561 {
562   int i;
563   
564   i = 0;
565   while (all_mechanisms[i].mechanism != NULL)
566     {
567       if (_dbus_string_equal_c_str (name,
568                                     all_mechanisms[i].mechanism))
569
570         return &all_mechanisms[i];
571       
572       ++i;
573     }
574   
575   return NULL;
576 }
577
578 static dbus_bool_t
579 send_rejected (DBusAuth *auth)
580 {
581   DBusString command;
582   DBusAuthServer *server_auth;
583   int i;
584   
585   if (!_dbus_string_init (&command, _DBUS_INT_MAX))
586     return FALSE;
587   
588   if (!_dbus_string_append (&command,
589                             "REJECTED"))
590     goto nomem;
591
592   i = 0;
593   while (all_mechanisms[i].mechanism != NULL)
594     {
595       if (!_dbus_string_append (&command,
596                                 " "))
597         goto nomem;
598
599       if (!_dbus_string_append (&command,
600                                 all_mechanisms[i].mechanism))
601         goto nomem;
602       
603       ++i;
604     }
605   
606   if (!_dbus_string_append (&command, "\r\n"))
607     goto nomem;
608
609   if (!_dbus_string_copy (&command, 0, &auth->outgoing,
610                           _dbus_string_get_length (&auth->outgoing)))
611     goto nomem;
612
613   _dbus_assert (DBUS_AUTH_IS_SERVER (auth));
614   server_auth = DBUS_AUTH_SERVER (auth);
615   server_auth->failures += 1;
616   
617   return TRUE;
618
619  nomem:
620   _dbus_string_free (&command);
621   return FALSE;
622 }
623
624 static dbus_bool_t
625 process_auth (DBusAuth         *auth,
626               const DBusString *command,
627               const DBusString *args)
628 {
629   if (auth->mech)
630     {
631       /* We are already using a mechanism, client is on crack */
632       if (!_dbus_string_append (&auth->outgoing,
633                                 "ERROR \"Sent AUTH while another AUTH in progress\"\r\n"))
634         return FALSE;
635
636       return TRUE;
637     }
638   else if (_dbus_string_get_length (args) == 0)
639     {
640       /* No args to the auth, send mechanisms */
641       if (!send_rejected (auth))
642         return FALSE;
643
644       return TRUE;
645     }
646   else
647     {
648       int i;
649       DBusString mech;
650       DBusString base64_response;
651       DBusString decoded_response;
652       
653       _dbus_string_find_blank (args, 0, &i);
654
655       if (!_dbus_string_init (&mech, _DBUS_INT_MAX))
656         return FALSE;
657
658       if (!_dbus_string_init (&base64_response, _DBUS_INT_MAX))
659         {
660           _dbus_string_free (&mech);
661           return FALSE;
662         }
663
664       if (!_dbus_string_init (&decoded_response, _DBUS_INT_MAX))
665         {
666           _dbus_string_free (&mech);
667           _dbus_string_free (&base64_response);
668           return FALSE;
669         }
670       
671       if (!_dbus_string_copy_len (args, 0, i, &mech, 0))
672         goto failed;
673
674       if (!_dbus_string_copy (args, i, &base64_response, 0))
675         goto failed;
676
677       if (!_dbus_string_base64_decode (&base64_response, 0,
678                                        &decoded_response, 0))
679         goto failed;
680
681       auth->mech = find_mech (&mech);
682       if (auth->mech != NULL)
683         {
684           _dbus_verbose ("Trying mechanism %s with initial response of %d bytes\n",
685                          auth->mech->mechanism,
686                          _dbus_string_get_length (&decoded_response));
687           
688           if (!(* auth->mech->server_data_func) (auth,
689                                                  &decoded_response))
690             goto failed;
691         }
692       else
693         {
694           /* Unsupported mechanism */
695           if (!send_rejected (auth))
696             return FALSE;
697         }
698
699       _dbus_string_free (&mech);      
700       _dbus_string_free (&base64_response);
701       _dbus_string_free (&decoded_response);
702
703       return TRUE;
704       
705     failed:
706       auth->mech = NULL;
707       _dbus_string_free (&mech);
708       _dbus_string_free (&base64_response);
709       _dbus_string_free (&decoded_response);
710       return FALSE;
711     }
712 }
713
714 static dbus_bool_t
715 process_cancel (DBusAuth         *auth,
716                 const DBusString *command,
717                 const DBusString *args)
718 {
719   shutdown_mech (auth);
720   
721   return TRUE;
722 }
723
724 static dbus_bool_t
725 process_begin (DBusAuth         *auth,
726                const DBusString *command,
727                const DBusString *args)
728 {
729   if (auth->authenticated_pending_begin)
730     auth->authenticated = TRUE;
731   else
732     {
733       auth->need_disconnect = TRUE; /* client trying to send data before auth,
734                                      * kick it
735                                      */
736       shutdown_mech (auth);
737     }
738   
739   return TRUE;
740 }
741
742 static dbus_bool_t
743 process_data_server (DBusAuth         *auth,
744                      const DBusString *command,
745                      const DBusString *args)
746 {
747   if (auth->mech != NULL)
748     {
749       DBusString decoded;
750
751       if (!_dbus_string_init (&decoded, _DBUS_INT_MAX))
752         return FALSE;
753
754       if (!_dbus_string_base64_decode (args, 0, &decoded, 0))
755         {
756           _dbus_string_free (&decoded);
757           return FALSE;
758         }
759       
760       if (!(* auth->mech->server_data_func) (auth, &decoded))
761         {
762           _dbus_string_free (&decoded);
763           return FALSE;
764         }
765
766       _dbus_string_free (&decoded);
767     }
768   else
769     {
770       if (!_dbus_string_append (&auth->outgoing,
771                                 "ERROR \"Not currently in an auth conversation\"\r\n"))
772         return FALSE;
773     }
774   
775   return TRUE;
776 }
777
778 static dbus_bool_t
779 process_error_server (DBusAuth         *auth,
780                       const DBusString *command,
781                       const DBusString *args)
782 {
783   
784   return TRUE;
785 }
786
787 /* return FALSE if no memory, TRUE if all OK */
788 static dbus_bool_t
789 get_word (const DBusString *str,
790           int              *start,
791           DBusString       *word)
792 {
793   int i;
794
795   _dbus_string_skip_blank (str, *start, start);
796   _dbus_string_find_blank (str, *start, &i);
797   
798   if (i > *start)
799     {
800       if (!_dbus_string_copy_len (str, *start, i, word, 0))
801         return FALSE;
802       
803       *start = i;
804     }
805
806   return TRUE;
807 }
808
809 static dbus_bool_t
810 record_mechanisms (DBusAuth         *auth,
811                    const DBusString *command,
812                    const DBusString *args)
813 {
814   int next;
815   int len;
816
817   if (auth->already_got_mechanisms)
818     return TRUE;
819   
820   len = _dbus_string_get_length (args);
821   
822   next = 0;
823   while (next < len)
824     {
825       DBusString m;
826       const DBusAuthMechanismHandler *mech;
827       
828       if (!_dbus_string_init (&m, _DBUS_INT_MAX))
829         goto nomem;
830       
831       if (!get_word (args, &next, &m))
832         goto nomem;
833
834       mech = find_mech (&m);
835
836       if (mech != NULL)
837         {
838           /* FIXME right now we try mechanisms in the order
839            * the server lists them; should we do them in
840            * some more deterministic order?
841            *
842            * Probably in all_mechanisms order, our order of
843            * preference. Of course when the server is us,
844            * it lists things in that order anyhow.
845            */
846
847           _dbus_verbose ("Adding mechanism %s to list we will try\n",
848                          mech->mechanism);
849           
850           if (!_dbus_list_append (& DBUS_AUTH_CLIENT (auth)->mechs_to_try,
851                                   (void*) mech))
852             goto nomem;
853         }
854       else
855         {
856           const char *s;
857
858           _dbus_string_get_const_data (&m, &s);
859           _dbus_verbose ("Server offered mechanism \"%s\" that we don't know how to use\n",
860                          s);
861         }
862
863       _dbus_string_free (&m);
864     }
865   
866   auth->already_got_mechanisms = TRUE;
867   
868   return TRUE;
869
870  nomem:
871   _dbus_list_clear (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
872   
873   return FALSE;
874 }
875
876 static dbus_bool_t
877 client_try_next_mechanism (DBusAuth *auth)
878 {
879   const DBusAuthMechanismHandler *mech;
880   DBusString auth_command;
881
882   if (DBUS_AUTH_CLIENT (auth)->mechs_to_try == NULL)
883     return FALSE;
884
885   mech = DBUS_AUTH_CLIENT (auth)->mechs_to_try->data;
886
887   if (!_dbus_string_init (&auth_command, _DBUS_INT_MAX))
888     return FALSE;
889       
890   if (!_dbus_string_append (&auth_command,
891                             "AUTH "))
892     {
893       _dbus_string_free (&auth_command);
894       return FALSE;
895     }  
896   
897   if (!_dbus_string_append (&auth_command,
898                             mech->mechanism))
899     {
900       _dbus_string_free (&auth_command);
901       return FALSE;
902     }
903
904   if (mech->client_initial_response_func != NULL)
905     {
906       if (!_dbus_string_append (&auth_command, " "))
907         {
908           _dbus_string_free (&auth_command);
909           return FALSE;
910         }
911       
912       if (!(* mech->client_initial_response_func) (auth, &auth_command))
913         {
914           _dbus_string_free (&auth_command);
915           return FALSE;
916         }
917     }
918   
919   if (!_dbus_string_append (&auth_command,
920                             "\r\n"))
921     {
922       _dbus_string_free (&auth_command);
923       return FALSE;
924     }
925
926   if (!_dbus_string_copy (&auth_command, 0,
927                           &auth->outgoing,
928                           _dbus_string_get_length (&auth->outgoing)))
929     {
930       _dbus_string_free (&auth_command);
931       return FALSE;
932     }
933
934   auth->mech = mech;      
935   _dbus_list_pop_first (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
936
937   _dbus_verbose ("Trying mechanism %s\n",
938                  auth->mech->mechanism);
939
940   return TRUE;
941 }
942
943 static dbus_bool_t
944 process_rejected (DBusAuth         *auth,
945                   const DBusString *command,
946                   const DBusString *args)
947 {
948   shutdown_mech (auth);
949   
950   if (!auth->already_got_mechanisms)
951     {
952       if (!record_mechanisms (auth, command, args))
953         return FALSE;
954     }
955   
956   if (DBUS_AUTH_CLIENT (auth)->mechs_to_try != NULL)
957     {
958       client_try_next_mechanism (auth);
959     }
960   else
961     {
962       /* Give up */
963       auth->need_disconnect = TRUE;
964     }
965   
966   return TRUE;
967 }
968
969 static dbus_bool_t
970 process_ok (DBusAuth         *auth,
971             const DBusString *command,
972             const DBusString *args)
973 {
974   if (!_dbus_string_append (&auth->outgoing,
975                             "BEGIN\r\n"))
976     return FALSE;
977   
978   auth->authenticated_pending_output = TRUE;
979   
980   return TRUE;
981 }
982
983
984 static dbus_bool_t
985 process_data_client (DBusAuth         *auth,
986                      const DBusString *command,
987                      const DBusString *args)
988 {
989   if (auth->mech != NULL)
990     {
991       DBusString decoded;
992
993       if (!_dbus_string_init (&decoded, _DBUS_INT_MAX))
994         return FALSE;
995
996       if (!_dbus_string_base64_decode (args, 0, &decoded, 0))
997         {
998           _dbus_string_free (&decoded);
999           return FALSE;
1000         }
1001       
1002       if (!(* auth->mech->client_data_func) (auth, &decoded))
1003         {
1004           _dbus_string_free (&decoded);
1005           return FALSE;
1006         }
1007
1008       _dbus_string_free (&decoded);
1009     }
1010   else
1011     {
1012       if (!_dbus_string_append (&auth->outgoing,
1013                                 "ERROR \"Got DATA when not in an auth exchange\"\r\n"))
1014         return FALSE;
1015     }
1016   
1017   return TRUE;
1018 }
1019
1020 static dbus_bool_t
1021 process_error_client (DBusAuth         *auth,
1022                       const DBusString *command,
1023                       const DBusString *args)
1024 {
1025   return TRUE;
1026 }
1027
1028 static dbus_bool_t
1029 process_unknown (DBusAuth         *auth,
1030                  const DBusString *command,
1031                  const DBusString *args)
1032 {
1033   if (!_dbus_string_append (&auth->outgoing,
1034                             "ERROR \"Unknown command\"\r\n"))
1035     return FALSE;
1036
1037   return TRUE;
1038 }
1039
1040 /* returns whether to call it again right away */
1041 static dbus_bool_t
1042 process_command (DBusAuth *auth)
1043 {
1044   DBusString command;
1045   DBusString args;
1046   int eol;
1047   int i, j;
1048   dbus_bool_t retval;
1049
1050   /* _dbus_verbose ("  trying process_command()\n"); */
1051   
1052   retval = FALSE;
1053   
1054   eol = 0;
1055   if (!_dbus_string_find (&auth->incoming, 0, "\r\n", &eol))
1056     return FALSE;
1057   
1058   if (!_dbus_string_init (&command, _DBUS_INT_MAX))
1059     {
1060       auth->needed_memory = TRUE;
1061       return FALSE;
1062     }
1063
1064   if (!_dbus_string_init (&args, _DBUS_INT_MAX))
1065     {
1066       auth->needed_memory = TRUE;
1067       return FALSE;
1068     }
1069   
1070   if (eol > _DBUS_ONE_MEGABYTE)
1071     {
1072       /* This is a giant line, someone is trying to hose us. */
1073       if (!_dbus_string_append (&auth->outgoing, "ERROR \"Command too long\"\r\n"))
1074         goto out;
1075       else
1076         goto next_command;
1077     }
1078
1079   if (!_dbus_string_copy_len (&auth->incoming, 0, eol, &command, 0))
1080     goto out;
1081
1082   if (!_dbus_string_validate_ascii (&command, 0,
1083                                     _dbus_string_get_length (&command)))
1084     {
1085       _dbus_verbose ("Command contained non-ASCII chars or embedded nul\n");
1086       if (!_dbus_string_append (&auth->outgoing, "ERROR \"Command contained non-ASCII\"\r\n"))
1087         goto out;
1088       else
1089         goto next_command;
1090     }
1091   
1092   {
1093     const char *q;
1094     _dbus_string_get_const_data (&command, &q);
1095     _dbus_verbose ("got command \"%s\"\n", q);
1096   }
1097   
1098   _dbus_string_find_blank (&command, 0, &i);
1099   _dbus_string_skip_blank (&command, i, &j);
1100
1101   if (j > i)
1102     _dbus_string_delete (&command, i, j - i);
1103   
1104   if (!_dbus_string_move (&command, i, &args, 0))
1105     goto out;
1106   
1107   i = 0;
1108   while (auth->handlers[i].command != NULL)
1109     {
1110       if (_dbus_string_equal_c_str (&command,
1111                                     auth->handlers[i].command))
1112         {
1113           _dbus_verbose ("Processing auth command %s\n",
1114                          auth->handlers[i].command);
1115           
1116           if (!(* auth->handlers[i].func) (auth, &command, &args))
1117             goto out;
1118
1119           break;
1120         }
1121       ++i;
1122     }
1123
1124   if (auth->handlers[i].command == NULL)
1125     {
1126       if (!process_unknown (auth, &command, &args))
1127         goto out;
1128     }
1129
1130  next_command:
1131   
1132   /* We've succeeded in processing the whole command so drop it out
1133    * of the incoming buffer and return TRUE to try another command.
1134    */
1135
1136   _dbus_string_delete (&auth->incoming, 0, eol);
1137   
1138   /* kill the \r\n */
1139   _dbus_string_delete (&auth->incoming, 0, 2);
1140
1141   retval = TRUE;
1142   
1143  out:
1144   _dbus_string_free (&args);
1145   _dbus_string_free (&command);
1146
1147   if (!retval)
1148     auth->needed_memory = TRUE;
1149   else
1150     auth->needed_memory = FALSE;
1151   
1152   return retval;
1153 }
1154
1155
1156 /** @} */
1157
1158 /**
1159  * @addtogroup DBusAuth
1160  * @{
1161  */
1162
1163 /**
1164  * Creates a new auth conversation object for the server side.
1165  * See doc/dbus-sasl-profile.txt for full details on what
1166  * this object does.
1167  *
1168  * @returns the new object or #NULL if no memory
1169  */
1170 DBusAuth*
1171 _dbus_auth_server_new (void)
1172 {
1173   DBusAuth *auth;
1174   DBusAuthServer *server_auth;
1175
1176   auth = _dbus_auth_new (sizeof (DBusAuthServer));
1177   if (auth == NULL)
1178     return NULL;
1179
1180   auth->handlers = server_handlers;
1181
1182   server_auth = DBUS_AUTH_SERVER (auth);
1183
1184   /* perhaps this should be per-mechanism with a lower
1185    * max
1186    */
1187   server_auth->failures = 0;
1188   server_auth->max_failures = 6;
1189   
1190   return auth;
1191 }
1192
1193 /**
1194  * Creates a new auth conversation object for the client side.
1195  * See doc/dbus-sasl-profile.txt for full details on what
1196  * this object does.
1197  *
1198  * @returns the new object or #NULL if no memory
1199  */
1200 DBusAuth*
1201 _dbus_auth_client_new (void)
1202 {
1203   DBusAuth *auth;
1204
1205   auth = _dbus_auth_new (sizeof (DBusAuthClient));
1206   if (auth == NULL)
1207     return NULL;
1208
1209   auth->handlers = client_handlers;
1210
1211   /* Add a default mechanism to try */
1212   if (!_dbus_list_append (& DBUS_AUTH_CLIENT (auth)->mechs_to_try,
1213                           (void*) &all_mechanisms[0]))
1214     {
1215       _dbus_auth_unref (auth);
1216       return NULL;
1217     }
1218
1219   /* Now try the mechanism we just added */
1220   if (!client_try_next_mechanism (auth))
1221     {
1222       _dbus_auth_unref (auth);
1223       return NULL;
1224     }
1225   
1226   return auth;
1227 }
1228
1229 /**
1230  * Increments the refcount of an auth object.
1231  *
1232  * @param auth the auth conversation
1233  */
1234 void
1235 _dbus_auth_ref (DBusAuth *auth)
1236 {
1237   _dbus_assert (auth != NULL);
1238   
1239   auth->refcount += 1;
1240 }
1241
1242 /**
1243  * Decrements the refcount of an auth object.
1244  *
1245  * @param auth the auth conversation
1246  */
1247 void
1248 _dbus_auth_unref (DBusAuth *auth)
1249 {
1250   _dbus_assert (auth != NULL);
1251   _dbus_assert (auth->refcount > 0);
1252
1253   auth->refcount -= 1;
1254   if (auth->refcount == 0)
1255     {
1256       shutdown_mech (auth);
1257
1258       if (DBUS_AUTH_IS_CLIENT (auth))
1259         {
1260           _dbus_list_clear (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
1261         }
1262
1263       _dbus_string_free (&auth->identity);
1264       _dbus_string_free (&auth->incoming);
1265       _dbus_string_free (&auth->outgoing);
1266       dbus_free (auth);
1267     }
1268 }
1269
1270 /**
1271  * @param auth the auth conversation object
1272  * @returns #TRUE if we're in a final state
1273  */
1274 #define DBUS_AUTH_IN_END_STATE(auth) ((auth)->need_disconnect || (auth)->authenticated)
1275
1276 /**
1277  * Analyzes buffered input and moves the auth conversation forward,
1278  * returning the new state of the auth conversation.
1279  *
1280  * @param auth the auth conversation
1281  * @returns the new state
1282  */
1283 DBusAuthState
1284 _dbus_auth_do_work (DBusAuth *auth)
1285 {
1286   auth->needed_memory = FALSE;
1287
1288   /* Max amount we'll buffer up before deciding someone's on crack */
1289 #define MAX_BUFFER (16 * _DBUS_ONE_KILOBYTE)
1290
1291   do
1292     {
1293       if (DBUS_AUTH_IN_END_STATE (auth))
1294         break;
1295       
1296       if (_dbus_string_get_length (&auth->incoming) > MAX_BUFFER ||
1297           _dbus_string_get_length (&auth->outgoing) > MAX_BUFFER)
1298         {
1299           auth->need_disconnect = TRUE;
1300           _dbus_verbose ("Disconnecting due to excessive data buffered in auth phase\n");
1301           break;
1302         }
1303
1304       if (auth->mech == NULL &&
1305           auth->already_got_mechanisms &&
1306           DBUS_AUTH_CLIENT (auth)->mechs_to_try == NULL)
1307         {
1308           auth->need_disconnect = TRUE;
1309           _dbus_verbose ("Disconnecting because we are out of mechanisms to try using\n");
1310           break;
1311         }
1312     }
1313   while (process_command (auth));
1314
1315   if (DBUS_AUTH_IS_SERVER (auth) &&
1316       DBUS_AUTH_SERVER (auth)->failures >=
1317       DBUS_AUTH_SERVER (auth)->max_failures)
1318     auth->need_disconnect = TRUE;
1319
1320   if (auth->need_disconnect)
1321     return DBUS_AUTH_STATE_NEED_DISCONNECT;
1322   else if (auth->authenticated)
1323     {
1324       if (_dbus_string_get_length (&auth->incoming) > 0)
1325         return DBUS_AUTH_STATE_AUTHENTICATED_WITH_UNUSED_BYTES;
1326       else
1327         return DBUS_AUTH_STATE_AUTHENTICATED;
1328     }
1329   else if (auth->needed_memory)
1330     return DBUS_AUTH_STATE_WAITING_FOR_MEMORY;
1331   else if (_dbus_string_get_length (&auth->outgoing) > 0)
1332     return DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND;
1333   else
1334     return DBUS_AUTH_STATE_WAITING_FOR_INPUT;
1335 }
1336
1337 /**
1338  * Gets bytes that need to be sent to the peer we're conversing with.
1339  * After writing some bytes, _dbus_auth_bytes_sent() must be called
1340  * to notify the auth object that they were written.
1341  *
1342  * @param auth the auth conversation
1343  * @param str return location for a ref to the buffer to send
1344  * @returns #FALSE if nothing to send
1345  */
1346 dbus_bool_t
1347 _dbus_auth_get_bytes_to_send (DBusAuth          *auth,
1348                               const DBusString **str)
1349 {
1350   _dbus_assert (auth != NULL);
1351   _dbus_assert (str != NULL);
1352
1353   *str = NULL;
1354   
1355   if (DBUS_AUTH_IN_END_STATE (auth))
1356     return FALSE;
1357
1358   if (_dbus_string_get_length (&auth->outgoing) == 0)
1359     return FALSE;
1360
1361   *str = &auth->outgoing;
1362
1363   return TRUE;
1364 }
1365
1366 /**
1367  * Notifies the auth conversation object that
1368  * the given number of bytes of the outgoing buffer
1369  * have been written out.
1370  *
1371  * @param auth the auth conversation
1372  * @param bytes_sent number of bytes written out
1373  */
1374 void
1375 _dbus_auth_bytes_sent (DBusAuth *auth,
1376                        int       bytes_sent)
1377 {
1378   _dbus_string_delete (&auth->outgoing,
1379                        0, bytes_sent);
1380   
1381   if (auth->authenticated_pending_output &&
1382       _dbus_string_get_length (&auth->outgoing) == 0)
1383     auth->authenticated = TRUE;
1384 }
1385
1386 /**
1387  * Stores bytes received from the peer we're conversing with.
1388  *
1389  * @param auth the auth conversation
1390  * @param str the received bytes.
1391  * @returns #FALSE if not enough memory to store the bytes or we were already authenticated.
1392  */
1393 dbus_bool_t
1394 _dbus_auth_bytes_received (DBusAuth   *auth,
1395                            const DBusString *str)
1396 {
1397   _dbus_assert (auth != NULL);
1398   _dbus_assert (str != NULL);
1399   
1400   if (DBUS_AUTH_IN_END_STATE (auth))
1401     return FALSE;
1402
1403   auth->needed_memory = FALSE;
1404   
1405   if (!_dbus_string_copy (str, 0,
1406                           &auth->incoming,
1407                           _dbus_string_get_length (&auth->incoming)))
1408     {
1409       auth->needed_memory = TRUE;
1410       return FALSE;
1411     }
1412
1413   _dbus_auth_do_work (auth);
1414   
1415   return TRUE;
1416 }
1417
1418 /**
1419  * Returns leftover bytes that were not used as part of the auth
1420  * conversation.  These bytes will be part of the message stream
1421  * instead. This function may not be called until authentication has
1422  * succeeded.
1423  *
1424  * @param auth the auth conversation
1425  * @param str string to append the unused bytes to
1426  * @returns #FALSE if not enough memory to return the bytes
1427  */
1428 dbus_bool_t
1429 _dbus_auth_get_unused_bytes (DBusAuth   *auth,
1430                              DBusString *str)
1431 {
1432   if (!DBUS_AUTH_IN_END_STATE (auth))
1433     return FALSE;
1434   
1435   if (!_dbus_string_move (&auth->incoming,
1436                           0, str,
1437                           _dbus_string_get_length (str)))
1438     return FALSE;
1439
1440   return TRUE;
1441 }
1442
1443 /**
1444  * Called post-authentication, indicates whether we need to encode
1445  * the message stream with _dbus_auth_encode_data() prior to
1446  * sending it to the peer.
1447  *
1448  * @param auth the auth conversation
1449  * @returns #TRUE if we need to encode the stream
1450  */
1451 dbus_bool_t
1452 _dbus_auth_needs_encoding (DBusAuth *auth)
1453 {
1454   if (!auth->authenticated)
1455     return FALSE;
1456   
1457   if (auth->mech != NULL)
1458     {
1459       if (DBUS_AUTH_IS_CLIENT (auth))
1460         return auth->mech->client_encode_func != NULL;
1461       else
1462         return auth->mech->server_encode_func != NULL;
1463     }
1464   else
1465     return FALSE;
1466 }
1467
1468 /**
1469  * Called post-authentication, encodes a block of bytes for sending to
1470  * the peer. If no encoding was negotiated, just copies the bytes
1471  * (you can avoid this by checking _dbus_auth_needs_encoding()).
1472  *
1473  * @param auth the auth conversation
1474  * @param plaintext the plain text data
1475  * @param encoded initialized string to where encoded data is appended
1476  * @returns #TRUE if we had enough memory and successfully encoded
1477  */
1478 dbus_bool_t
1479 _dbus_auth_encode_data (DBusAuth         *auth,
1480                         const DBusString *plaintext,
1481                         DBusString       *encoded)
1482 {
1483   _dbus_assert (plaintext != encoded);
1484   
1485   if (!auth->authenticated)
1486     return FALSE;
1487   
1488   if (_dbus_auth_needs_encoding (auth))
1489     {
1490       if (DBUS_AUTH_IS_CLIENT (auth))
1491         return (* auth->mech->client_encode_func) (auth, plaintext, encoded);
1492       else
1493         return (* auth->mech->server_encode_func) (auth, plaintext, encoded);
1494     }
1495   else
1496     {
1497       return _dbus_string_copy (plaintext, 0, encoded,
1498                                 _dbus_string_get_length (encoded));
1499     }
1500 }
1501
1502 /**
1503  * Called post-authentication, indicates whether we need to decode
1504  * the message stream with _dbus_auth_decode_data() after
1505  * receiving it from the peer.
1506  *
1507  * @param auth the auth conversation
1508  * @returns #TRUE if we need to encode the stream
1509  */
1510 dbus_bool_t
1511 _dbus_auth_needs_decoding (DBusAuth *auth)
1512 {
1513   if (!auth->authenticated)
1514     return FALSE;
1515     
1516   if (auth->mech != NULL)
1517     {
1518       if (DBUS_AUTH_IS_CLIENT (auth))
1519         return auth->mech->client_decode_func != NULL;
1520       else
1521         return auth->mech->server_decode_func != NULL;
1522     }
1523   else
1524     return FALSE;
1525 }
1526
1527
1528 /**
1529  * Called post-authentication, decodes a block of bytes received from
1530  * the peer. If no encoding was negotiated, just copies the bytes (you
1531  * can avoid this by checking _dbus_auth_needs_decoding()).
1532  *
1533  * @todo We need to be able to distinguish "out of memory" error
1534  * from "the data is hosed" error.
1535  *
1536  * @param auth the auth conversation
1537  * @param encoded the encoded data
1538  * @param plaintext initialized string where decoded data is appended
1539  * @returns #TRUE if we had enough memory and successfully decoded
1540  */
1541 dbus_bool_t
1542 _dbus_auth_decode_data (DBusAuth         *auth,
1543                         const DBusString *encoded,
1544                         DBusString       *plaintext)
1545 {
1546   _dbus_assert (plaintext != encoded);
1547   
1548   if (!auth->authenticated)
1549     return FALSE;
1550   
1551   if (_dbus_auth_needs_decoding (auth))
1552     {
1553       if (DBUS_AUTH_IS_CLIENT (auth))
1554         return (* auth->mech->client_decode_func) (auth, encoded, plaintext);
1555       else
1556         return (* auth->mech->server_decode_func) (auth, encoded, plaintext);
1557     }
1558   else
1559     {
1560       return _dbus_string_copy (encoded, 0, plaintext,
1561                                 _dbus_string_get_length (plaintext));
1562     }
1563 }
1564
1565 /**
1566  * Sets credentials received via reliable means from the operating
1567  * system.
1568  *
1569  * @param auth the auth conversation
1570  * @param credentials the credentials received
1571  */
1572 void
1573 _dbus_auth_set_credentials (DBusAuth               *auth,
1574                             const DBusCredentials  *credentials)
1575 {
1576   auth->credentials = *credentials;
1577 }
1578
1579 /**
1580  * Gets the identity we authorized the client as.  Apps may have
1581  * different policies as to what identities they allow.
1582  *
1583  * @param auth the auth conversation
1584  * @param credentials the credentials we've authorized
1585  */
1586 void
1587 _dbus_auth_get_identity (DBusAuth               *auth,
1588                          DBusCredentials        *credentials)
1589 {
1590   if (auth->authenticated)
1591     {
1592       *credentials = auth->authorized_identity;
1593     }
1594   else
1595     {
1596       credentials->pid = -1;
1597       credentials->uid = -1;
1598       credentials->gid = -1;
1599     }
1600 }
1601
1602 /** @} */
1603
1604 #ifdef DBUS_BUILD_TESTS
1605 #include "dbus-test.h"
1606 #include "dbus-auth-script.h"
1607 #include <stdio.h>
1608
1609 static dbus_bool_t
1610 process_test_subdir (const DBusString          *test_base_dir,
1611                      const char                *subdir)
1612 {
1613   DBusString test_directory;
1614   DBusString filename;
1615   DBusDirIter *dir;
1616   dbus_bool_t retval;
1617   DBusResultCode result;
1618
1619   retval = FALSE;
1620   dir = NULL;
1621   
1622   if (!_dbus_string_init (&test_directory, _DBUS_INT_MAX))
1623     _dbus_assert_not_reached ("didn't allocate test_directory\n");
1624
1625   _dbus_string_init_const (&filename, subdir);
1626   
1627   if (!_dbus_string_copy (test_base_dir, 0,
1628                           &test_directory, 0))
1629     _dbus_assert_not_reached ("couldn't copy test_base_dir to test_directory");
1630   
1631   if (!_dbus_concat_dir_and_file (&test_directory, &filename))    
1632     _dbus_assert_not_reached ("couldn't allocate full path");
1633
1634   _dbus_string_free (&filename);
1635   if (!_dbus_string_init (&filename, _DBUS_INT_MAX))
1636     _dbus_assert_not_reached ("didn't allocate filename string\n");
1637   
1638   dir = _dbus_directory_open (&test_directory, &result);
1639   if (dir == NULL)
1640     {
1641       const char *s;
1642       _dbus_string_get_const_data (&test_directory, &s);
1643       _dbus_warn ("Could not open %s: %s\n", s,
1644                   dbus_result_to_string (result));
1645       goto failed;
1646     }
1647
1648   printf ("Testing:\n");
1649   
1650   result = DBUS_RESULT_SUCCESS;
1651  next:
1652   while (_dbus_directory_get_next_file (dir, &filename, &result))
1653     {
1654       DBusString full_path;
1655       
1656       if (!_dbus_string_init (&full_path, _DBUS_INT_MAX))
1657         _dbus_assert_not_reached ("couldn't init string");
1658
1659       if (!_dbus_string_copy (&test_directory, 0, &full_path, 0))
1660         _dbus_assert_not_reached ("couldn't copy dir to full_path");
1661
1662       if (!_dbus_concat_dir_and_file (&full_path, &filename))
1663         _dbus_assert_not_reached ("couldn't concat file to dir");
1664
1665       if (!_dbus_string_ends_with_c_str (&filename, ".auth-script"))
1666         {
1667           const char *filename_c;
1668           _dbus_string_get_const_data (&filename, &filename_c);
1669           _dbus_verbose ("Skipping non-.auth-script file %s\n",
1670                          filename_c);
1671           goto next;
1672         }
1673
1674       {
1675         const char *s;
1676         _dbus_string_get_const_data (&filename, &s);
1677         printf ("    %s\n", s);
1678       }
1679       
1680       if (!_dbus_auth_script_run (&full_path))
1681         {
1682           _dbus_string_free (&full_path);
1683           goto failed;
1684         }
1685       else
1686         _dbus_string_free (&full_path);
1687     }
1688
1689   if (result != DBUS_RESULT_SUCCESS)
1690     {
1691       const char *s;
1692       _dbus_string_get_const_data (&test_directory, &s);
1693       _dbus_warn ("Could not get next file in %s: %s\n",
1694                   s, dbus_result_to_string (result));
1695       goto failed;
1696     }
1697     
1698   retval = TRUE;
1699   
1700  failed:
1701
1702   if (dir)
1703     _dbus_directory_close (dir);
1704   _dbus_string_free (&test_directory);
1705   _dbus_string_free (&filename);
1706
1707   return retval;
1708 }
1709
1710 static dbus_bool_t
1711 process_test_dirs (const char *test_data_dir)
1712 {
1713   DBusString test_directory;
1714   dbus_bool_t retval;
1715
1716   retval = FALSE;
1717   
1718   _dbus_string_init_const (&test_directory, test_data_dir);
1719
1720   if (!process_test_subdir (&test_directory, "auth"))
1721     goto failed;
1722
1723   retval = TRUE;
1724   
1725  failed:
1726
1727   _dbus_string_free (&test_directory);
1728   
1729   return retval;
1730 }
1731
1732 dbus_bool_t
1733 _dbus_auth_test (const char *test_data_dir)
1734 {
1735   
1736   if (test_data_dir == NULL)
1737     return TRUE;
1738   
1739   if (!process_test_dirs (test_data_dir))
1740     return FALSE;
1741
1742   return TRUE;
1743 }
1744
1745 #endif /* DBUS_BUILD_TESTS */