2 Unix SMB/CIFS implementation.
3 kerberos utility library
4 Copyright (C) Andrew Tridgell 2001
5 Copyright (C) Remus Koos 2001
6 Copyright (C) Nalin Dahyabhai <nalin@redhat.com> 2004.
7 Copyright (C) Jeremy Allison 2004.
8 Copyright (C) Gerald Carter 2006.
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 3 of the License, or
13 (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program. If not, see <http://www.gnu.org/licenses/>.
28 #define LIBADS_CCACHE_NAME "MEMORY:libads"
31 we use a prompter to avoid a crash bug in the kerberos libs when
32 dealing with empty passwords
33 this prompter is just a string copy ...
35 static krb5_error_code
36 kerb_prompter(krb5_context ctx
, void *data
,
40 krb5_prompt prompts
[])
42 if (num_prompts
== 0) return 0;
44 memset(prompts
[0].reply
->data
, '\0', prompts
[0].reply
->length
);
45 if (prompts
[0].reply
->length
> 0) {
47 strncpy(prompts
[0].reply
->data
, (const char *)data
,
48 prompts
[0].reply
->length
-1);
49 prompts
[0].reply
->length
= strlen(prompts
[0].reply
->data
);
51 prompts
[0].reply
->length
= 0;
57 static bool smb_krb5_err_io_nstatus(TALLOC_CTX
*mem_ctx
,
58 DATA_BLOB
*edata_blob
,
59 KRB5_EDATA_NTSTATUS
*edata
)
64 if (!mem_ctx
|| !edata_blob
|| !edata
)
67 if (!prs_init(&ps
, edata_blob
->length
, mem_ctx
, UNMARSHALL
))
70 if (!prs_copy_data_in(&ps
, (char *)edata_blob
->data
, edata_blob
->length
))
73 prs_set_offset(&ps
, 0);
75 if (!prs_ntstatus("ntstatus", &ps
, 1, &edata
->ntstatus
))
78 if (!prs_uint32("unknown1", &ps
, 1, &edata
->unknown1
))
81 if (!prs_uint32("unknown2", &ps
, 1, &edata
->unknown2
)) /* only seen 00000001 here */
91 static bool smb_krb5_get_ntstatus_from_krb5_error(krb5_error
*error
,
95 DATA_BLOB unwrapped_edata
;
97 KRB5_EDATA_NTSTATUS parsed_edata
;
99 #ifdef HAVE_E_DATA_POINTER_IN_KRB5_ERROR
100 edata
= data_blob(error
->e_data
->data
, error
->e_data
->length
);
102 edata
= data_blob(error
->e_data
.data
, error
->e_data
.length
);
103 #endif /* HAVE_E_DATA_POINTER_IN_KRB5_ERROR */
106 dump_data(10, edata
.data
, edata
.length
);
107 #endif /* DEVELOPER */
109 mem_ctx
= talloc_init("smb_krb5_get_ntstatus_from_krb5_error");
110 if (mem_ctx
== NULL
) {
111 data_blob_free(&edata
);
115 if (!unwrap_edata_ntstatus(mem_ctx
, &edata
, &unwrapped_edata
)) {
116 data_blob_free(&edata
);
117 TALLOC_FREE(mem_ctx
);
121 data_blob_free(&edata
);
123 if (!smb_krb5_err_io_nstatus(mem_ctx
, &unwrapped_edata
, &parsed_edata
)) {
124 data_blob_free(&unwrapped_edata
);
125 TALLOC_FREE(mem_ctx
);
129 data_blob_free(&unwrapped_edata
);
132 *nt_status
= parsed_edata
.ntstatus
;
135 TALLOC_FREE(mem_ctx
);
140 static bool smb_krb5_get_ntstatus_from_krb5_error_init_creds_opt(krb5_context ctx
,
141 krb5_get_init_creds_opt
*opt
,
145 krb5_error
*error
= NULL
;
147 #ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_GET_ERROR
148 ret
= krb5_get_init_creds_opt_get_error(ctx
, opt
, &error
);
150 DEBUG(1,("krb5_get_init_creds_opt_get_error gave: %s\n",
151 error_message(ret
)));
154 #endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_GET_ERROR */
157 DEBUG(1,("no krb5_error\n"));
161 #ifdef HAVE_E_DATA_POINTER_IN_KRB5_ERROR
162 if (!error
->e_data
) {
164 if (error
->e_data
.data
== NULL
) {
165 #endif /* HAVE_E_DATA_POINTER_IN_KRB5_ERROR */
166 DEBUG(1,("no edata in krb5_error\n"));
167 krb5_free_error(ctx
, error
);
171 ret
= smb_krb5_get_ntstatus_from_krb5_error(error
, nt_status
);
173 krb5_free_error(ctx
, error
);
179 simulate a kinit, putting the tgt in the given cache location. If cache_name == NULL
180 place in default cache location.
183 int kerberos_kinit_password_ext(const char *principal
,
184 const char *password
,
187 time_t *renew_till_time
,
188 const char *cache_name
,
190 bool add_netbios_addr
,
191 time_t renewable_time
,
194 krb5_context ctx
= NULL
;
195 krb5_error_code code
= 0;
196 krb5_ccache cc
= NULL
;
197 krb5_principal me
= NULL
;
199 krb5_get_init_creds_opt
*opt
= NULL
;
200 smb_krb5_addresses
*addr
= NULL
;
202 ZERO_STRUCT(my_creds
);
204 initialize_krb5_error_table();
205 if ((code
= krb5_init_context(&ctx
)))
208 if (time_offset
!= 0) {
209 krb5_set_real_time(ctx
, time(NULL
) + time_offset
, 0);
212 DEBUG(10,("kerberos_kinit_password: as %s using [%s] as ccache and config [%s]\n",
214 cache_name
? cache_name
: krb5_cc_default_name(ctx
),
215 getenv("KRB5_CONFIG")));
217 if ((code
= krb5_cc_resolve(ctx
, cache_name
? cache_name
: krb5_cc_default_name(ctx
), &cc
))) {
221 if ((code
= smb_krb5_parse_name(ctx
, principal
, &me
))) {
225 if ((code
= smb_krb5_get_init_creds_opt_alloc(ctx
, &opt
))) {
229 krb5_get_init_creds_opt_set_renew_life(opt
, renewable_time
);
230 krb5_get_init_creds_opt_set_forwardable(opt
, True
);
233 krb5_get_init_creds_opt_set_tkt_life(opt
, 60);
236 #ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_PAC_REQUEST
238 if ((code
= krb5_get_init_creds_opt_set_pac_request(ctx
, opt
, (krb5_boolean
)request_pac
))) {
243 if (add_netbios_addr
) {
244 if ((code
= smb_krb5_gen_netbios_krb5_address(&addr
))) {
247 krb5_get_init_creds_opt_set_address_list(opt
, addr
->addrs
);
250 if ((code
= krb5_get_init_creds_password(ctx
, &my_creds
, me
, CONST_DISCARD(char *,password
),
251 kerb_prompter
, CONST_DISCARD(char *,password
),
256 if ((code
= krb5_cc_initialize(ctx
, cc
, me
))) {
260 if ((code
= krb5_cc_store_cred(ctx
, cc
, &my_creds
))) {
265 *expire_time
= (time_t) my_creds
.times
.endtime
;
268 if (renew_till_time
) {
269 *renew_till_time
= (time_t) my_creds
.times
.renew_till
;
278 *ntstatus
= NT_STATUS_OK
;
282 /* try to get ntstatus code out of krb5_error when we have it
283 * inside the krb5_get_init_creds_opt - gd */
285 if (opt
&& smb_krb5_get_ntstatus_from_krb5_error_init_creds_opt(ctx
, opt
, &status
)) {
290 /* fall back to self-made-mapping */
291 *ntstatus
= krb5_to_nt_status(code
);
295 krb5_free_cred_contents(ctx
, &my_creds
);
297 krb5_free_principal(ctx
, me
);
300 smb_krb5_free_addresses(ctx
, addr
);
303 smb_krb5_get_init_creds_opt_free(ctx
, opt
);
306 krb5_cc_close(ctx
, cc
);
309 krb5_free_context(ctx
);
316 /* run kinit to setup our ccache */
317 int ads_kinit_password(ADS_STRUCT
*ads
)
321 const char *account_name
;
325 /* this will end up getting a ticket for DOMAIN@RUSTED.REA.LM */
326 account_name
= lp_workgroup();
328 /* always use the sAMAccountName for security = domain */
329 /* global_myname()$@REA.LM */
330 if ( lp_security() == SEC_DOMAIN
) {
331 fstr_sprintf( acct_name
, "%s$", global_myname() );
332 account_name
= acct_name
;
335 /* This looks like host/global_myname()@REA.LM */
336 account_name
= ads
->auth
.user_name
;
339 if (asprintf(&s
, "%s@%s", account_name
, ads
->auth
.realm
) == -1) {
340 return KRB5_CC_NOMEM
;
343 if (!ads
->auth
.password
) {
345 return KRB5_LIBOS_CANTREADPWD
;
348 ret
= kerberos_kinit_password_ext(s
, ads
->auth
.password
, ads
->auth
.time_offset
,
349 &ads
->auth
.tgt_expire
, NULL
, NULL
, False
, False
, ads
->auth
.renewable
,
353 DEBUG(0,("kerberos_kinit_password %s failed: %s\n",
354 s
, error_message(ret
)));
360 int ads_kdestroy(const char *cc_name
)
362 krb5_error_code code
;
363 krb5_context ctx
= NULL
;
364 krb5_ccache cc
= NULL
;
366 initialize_krb5_error_table();
367 if ((code
= krb5_init_context (&ctx
))) {
368 DEBUG(3, ("ads_kdestroy: kdb5_init_context failed: %s\n",
369 error_message(code
)));
374 if ((code
= krb5_cc_default(ctx
, &cc
))) {
375 krb5_free_context(ctx
);
379 if ((code
= krb5_cc_resolve(ctx
, cc_name
, &cc
))) {
380 DEBUG(3, ("ads_kdestroy: krb5_cc_resolve failed: %s\n",
381 error_message(code
)));
382 krb5_free_context(ctx
);
387 if ((code
= krb5_cc_destroy (ctx
, cc
))) {
388 DEBUG(3, ("ads_kdestroy: krb5_cc_destroy failed: %s\n",
389 error_message(code
)));
392 krb5_free_context (ctx
);
396 /************************************************************************
397 Routine to fetch the salting principal for a service. Active
398 Directory may use a non-obvious principal name to generate the salt
399 when it determines the key to use for encrypting tickets for a service,
400 and hopefully we detected that when we joined the domain.
401 ************************************************************************/
403 static char *kerberos_secrets_fetch_salting_principal(const char *service
, int enctype
)
408 asprintf(&key
, "%s/%s/enctype=%d", SECRETS_SALTING_PRINCIPAL
, service
, enctype
);
412 ret
= (char *)secrets_fetch(key
, NULL
);
417 /************************************************************************
418 Return the standard DES salt key
419 ************************************************************************/
421 char* kerberos_standard_des_salt( void )
425 fstr_sprintf( salt
, "host/%s.%s@", global_myname(), lp_realm() );
427 fstrcat( salt
, lp_realm() );
429 return SMB_STRDUP( salt
);
432 /************************************************************************
433 ************************************************************************/
435 static char* des_salt_key( void )
439 asprintf(&key
, "%s/DES/%s", SECRETS_SALTING_PRINCIPAL
, lp_realm());
444 /************************************************************************
445 ************************************************************************/
447 bool kerberos_secrets_store_des_salt( const char* salt
)
452 if ( (key
= des_salt_key()) == NULL
) {
453 DEBUG(0,("kerberos_secrets_store_des_salt: failed to generate key!\n"));
458 DEBUG(8,("kerberos_secrets_store_des_salt: deleting salt\n"));
459 secrets_delete( key
);
463 DEBUG(3,("kerberos_secrets_store_des_salt: Storing salt \"%s\"\n", salt
));
465 ret
= secrets_store( key
, salt
, strlen(salt
)+1 );
472 /************************************************************************
473 ************************************************************************/
475 char* kerberos_secrets_fetch_des_salt( void )
479 if ( (key
= des_salt_key()) == NULL
) {
480 DEBUG(0,("kerberos_secrets_fetch_des_salt: failed to generate key!\n"));
484 salt
= (char*)secrets_fetch( key
, NULL
);
491 /************************************************************************
492 Routine to get the default realm from the kerberos credentials cache.
493 Caller must free if the return value is not NULL.
494 ************************************************************************/
496 char *kerberos_get_default_realm_from_ccache( void )
499 krb5_context ctx
= NULL
;
500 krb5_ccache cc
= NULL
;
501 krb5_principal princ
= NULL
;
503 initialize_krb5_error_table();
504 if (krb5_init_context(&ctx
)) {
508 DEBUG(5,("kerberos_get_default_realm_from_ccache: "
509 "Trying to read krb5 cache: %s\n",
510 krb5_cc_default_name(ctx
)));
511 if (krb5_cc_default(ctx
, &cc
)) {
512 DEBUG(0,("kerberos_get_default_realm_from_ccache: "
513 "failed to read default cache\n"));
516 if (krb5_cc_get_principal(ctx
, cc
, &princ
)) {
517 DEBUG(0,("kerberos_get_default_realm_from_ccache: "
518 "failed to get default principal\n"));
522 #if defined(HAVE_KRB5_PRINCIPAL_GET_REALM)
523 realm
= SMB_STRDUP(krb5_principal_get_realm(ctx
, princ
));
524 #elif defined(HAVE_KRB5_PRINC_REALM)
526 krb5_data
*realm_data
= krb5_princ_realm(ctx
, princ
);
527 realm
= SMB_STRNDUP(realm_data
->data
, realm_data
->length
);
534 krb5_free_principal(ctx
, princ
);
537 krb5_cc_close(ctx
, cc
);
540 krb5_free_context(ctx
);
547 /************************************************************************
548 Routine to get the salting principal for this service. This is
549 maintained for backwards compatibilty with releases prior to 3.0.24.
550 Since we store the salting principal string only at join, we may have
551 to look for the older tdb keys. Caller must free if return is not null.
552 ************************************************************************/
554 krb5_principal
kerberos_fetch_salt_princ_for_host_princ(krb5_context context
,
555 krb5_principal host_princ
,
558 char *unparsed_name
= NULL
, *salt_princ_s
= NULL
;
559 krb5_principal ret_princ
= NULL
;
561 /* lookup new key first */
563 if ( (salt_princ_s
= kerberos_secrets_fetch_des_salt()) == NULL
) {
565 /* look under the old key. If this fails, just use the standard key */
567 if (smb_krb5_unparse_name(context
, host_princ
, &unparsed_name
) != 0) {
568 return (krb5_principal
)NULL
;
570 if ((salt_princ_s
= kerberos_secrets_fetch_salting_principal(unparsed_name
, enctype
)) == NULL
) {
571 /* fall back to host/machine.realm@REALM */
572 salt_princ_s
= kerberos_standard_des_salt();
576 if (smb_krb5_parse_name(context
, salt_princ_s
, &ret_princ
) != 0) {
580 SAFE_FREE(unparsed_name
);
581 SAFE_FREE(salt_princ_s
);
586 /************************************************************************
587 Routine to set the salting principal for this service. Active
588 Directory may use a non-obvious principal name to generate the salt
589 when it determines the key to use for encrypting tickets for a service,
590 and hopefully we detected that when we joined the domain.
591 Setting principal to NULL deletes this entry.
592 ************************************************************************/
594 bool kerberos_secrets_store_salting_principal(const char *service
,
596 const char *principal
)
600 krb5_context context
= NULL
;
601 krb5_principal princ
= NULL
;
602 char *princ_s
= NULL
;
603 char *unparsed_name
= NULL
;
605 krb5_init_context(&context
);
609 if (strchr_m(service
, '@')) {
610 asprintf(&princ_s
, "%s", service
);
612 asprintf(&princ_s
, "%s@%s", service
, lp_realm());
615 if (smb_krb5_parse_name(context
, princ_s
, &princ
) != 0) {
619 if (smb_krb5_unparse_name(context
, princ
, &unparsed_name
) != 0) {
623 asprintf(&key
, "%s/%s/enctype=%d", SECRETS_SALTING_PRINCIPAL
, unparsed_name
, enctype
);
628 if ((principal
!= NULL
) && (strlen(principal
) > 0)) {
629 ret
= secrets_store(key
, principal
, strlen(principal
) + 1);
631 ret
= secrets_delete(key
);
638 SAFE_FREE(unparsed_name
);
641 krb5_free_context(context
);
648 /************************************************************************
649 ************************************************************************/
651 int kerberos_kinit_password(const char *principal
,
652 const char *password
,
654 const char *cache_name
)
656 return kerberos_kinit_password_ext(principal
,
668 /************************************************************************
669 Create a string list of available kdc's, possibly searching by sitename.
671 ************************************************************************/
673 static char *get_kdc_ip_string(char *mem_ctx
,
675 const char *sitename
,
676 struct sockaddr_storage
*pss
)
679 struct ip_service
*ip_srv_site
= NULL
;
680 struct ip_service
*ip_srv_nonsite
;
683 char *kdc_str
= talloc_asprintf(mem_ctx
, "\tkdc = %s\n",
684 print_canonical_sockaddr(mem_ctx
,
687 if (kdc_str
== NULL
) {
691 /* Get the KDC's only in this site. */
695 get_kdc_list(realm
, sitename
, &ip_srv_site
, &count_site
);
697 for (i
= 0; i
< count_site
; i
++) {
698 if (addr_equal(&ip_srv_site
[i
].ss
, pss
)) {
701 /* Append to the string - inefficient
702 * but not done often. */
703 kdc_str
= talloc_asprintf(mem_ctx
, "%s\tkdc = %s\n",
705 print_canonical_sockaddr(mem_ctx
,
706 &ip_srv_site
[i
].ss
));
708 SAFE_FREE(ip_srv_site
);
716 get_kdc_list(realm
, NULL
, &ip_srv_nonsite
, &count_nonsite
);
718 for (i
= 0; i
< count_nonsite
; i
++) {
721 if (addr_equal(&ip_srv_nonsite
[i
].ss
, pss
)) {
725 /* Ensure this isn't an IP already seen (YUK! this is n*n....) */
726 for (j
= 0; j
< count_site
; j
++) {
727 if (addr_equal(&ip_srv_nonsite
[i
].ss
,
728 &ip_srv_site
[j
].ss
)) {
731 /* As the lists are sorted we can break early if nonsite > site. */
732 if (ip_service_compare(&ip_srv_nonsite
[i
], &ip_srv_site
[j
]) > 0) {
740 /* Append to the string - inefficient but not done often. */
741 kdc_str
= talloc_asprintf(mem_ctx
, "%s\tkdc = %s\n",
743 print_canonical_sockaddr(mem_ctx
,
744 &ip_srv_nonsite
[i
].ss
));
746 SAFE_FREE(ip_srv_site
);
747 SAFE_FREE(ip_srv_nonsite
);
753 SAFE_FREE(ip_srv_site
);
754 SAFE_FREE(ip_srv_nonsite
);
756 DEBUG(10,("get_kdc_ip_string: Returning %s\n",
762 /************************************************************************
763 Create a specific krb5.conf file in the private directory pointing
764 at a specific kdc for a realm. Keyed off domain name. Sets
765 KRB5_CONFIG environment variable to point to this file. Must be
766 run as root or will fail (which is a good thing :-).
767 ************************************************************************/
769 bool create_local_private_krb5_conf_for_domain(const char *realm
,
771 const char *sitename
,
772 struct sockaddr_storage
*pss
)
774 char *dname
= talloc_asprintf(NULL
, "%s/smb_krb5", lp_lockdir());
775 char *tmpname
= NULL
;
777 char *file_contents
= NULL
;
778 char *kdc_ip_string
= NULL
;
782 char *realm_upper
= NULL
;
787 if ((mkdir(dname
, 0755)==-1) && (errno
!= EEXIST
)) {
788 DEBUG(0,("create_local_private_krb5_conf_for_domain: "
789 "failed to create directory %s. Error was %s\n",
790 dname
, strerror(errno
) ));
795 tmpname
= talloc_asprintf(dname
, "%s/smb_tmp_krb5.XXXXXX", lp_lockdir());
801 fname
= talloc_asprintf(dname
, "%s/krb5.conf.%s", dname
, domain
);
807 DEBUG(10,("create_local_private_krb5_conf_for_domain: fname = %s, realm = %s, domain = %s\n",
808 fname
, realm
, domain
));
810 realm_upper
= talloc_strdup(fname
, realm
);
811 strupper_m(realm_upper
);
813 kdc_ip_string
= get_kdc_ip_string(dname
, realm
, sitename
, pss
);
814 if (!kdc_ip_string
) {
819 file_contents
= talloc_asprintf(fname
, "[libdefaults]\n\tdefault_realm = %s\n\n"
820 "[realms]\n\t%s = {\n"
822 realm_upper
, realm_upper
, kdc_ip_string
);
824 if (!file_contents
) {
829 flen
= strlen(file_contents
);
831 fd
= smb_mkstemp(tmpname
);
833 DEBUG(0,("create_local_private_krb5_conf_for_domain: smb_mkstemp failed,"
834 " for file %s. Errno %s\n",
835 tmpname
, strerror(errno
) ));
838 if (fchmod(fd
, 0644)==-1) {
839 DEBUG(0,("create_local_private_krb5_conf_for_domain: fchmod failed for %s."
841 tmpname
, strerror(errno
) ));
848 ret
= write(fd
, file_contents
, flen
);
850 DEBUG(0,("create_local_private_krb5_conf_for_domain: write failed,"
851 " returned %d (should be %u). Errno %s\n",
852 (int)ret
, (unsigned int)flen
, strerror(errno
) ));
859 DEBUG(0,("create_local_private_krb5_conf_for_domain: close failed."
860 " Errno %s\n", strerror(errno
) ));
866 if (rename(tmpname
, fname
) == -1) {
867 DEBUG(0,("create_local_private_krb5_conf_for_domain: rename "
868 "of %s to %s failed. Errno %s\n",
869 tmpname
, fname
, strerror(errno
) ));
875 DEBUG(5,("create_local_private_krb5_conf_for_domain: wrote "
876 "file %s with realm %s KDC = %s\n",
877 fname
, realm_upper
, print_canonical_sockaddr(dname
, pss
) ));
879 /* Set the environment variable to this file. */
880 setenv("KRB5_CONFIG", fname
, 1);
882 #if defined(OVERWRITE_SYSTEM_KRB5_CONF)
884 #define SYSTEM_KRB5_CONF_PATH "/etc/krb5.conf"
885 /* Insanity, sheer insanity..... */
887 if (strequal(realm
, lp_realm())) {
888 char linkpath
[PATH_MAX
+1];
891 lret
= readlink(SYSTEM_KRB5_CONF_PATH
, linkpath
, sizeof(linkpath
)-1);
893 linkpath
[lret
] = '\0';
896 if (lret
!= -1 || strcmp(linkpath
, fname
) == 0) {
897 /* Symlink already exists. */
902 /* Try and replace with a symlink. */
903 if (symlink(fname
, SYSTEM_KRB5_CONF_PATH
) == -1) {
904 const char *newpath
= SYSTEM_KRB5_CONF_PATH
## ".saved";
905 if (errno
!= EEXIST
) {
906 DEBUG(0,("create_local_private_krb5_conf_for_domain: symlink "
907 "of %s to %s failed. Errno %s\n",
908 fname
, SYSTEM_KRB5_CONF_PATH
, strerror(errno
) ));
910 return True
; /* Not a fatal error. */
913 /* Yes, this is a race conditon... too bad. */
914 if (rename(SYSTEM_KRB5_CONF_PATH
, newpath
) == -1) {
915 DEBUG(0,("create_local_private_krb5_conf_for_domain: rename "
916 "of %s to %s failed. Errno %s\n",
917 SYSTEM_KRB5_CONF_PATH
, newpath
,
920 return True
; /* Not a fatal error. */
923 if (symlink(fname
, SYSTEM_KRB5_CONF_PATH
) == -1) {
924 DEBUG(0,("create_local_private_krb5_conf_for_domain: "
925 "forced symlink of %s to /etc/krb5.conf failed. Errno %s\n",
926 fname
, strerror(errno
) ));
928 return True
; /* Not a fatal error. */