s3:libads: call gensec_set_channel_bindings() for tls connections
[metze/samba/wip.git] / source3 / libads / sasl.c
1 /*
2    Unix SMB/CIFS implementation.
3    ads sasl code
4    Copyright (C) Andrew Tridgell 2001
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "includes.h"
21 #include "auth/credentials/credentials.h"
22 #include "auth/gensec/gensec.h"
23 #include "auth_generic.h"
24 #include "ads.h"
25 #include "smb_krb5.h"
26 #include "system/gssapi.h"
27 #include "lib/param/loadparm.h"
28 #include "krb5_env.h"
29
30 #ifdef HAVE_LDAP
31
32 static ADS_STATUS ads_sasl_gensec_wrap(struct ads_saslwrap *wrap,
33                                        uint8_t *buf, uint32_t len)
34 {
35         struct gensec_security *gensec_security =
36                 talloc_get_type_abort(wrap->wrap_private_data,
37                 struct gensec_security);
38         NTSTATUS nt_status;
39         DATA_BLOB unwrapped, wrapped;
40         TALLOC_CTX *frame = talloc_stackframe();
41
42         unwrapped = data_blob_const(buf, len);
43
44         nt_status = gensec_wrap(gensec_security, frame, &unwrapped, &wrapped);
45         if (!NT_STATUS_IS_OK(nt_status)) {
46                 TALLOC_FREE(frame);
47                 return ADS_ERROR_NT(nt_status);
48         }
49
50         if ((wrap->out.size - 4) < wrapped.length) {
51                 TALLOC_FREE(frame);
52                 return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
53         }
54
55         /* copy the wrapped blob to the right location */
56         memcpy(wrap->out.buf + 4, wrapped.data, wrapped.length);
57
58         /* set how many bytes must be written to the underlying socket */
59         wrap->out.left = 4 + wrapped.length;
60
61         TALLOC_FREE(frame);
62
63         return ADS_SUCCESS;
64 }
65
66 static ADS_STATUS ads_sasl_gensec_unwrap(struct ads_saslwrap *wrap)
67 {
68         struct gensec_security *gensec_security =
69                 talloc_get_type_abort(wrap->wrap_private_data,
70                 struct gensec_security);
71         NTSTATUS nt_status;
72         DATA_BLOB unwrapped, wrapped;
73         TALLOC_CTX *frame = talloc_stackframe();
74
75         wrapped = data_blob_const(wrap->in.buf + 4, wrap->in.ofs - 4);
76
77         nt_status = gensec_unwrap(gensec_security, frame, &wrapped, &unwrapped);
78         if (!NT_STATUS_IS_OK(nt_status)) {
79                 TALLOC_FREE(frame);
80                 return ADS_ERROR_NT(nt_status);
81         }
82
83         if (wrapped.length < unwrapped.length) {
84                 TALLOC_FREE(frame);
85                 return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
86         }
87
88         /* copy the wrapped blob to the right location */
89         memcpy(wrap->in.buf + 4, unwrapped.data, unwrapped.length);
90
91         /* set how many bytes must be written to the underlying socket */
92         wrap->in.left   = unwrapped.length;
93         wrap->in.ofs    = 4;
94
95         TALLOC_FREE(frame);
96
97         return ADS_SUCCESS;
98 }
99
100 static void ads_sasl_gensec_disconnect(struct ads_saslwrap *wrap)
101 {
102         struct gensec_security *gensec_security =
103                 talloc_get_type_abort(wrap->wrap_private_data,
104                 struct gensec_security);
105
106         TALLOC_FREE(gensec_security);
107
108         wrap->wrap_ops = NULL;
109         wrap->wrap_private_data = NULL;
110 }
111
112 static const struct ads_saslwrap_ops ads_sasl_gensec_ops = {
113         .name           = "gensec",
114         .wrap           = ads_sasl_gensec_wrap,
115         .unwrap         = ads_sasl_gensec_unwrap,
116         .disconnect     = ads_sasl_gensec_disconnect
117 };
118
119 /*
120    perform a LDAP/SASL/SPNEGO/{NTLMSSP,KRB5} bind (just how many layers can
121    we fit on one socket??)
122 */
123 static ADS_STATUS ads_sasl_spnego_gensec_bind(ADS_STRUCT *ads,
124                                 enum credentials_use_kerberos krb5_state,
125                                 const char *target_service,
126                                 const char *target_hostname)
127 {
128         DATA_BLOB blob_in = data_blob_null;
129         DATA_BLOB blob_out = data_blob_null;
130         int rc;
131         NTSTATUS nt_status;
132         ADS_STATUS status;
133         struct auth_generic_state *auth_generic_state;
134         const char *sasl = "GSS-SPNEGO";
135         const char *sasl_list[] = { sasl, NULL };
136         NTTIME end_nt_time;
137         struct ads_saslwrap *wrap = &ads->ldap_wrap_data;
138         const DATA_BLOB *tls_cb = NULL;
139
140         nt_status = auth_generic_client_prepare(NULL, &auth_generic_state);
141         if (!NT_STATUS_IS_OK(nt_status)) {
142                 return ADS_ERROR_NT(nt_status);
143         }
144
145         if (!NT_STATUS_IS_OK(nt_status = auth_generic_set_username(auth_generic_state, ads->auth.user_name))) {
146                 return ADS_ERROR_NT(nt_status);
147         }
148         if (!NT_STATUS_IS_OK(nt_status = auth_generic_set_domain(auth_generic_state, ads->auth.realm))) {
149                 return ADS_ERROR_NT(nt_status);
150         }
151         if (!NT_STATUS_IS_OK(nt_status = auth_generic_set_password(auth_generic_state, ads->auth.password))) {
152                 return ADS_ERROR_NT(nt_status);
153         }
154
155         cli_credentials_set_kerberos_state(auth_generic_state->credentials,
156                                            krb5_state,
157                                            CRED_SPECIFIED);
158
159         if (target_service != NULL) {
160                 nt_status = gensec_set_target_service(
161                                         auth_generic_state->gensec_security,
162                                         target_service);
163                 if (!NT_STATUS_IS_OK(nt_status)) {
164                         return ADS_ERROR_NT(nt_status);
165                 }
166         }
167
168         if (target_hostname != NULL) {
169                 nt_status = gensec_set_target_hostname(
170                                         auth_generic_state->gensec_security,
171                                         target_hostname);
172                 if (!NT_STATUS_IS_OK(nt_status)) {
173                         return ADS_ERROR_NT(nt_status);
174                 }
175         }
176
177         tls_cb = ads_tls_channel_bindings(&ads->ldap_tls_data);
178         if (tls_cb != NULL) {
179                 uint32_t initiator_addrtype = 0;
180                 const DATA_BLOB *initiator_address = NULL;
181                 uint32_t acceptor_addrtype = 0;
182                 const DATA_BLOB *acceptor_address = NULL;
183                 const DATA_BLOB *application_data = tls_cb;
184
185                 nt_status = gensec_set_channel_bindings(auth_generic_state->gensec_security,
186                                                         initiator_addrtype,
187                                                         initiator_address,
188                                                         acceptor_addrtype,
189                                                         acceptor_address,
190                                                         application_data);
191                 if (!NT_STATUS_IS_OK(nt_status)) {
192                         DBG_WARNING("Failed to set GENSEC channel bindings: %s\n",
193                                     nt_errstr(nt_status));
194                         return ADS_ERROR_NT(nt_status);
195                 }
196
197                 wrap->wrap_type = ADS_SASLWRAP_TYPE_PLAIN;
198         }
199
200         switch (wrap->wrap_type) {
201         case ADS_SASLWRAP_TYPE_SEAL:
202                 gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SIGN);
203                 gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SEAL);
204                 break;
205         case ADS_SASLWRAP_TYPE_SIGN:
206                 if (ads->auth.flags & ADS_AUTH_SASL_FORCE) {
207                         gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SIGN);
208                 } else {
209                         /*
210                          * windows servers are broken with sign only,
211                          * so we let the NTLMSSP backend to seal here,
212                          * via GENSEC_FEATURE_LDAP_STYLE.
213                          */
214                         gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SIGN);
215                         gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_LDAP_STYLE);
216                 }
217                 break;
218         case ADS_SASLWRAP_TYPE_PLAIN:
219                 break;
220         }
221
222         nt_status = auth_generic_client_start_by_sasl(auth_generic_state,
223                                                       sasl_list);
224         if (!NT_STATUS_IS_OK(nt_status)) {
225                 return ADS_ERROR_NT(nt_status);
226         }
227
228         rc = LDAP_SASL_BIND_IN_PROGRESS;
229         blob_in = data_blob_null;
230         blob_out = data_blob_null;
231
232         while (true) {
233                 struct berval cred, *scred = NULL;
234
235                 nt_status = gensec_update(auth_generic_state->gensec_security,
236                                           talloc_tos(), blob_in, &blob_out);
237                 data_blob_free(&blob_in);
238                 if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)
239                     && !NT_STATUS_IS_OK(nt_status))
240                 {
241                         TALLOC_FREE(auth_generic_state);
242                         data_blob_free(&blob_out);
243                         return ADS_ERROR_NT(nt_status);
244                 }
245
246                 if (NT_STATUS_IS_OK(nt_status) && rc == 0 && blob_out.length == 0) {
247                         break;
248                 }
249
250                 cred.bv_val = (char *)blob_out.data;
251                 cred.bv_len = blob_out.length;
252                 scred = NULL;
253                 rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, sasl, &cred, NULL, NULL, &scred);
254                 data_blob_free(&blob_out);
255                 if ((rc != LDAP_SASL_BIND_IN_PROGRESS) && (rc != 0)) {
256                         if (scred) {
257                                 ber_bvfree(scred);
258                         }
259
260                         TALLOC_FREE(auth_generic_state);
261                         return ADS_ERROR(rc);
262                 }
263                 if (scred) {
264                         blob_in = data_blob_talloc(talloc_tos(),
265                                                    scred->bv_val,
266                                                    scred->bv_len);
267                         if (blob_in.length != scred->bv_len) {
268                                 ber_bvfree(scred);
269                                 TALLOC_FREE(auth_generic_state);
270                                 return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
271                         }
272                         ber_bvfree(scred);
273                 } else {
274                         blob_in = data_blob_null;
275                 }
276                 if (NT_STATUS_IS_OK(nt_status) && rc == 0 && blob_in.length == 0) {
277                         break;
278                 }
279         }
280
281         data_blob_free(&blob_in);
282         data_blob_free(&blob_out);
283
284         if (wrap->wrap_type >= ADS_SASLWRAP_TYPE_SEAL) {
285                 bool ok;
286
287                 ok = gensec_have_feature(auth_generic_state->gensec_security,
288                                          GENSEC_FEATURE_SEAL);
289                 if (!ok) {
290                         DEBUG(0,("The gensec feature sealing request, but unavailable\n"));
291                         TALLOC_FREE(auth_generic_state);
292                         return ADS_ERROR_NT(NT_STATUS_INVALID_NETWORK_RESPONSE);
293                 }
294
295                 ok = gensec_have_feature(auth_generic_state->gensec_security,
296                                          GENSEC_FEATURE_SIGN);
297                 if (!ok) {
298                         DEBUG(0,("The gensec feature signing request, but unavailable\n"));
299                         TALLOC_FREE(auth_generic_state);
300                         return ADS_ERROR_NT(NT_STATUS_INVALID_NETWORK_RESPONSE);
301                 }
302
303         } else if (wrap->wrap_type >= ADS_SASLWRAP_TYPE_SIGN) {
304                 bool ok;
305
306                 ok = gensec_have_feature(auth_generic_state->gensec_security,
307                                          GENSEC_FEATURE_SIGN);
308                 if (!ok) {
309                         DEBUG(0,("The gensec feature signing request, but unavailable\n"));
310                         TALLOC_FREE(auth_generic_state);
311                         return ADS_ERROR_NT(NT_STATUS_INVALID_NETWORK_RESPONSE);
312                 }
313         }
314
315         ads->auth.tgs_expire = LONG_MAX;
316         end_nt_time = gensec_expire_time(auth_generic_state->gensec_security);
317         if (end_nt_time != GENSEC_EXPIRE_TIME_INFINITY) {
318                 struct timeval tv;
319                 nttime_to_timeval(&tv, end_nt_time);
320                 ads->auth.tgs_expire = tv.tv_sec;
321         }
322
323         if (wrap->wrap_type > ADS_SASLWRAP_TYPE_PLAIN) {
324                 size_t max_wrapped =
325                         gensec_max_wrapped_size(auth_generic_state->gensec_security);
326                 wrap->out.max_unwrapped =
327                         gensec_max_input_size(auth_generic_state->gensec_security);
328
329                 wrap->out.sig_size = max_wrapped - wrap->out.max_unwrapped;
330                 /*
331                  * Note that we have to truncate this to 0x2C
332                  * (taken from a capture with LDAP unbind), as the
333                  * signature size is not constant for Kerberos with
334                  * arcfour-hmac-md5.
335                  */
336                 wrap->in.min_wrapped = MIN(wrap->out.sig_size, 0x2C);
337                 wrap->in.max_wrapped = ADS_SASL_WRAPPING_IN_MAX_WRAPPED;
338                 status = ads_setup_sasl_wrapping(wrap, ads->ldap.ld,
339                                                  &ads_sasl_gensec_ops,
340                                                  auth_generic_state->gensec_security);
341                 if (!ADS_ERR_OK(status)) {
342                         DEBUG(0, ("ads_setup_sasl_wrapping() failed: %s\n",
343                                 ads_errstr(status)));
344                         TALLOC_FREE(auth_generic_state);
345                         return status;
346                 }
347                 /* Only keep the gensec_security element around long-term */
348                 talloc_steal(NULL, auth_generic_state->gensec_security);
349         }
350         TALLOC_FREE(auth_generic_state);
351
352         return ADS_ERROR(rc);
353 }
354
355 #ifdef HAVE_KRB5
356 struct ads_service_principal {
357         char *service;
358         char *hostname;
359         char *string;
360 };
361
362 static void ads_free_service_principal(struct ads_service_principal *p)
363 {
364         SAFE_FREE(p->service);
365         SAFE_FREE(p->hostname);
366         SAFE_FREE(p->string);
367         ZERO_STRUCTP(p);
368 }
369
370 static ADS_STATUS ads_guess_target(ADS_STRUCT *ads,
371                                    char **service,
372                                    char **hostname,
373                                    char **principal)
374 {
375         ADS_STATUS status = ADS_ERROR(LDAP_NO_MEMORY);
376         char *princ = NULL;
377         TALLOC_CTX *frame;
378         char *server = NULL;
379         char *realm = NULL;
380         int rc;
381
382         frame = talloc_stackframe();
383         if (frame == NULL) {
384                 return ADS_ERROR(LDAP_NO_MEMORY);
385         }
386
387         if (ads->server.realm && ads->server.ldap_server) {
388                 server = strlower_talloc(frame, ads->server.ldap_server);
389                 if (server == NULL) {
390                         goto out;
391                 }
392
393                 realm = strupper_talloc(frame, ads->server.realm);
394                 if (realm == NULL) {
395                         goto out;
396                 }
397
398                 /*
399                  * If we got a name which is bigger than a NetBIOS name,
400                  * but isn't a FQDN, create one.
401                  */
402                 if (strlen(server) > 15 && strstr(server, ".") == NULL) {
403                         char *dnsdomain;
404
405                         dnsdomain = strlower_talloc(frame, ads->server.realm);
406                         if (dnsdomain == NULL) {
407                                 goto out;
408                         }
409
410                         server = talloc_asprintf(frame,
411                                                  "%s.%s",
412                                                  server, dnsdomain);
413                         if (server == NULL) {
414                                 goto out;
415                         }
416                 }
417         } else if (ads->config.realm && ads->config.ldap_server_name) {
418                 server = strlower_talloc(frame, ads->config.ldap_server_name);
419                 if (server == NULL) {
420                         goto out;
421                 }
422
423                 realm = strupper_talloc(frame, ads->config.realm);
424                 if (realm == NULL) {
425                         goto out;
426                 }
427
428                 /*
429                  * If we got a name which is bigger than a NetBIOS name,
430                  * but isn't a FQDN, create one.
431                  */
432                 if (strlen(server) > 15 && strstr(server, ".") == NULL) {
433                         char *dnsdomain;
434
435                         dnsdomain = strlower_talloc(frame, ads->server.realm);
436                         if (dnsdomain == NULL) {
437                                 goto out;
438                         }
439
440                         server = talloc_asprintf(frame,
441                                                  "%s.%s",
442                                                  server, dnsdomain);
443                         if (server == NULL) {
444                                 goto out;
445                         }
446                 }
447         }
448
449         if (server == NULL || realm == NULL) {
450                 goto out;
451         }
452
453         *service = SMB_STRDUP("ldap");
454         if (*service == NULL) {
455                 status = ADS_ERROR(LDAP_PARAM_ERROR);
456                 goto out;
457         }
458         *hostname = SMB_STRDUP(server);
459         if (*hostname == NULL) {
460                 SAFE_FREE(*service);
461                 status = ADS_ERROR(LDAP_PARAM_ERROR);
462                 goto out;
463         }
464         rc = asprintf(&princ, "ldap/%s@%s", server, realm);
465         if (rc == -1 || princ == NULL) {
466                 SAFE_FREE(*service);
467                 SAFE_FREE(*hostname);
468                 status = ADS_ERROR(LDAP_PARAM_ERROR);
469                 goto out;
470         }
471
472         *principal = princ;
473
474         status = ADS_SUCCESS;
475 out:
476         TALLOC_FREE(frame);
477         return status;
478 }
479
480 static ADS_STATUS ads_generate_service_principal(ADS_STRUCT *ads,
481                                                  struct ads_service_principal *p)
482 {
483         ADS_STATUS status;
484
485         ZERO_STRUCTP(p);
486
487         status = ads_guess_target(ads,
488                                   &p->service,
489                                   &p->hostname,
490                                   &p->string);
491         if (!ADS_ERR_OK(status)) {
492                 return status;
493         }
494
495         return ADS_SUCCESS;
496 }
497
498 #endif /* HAVE_KRB5 */
499
500 /*
501    this performs a SASL/SPNEGO bind
502 */
503 static ADS_STATUS ads_sasl_spnego_bind(ADS_STRUCT *ads)
504 {
505         TALLOC_CTX *frame = talloc_stackframe();
506         struct ads_service_principal p = {0};
507         ADS_STATUS status;
508         const char *mech = NULL;
509
510         status = ads_generate_service_principal(ads, &p);
511         if (!ADS_ERR_OK(status)) {
512                 goto done;
513         }
514
515 #ifdef HAVE_KRB5
516         if (!(ads->auth.flags & ADS_AUTH_DISABLE_KERBEROS) &&
517             !is_ipaddress(p.hostname))
518         {
519                 mech = "KRB5";
520
521                 if (ads->auth.password == NULL ||
522                     ads->auth.password[0] == '\0')
523                 {
524
525                         status = ads_sasl_spnego_gensec_bind(ads,
526                                                              CRED_USE_KERBEROS_REQUIRED,
527                                                              p.service, p.hostname);
528                         if (ADS_ERR_OK(status)) {
529                                 ads_free_service_principal(&p);
530                                 goto done;
531                         }
532
533                         DEBUG(10,("ads_sasl_spnego_gensec_bind(KRB5) failed with: %s, "
534                                   "calling kinit\n", ads_errstr(status)));
535                 }
536
537                 status = ADS_ERROR_KRB5(ads_kinit_password(ads));
538
539                 if (ADS_ERR_OK(status)) {
540                         status = ads_sasl_spnego_gensec_bind(ads,
541                                                         CRED_USE_KERBEROS_REQUIRED,
542                                                         p.service, p.hostname);
543                         if (!ADS_ERR_OK(status)) {
544                                 DBG_ERR("kinit succeeded but "
545                                         "SPNEGO bind with Kerberos failed "
546                                         "for %s/%s - user[%s], realm[%s]: %s\n",
547                                         p.service, p.hostname,
548                                         ads->auth.user_name,
549                                         ads->auth.realm,
550                                         ads_errstr(status));
551                         }
552                 }
553
554                 /* only fallback to NTLMSSP if allowed */
555                 if (ADS_ERR_OK(status) ||
556                     !(ads->auth.flags & ADS_AUTH_ALLOW_NTLMSSP)) {
557                         goto done;
558                 }
559
560                 DBG_WARNING("SASL bind with Kerberos failed "
561                             "for %s/%s - user[%s], realm[%s]: %s, "
562                             "try to fallback to NTLMSSP\n",
563                             p.service, p.hostname,
564                             ads->auth.user_name,
565                             ads->auth.realm,
566                             ads_errstr(status));
567         }
568 #endif
569
570         /* lets do NTLMSSP ... this has the big advantage that we don't need
571            to sync clocks, and we don't rely on special versions of the krb5
572            library for HMAC_MD4 encryption */
573         mech = "NTLMSSP";
574
575         if (!(ads->auth.flags & ADS_AUTH_ALLOW_NTLMSSP)) {
576                 DBG_WARNING("We can't use NTLMSSP, it is not allowed.\n");
577                 status = ADS_ERROR_NT(NT_STATUS_NETWORK_CREDENTIAL_CONFLICT);
578                 goto done;
579         }
580
581         if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED) {
582                 DBG_WARNING("We can't fallback to NTLMSSP, weak crypto is"
583                             " disallowed.\n");
584                 status = ADS_ERROR_NT(NT_STATUS_NETWORK_CREDENTIAL_CONFLICT);
585                 goto done;
586         }
587
588         status = ads_sasl_spnego_gensec_bind(ads,
589                                              CRED_USE_KERBEROS_DISABLED,
590                                              p.service, p.hostname);
591 done:
592         if (!ADS_ERR_OK(status)) {
593                 DEBUG(1,("ads_sasl_spnego_gensec_bind(%s) failed "
594                          "for %s/%s with user[%s] realm=[%s]: %s\n", mech,
595                           p.service, p.hostname,
596                           ads->auth.user_name,
597                           ads->auth.realm,
598                           ads_errstr(status)));
599         }
600         ads_free_service_principal(&p);
601         TALLOC_FREE(frame);
602         return status;
603 }
604
605 ADS_STATUS ads_sasl_bind(ADS_STRUCT *ads)
606 {
607         ADS_STATUS status;
608         struct ads_saslwrap *wrap = &ads->ldap_wrap_data;
609
610         if (ads->auth.flags & ADS_AUTH_SASL_SEAL) {
611                 wrap->wrap_type = ADS_SASLWRAP_TYPE_SEAL;
612         } else if (ads->auth.flags & ADS_AUTH_SASL_SIGN) {
613                 wrap->wrap_type = ADS_SASLWRAP_TYPE_SIGN;
614         } else {
615                 wrap->wrap_type = ADS_SASLWRAP_TYPE_PLAIN;
616         }
617
618 retry:
619         status = ads_sasl_spnego_bind(ads);
620         if (status.error_type == ENUM_ADS_ERROR_LDAP &&
621             status.err.rc == LDAP_STRONG_AUTH_REQUIRED &&
622             wrap->wrap_type == ADS_SASLWRAP_TYPE_PLAIN)
623         {
624                 DEBUG(3,("SASL bin got LDAP_STRONG_AUTH_REQUIRED "
625                          "retrying with signing enabled\n"));
626                 wrap->wrap_type = ADS_SASLWRAP_TYPE_SIGN;
627                 goto retry;
628         }
629         return status;
630 }
631
632 #endif /* HAVE_LDAP */
633