1 /*
2  * e-source-ldap.c
3  *
4  * This library is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This library is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this library. If not, see <http://www.gnu.org/licenses/>.
15  *
16  */
17 
18 #include "evolution-data-server-config.h"
19 
20 #include "e-source-authentication.h"
21 #include "e-source-security.h"
22 #include "e-source-enumtypes.h"
23 
24 #include "e-source-ldap.h"
25 
26 struct _ESourceLDAPPrivate {
27 	gboolean can_browse;
28 	gchar *filter;
29 	guint limit;
30 	gchar *root_dn;
31 	ESourceLDAPScope scope;
32 
33 	/* These are bound to other extensions. */
34 	ESourceLDAPAuthentication authentication;
35 	ESourceLDAPSecurity security;
36 };
37 
38 enum {
39 	PROP_0,
40 	PROP_AUTHENTICATION,
41 	PROP_CAN_BROWSE,
42 	PROP_FILTER,
43 	PROP_LIMIT,
44 	PROP_ROOT_DN,
45 	PROP_SCOPE,
46 	PROP_SECURITY
47 };
48 
49 G_DEFINE_TYPE_WITH_PRIVATE (
50 	ESourceLDAP,
51 	e_source_ldap,
52 	E_TYPE_SOURCE_EXTENSION)
53 
54 static struct ELDAPAuthMapping {
55 	ESourceLDAPAuthentication value;
56 	const gchar *nick;
57 } ldap_auth_mapping[] = {
58 	{ E_SOURCE_LDAP_AUTHENTICATION_NONE, "none" },
59 	{ E_SOURCE_LDAP_AUTHENTICATION_EMAIL, "ldap/simple-email" },
60 	{ E_SOURCE_LDAP_AUTHENTICATION_BINDDN, "ldap/simple-binddn" }
61 };
62 
63 static gboolean
source_ldap_transform_auth_enum_nick_to_value(GBinding * binding,const GValue * source_value,GValue * target_value,gpointer not_used)64 source_ldap_transform_auth_enum_nick_to_value (GBinding *binding,
65 					       const GValue *source_value,
66 					       GValue *target_value,
67 					       gpointer not_used)
68 {
69 	GEnumClass *enum_class;
70 	const gchar *string;
71 	gboolean success = FALSE;
72 	gint ii;
73 
74 	enum_class = g_type_class_peek (G_VALUE_TYPE (target_value));
75 	g_return_val_if_fail (G_IS_ENUM_CLASS (enum_class), FALSE);
76 
77 	string = g_value_get_string (source_value);
78 
79 	for (ii = 0; ii < G_N_ELEMENTS (ldap_auth_mapping); ii++) {
80 		if (g_strcmp0 (ldap_auth_mapping[ii].nick, string) == 0) {
81 			g_value_set_enum (target_value, ldap_auth_mapping[ii].value);
82 			success = TRUE;
83 			break;
84 		}
85 	}
86 
87 	return success;
88 }
89 
90 static gboolean
source_ldap_transform_auth_enum_value_to_nick(GBinding * binding,const GValue * source_value,GValue * target_value,gpointer not_used)91 source_ldap_transform_auth_enum_value_to_nick (GBinding *binding,
92 					       const GValue *source_value,
93 					       GValue *target_value,
94 					       gpointer not_used)
95 {
96 	GEnumClass *enum_class;
97 	gint value;
98 	gboolean success = FALSE;
99 	gint ii;
100 
101 	enum_class = g_type_class_peek (G_VALUE_TYPE (source_value));
102 	g_return_val_if_fail (G_IS_ENUM_CLASS (enum_class), FALSE);
103 
104 	value = g_value_get_enum (source_value);
105 
106 	for (ii = 0; ii < G_N_ELEMENTS (ldap_auth_mapping); ii++) {
107 		if (value == ldap_auth_mapping[ii].value) {
108 			g_value_set_string (target_value, ldap_auth_mapping[ii].nick);
109 			success = TRUE;
110 			break;
111 		}
112 	}
113 
114 	return success;
115 }
116 
117 static gboolean
source_ldap_transform_enum_nick_to_value(GBinding * binding,const GValue * source_value,GValue * target_value,gpointer not_used)118 source_ldap_transform_enum_nick_to_value (GBinding *binding,
119                                           const GValue *source_value,
120                                           GValue *target_value,
121                                           gpointer not_used)
122 {
123 	GEnumClass *enum_class;
124 	GEnumValue *enum_value;
125 	const gchar *string;
126 	gboolean success = FALSE;
127 
128 	enum_class = g_type_class_peek (G_VALUE_TYPE (target_value));
129 	g_return_val_if_fail (G_IS_ENUM_CLASS (enum_class), FALSE);
130 
131 	string = g_value_get_string (source_value);
132 	enum_value = g_enum_get_value_by_nick (enum_class, string);
133 	if (enum_value != NULL) {
134 		g_value_set_enum (target_value, enum_value->value);
135 		success = TRUE;
136 	}
137 
138 	return success;
139 }
140 
141 static gboolean
source_ldap_transform_enum_value_to_nick(GBinding * binding,const GValue * source_value,GValue * target_value,gpointer not_used)142 source_ldap_transform_enum_value_to_nick (GBinding *binding,
143                                           const GValue *source_value,
144                                           GValue *target_value,
145                                           gpointer not_used)
146 {
147 	GEnumClass *enum_class;
148 	GEnumValue *enum_value;
149 	gint value;
150 	gboolean success = FALSE;
151 
152 	enum_class = g_type_class_peek (G_VALUE_TYPE (source_value));
153 	g_return_val_if_fail (G_IS_ENUM_CLASS (enum_class), FALSE);
154 
155 	value = g_value_get_enum (source_value);
156 	enum_value = g_enum_get_value (enum_class, value);
157 	if (enum_value != NULL) {
158 		g_value_set_string (target_value, enum_value->value_nick);
159 		success = TRUE;
160 	}
161 
162 	return success;
163 }
164 
165 static void
source_ldap_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)166 source_ldap_set_property (GObject *object,
167                           guint property_id,
168                           const GValue *value,
169                           GParamSpec *pspec)
170 {
171 	switch (property_id) {
172 		case PROP_AUTHENTICATION:
173 			e_source_ldap_set_authentication (
174 				E_SOURCE_LDAP (object),
175 				g_value_get_enum (value));
176 			return;
177 
178 		case PROP_CAN_BROWSE:
179 			e_source_ldap_set_can_browse (
180 				E_SOURCE_LDAP (object),
181 				g_value_get_boolean (value));
182 			return;
183 
184 		case PROP_FILTER:
185 			e_source_ldap_set_filter (
186 				E_SOURCE_LDAP (object),
187 				g_value_get_string (value));
188 			return;
189 
190 		case PROP_LIMIT:
191 			e_source_ldap_set_limit (
192 				E_SOURCE_LDAP (object),
193 				g_value_get_uint (value));
194 			return;
195 
196 		case PROP_ROOT_DN:
197 			e_source_ldap_set_root_dn (
198 				E_SOURCE_LDAP (object),
199 				g_value_get_string (value));
200 			return;
201 
202 		case PROP_SCOPE:
203 			e_source_ldap_set_scope (
204 				E_SOURCE_LDAP (object),
205 				g_value_get_enum (value));
206 			return;
207 
208 		case PROP_SECURITY:
209 			e_source_ldap_set_security (
210 				E_SOURCE_LDAP (object),
211 				g_value_get_enum (value));
212 			return;
213 	}
214 
215 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
216 }
217 
218 static void
source_ldap_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)219 source_ldap_get_property (GObject *object,
220                           guint property_id,
221                           GValue *value,
222                           GParamSpec *pspec)
223 {
224 	switch (property_id) {
225 		case PROP_AUTHENTICATION:
226 			g_value_set_enum (
227 				value,
228 				e_source_ldap_get_authentication (
229 				E_SOURCE_LDAP (object)));
230 			return;
231 
232 		case PROP_CAN_BROWSE:
233 			g_value_set_boolean (
234 				value,
235 				e_source_ldap_get_can_browse (
236 				E_SOURCE_LDAP (object)));
237 			return;
238 
239 		case PROP_FILTER:
240 			g_value_take_string (
241 				value,
242 				e_source_ldap_dup_filter (
243 				E_SOURCE_LDAP (object)));
244 			return;
245 
246 		case PROP_LIMIT:
247 			g_value_set_uint (
248 				value,
249 				e_source_ldap_get_limit (
250 				E_SOURCE_LDAP (object)));
251 			return;
252 
253 		case PROP_ROOT_DN:
254 			g_value_take_string (
255 				value,
256 				e_source_ldap_dup_root_dn (
257 				E_SOURCE_LDAP (object)));
258 			return;
259 
260 		case PROP_SCOPE:
261 			g_value_set_enum (
262 				value,
263 				e_source_ldap_get_scope (
264 				E_SOURCE_LDAP (object)));
265 			return;
266 
267 		case PROP_SECURITY:
268 			g_value_set_enum (
269 				value,
270 				e_source_ldap_get_security (
271 				E_SOURCE_LDAP (object)));
272 			return;
273 	}
274 
275 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
276 }
277 
278 static void
source_ldap_finalize(GObject * object)279 source_ldap_finalize (GObject *object)
280 {
281 	ESourceLDAPPrivate *priv;
282 
283 	priv = E_SOURCE_LDAP (object)->priv;
284 
285 	g_free (priv->filter);
286 	g_free (priv->root_dn);
287 
288 	/* Chain up to parent's finalize() method. */
289 	G_OBJECT_CLASS (e_source_ldap_parent_class)->finalize (object);
290 }
291 
292 static void
source_ldap_constructed(GObject * object)293 source_ldap_constructed (GObject *object)
294 {
295 	ESource *source;
296 	ESourceExtension *this_extension;
297 	ESourceExtension *other_extension;
298 	const gchar *extension_name;
299 
300 	/* Chain up to parent's method. */
301 	G_OBJECT_CLASS (e_source_ldap_parent_class)->constructed (object);
302 
303 	this_extension = E_SOURCE_EXTENSION (object);
304 	source = e_source_extension_ref_source (this_extension);
305 
306 	extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
307 	other_extension = e_source_get_extension (source, extension_name);
308 
309 	e_binding_bind_property_full (
310 		other_extension, "method",
311 		this_extension, "authentication",
312 		G_BINDING_BIDIRECTIONAL |
313 		G_BINDING_SYNC_CREATE,
314 		source_ldap_transform_auth_enum_nick_to_value,
315 		source_ldap_transform_auth_enum_value_to_nick,
316 		NULL, (GDestroyNotify) NULL);
317 
318 	extension_name = E_SOURCE_EXTENSION_SECURITY;
319 	other_extension = e_source_get_extension (source, extension_name);
320 
321 	e_binding_bind_property_full (
322 		other_extension, "method",
323 		this_extension, "security",
324 		G_BINDING_BIDIRECTIONAL |
325 		G_BINDING_SYNC_CREATE,
326 		source_ldap_transform_enum_nick_to_value,
327 		source_ldap_transform_enum_value_to_nick,
328 		NULL, (GDestroyNotify) NULL);
329 
330 	g_object_unref (source);
331 }
332 
333 static void
e_source_ldap_class_init(ESourceLDAPClass * class)334 e_source_ldap_class_init (ESourceLDAPClass *class)
335 {
336 	GObjectClass *object_class;
337 	ESourceExtensionClass *extension_class;
338 
339 	object_class = G_OBJECT_CLASS (class);
340 	object_class->set_property = source_ldap_set_property;
341 	object_class->get_property = source_ldap_get_property;
342 	object_class->finalize = source_ldap_finalize;
343 	object_class->constructed = source_ldap_constructed;
344 
345 	extension_class = E_SOURCE_EXTENSION_CLASS (class);
346 	extension_class->name = E_SOURCE_EXTENSION_LDAP_BACKEND;
347 
348 	/* This is bound to the authentication extension.
349 	 * Do not use E_SOURCE_PARAM_SETTING here. */
350 	g_object_class_install_property (
351 		object_class,
352 		PROP_AUTHENTICATION,
353 		g_param_spec_enum (
354 			"authentication",
355 			"Authentication",
356 			"LDAP authentication method",
357 			E_TYPE_SOURCE_LDAP_AUTHENTICATION,
358 			E_SOURCE_LDAP_AUTHENTICATION_NONE,
359 			G_PARAM_READWRITE |
360 			G_PARAM_EXPLICIT_NOTIFY |
361 			G_PARAM_STATIC_STRINGS));
362 
363 	g_object_class_install_property (
364 		object_class,
365 		PROP_CAN_BROWSE,
366 		g_param_spec_boolean (
367 			"can-browse",
368 			"Can Browse",
369 			"Allow browsing contacts",
370 			FALSE,
371 			G_PARAM_READWRITE |
372 			G_PARAM_CONSTRUCT |
373 			G_PARAM_EXPLICIT_NOTIFY |
374 			G_PARAM_STATIC_STRINGS |
375 			E_SOURCE_PARAM_SETTING));
376 
377 	g_object_class_install_property (
378 		object_class,
379 		PROP_FILTER,
380 		g_param_spec_string (
381 			"filter",
382 			"Filter",
383 			"LDAP search filter",
384 			"",
385 			G_PARAM_READWRITE |
386 			G_PARAM_CONSTRUCT |
387 			G_PARAM_EXPLICIT_NOTIFY |
388 			G_PARAM_STATIC_STRINGS |
389 			E_SOURCE_PARAM_SETTING));
390 
391 	g_object_class_install_property (
392 		object_class,
393 		PROP_LIMIT,
394 		g_param_spec_uint (
395 			"limit",
396 			"Limit",
397 			"Download limit",
398 			0, G_MAXUINT, 100,
399 			G_PARAM_READWRITE |
400 			G_PARAM_CONSTRUCT |
401 			G_PARAM_EXPLICIT_NOTIFY |
402 			G_PARAM_STATIC_STRINGS |
403 			E_SOURCE_PARAM_SETTING));
404 
405 	g_object_class_install_property (
406 		object_class,
407 		PROP_ROOT_DN,
408 		g_param_spec_string (
409 			"root-dn",
410 			"Root DN",
411 			"LDAP search base",
412 			"",
413 			G_PARAM_READWRITE |
414 			G_PARAM_CONSTRUCT |
415 			G_PARAM_EXPLICIT_NOTIFY |
416 			G_PARAM_STATIC_STRINGS |
417 			E_SOURCE_PARAM_SETTING));
418 
419 	g_object_class_install_property (
420 		object_class,
421 		PROP_SCOPE,
422 		g_param_spec_enum (
423 			"scope",
424 			"Scope",
425 			"LDAP search scope",
426 			E_TYPE_SOURCE_LDAP_SCOPE,
427 			E_SOURCE_LDAP_SCOPE_ONELEVEL,
428 			G_PARAM_READWRITE |
429 			G_PARAM_CONSTRUCT |
430 			G_PARAM_EXPLICIT_NOTIFY |
431 			G_PARAM_STATIC_STRINGS |
432 			E_SOURCE_PARAM_SETTING));
433 
434 	/* This is bound to the security extension.
435 	 * Do not use E_SOURCE_PARAM_SETTING here. */
436 	g_object_class_install_property (
437 		object_class,
438 		PROP_SECURITY,
439 		g_param_spec_enum (
440 			"security",
441 			"Security",
442 			"LDAP security method",
443 			E_TYPE_SOURCE_LDAP_SECURITY,
444 			E_SOURCE_LDAP_SECURITY_NONE,
445 			G_PARAM_READWRITE |
446 			G_PARAM_EXPLICIT_NOTIFY |
447 			G_PARAM_STATIC_STRINGS));
448 }
449 
450 static void
e_source_ldap_init(ESourceLDAP * extension)451 e_source_ldap_init (ESourceLDAP *extension)
452 {
453 	extension->priv = e_source_ldap_get_instance_private (extension);
454 }
455 
456 ESourceLDAPAuthentication
e_source_ldap_get_authentication(ESourceLDAP * extension)457 e_source_ldap_get_authentication (ESourceLDAP *extension)
458 {
459 	g_return_val_if_fail (E_IS_SOURCE_LDAP (extension), 0);
460 
461 	return extension->priv->authentication;
462 }
463 
464 void
e_source_ldap_set_authentication(ESourceLDAP * extension,ESourceLDAPAuthentication authentication)465 e_source_ldap_set_authentication (ESourceLDAP *extension,
466                                   ESourceLDAPAuthentication authentication)
467 {
468 	g_return_if_fail (E_IS_SOURCE_LDAP (extension));
469 
470 	if (extension->priv->authentication == authentication)
471 		return;
472 
473 	extension->priv->authentication = authentication;
474 
475 	g_object_notify (G_OBJECT (extension), "authentication");
476 }
477 
478 gboolean
e_source_ldap_get_can_browse(ESourceLDAP * extension)479 e_source_ldap_get_can_browse (ESourceLDAP *extension)
480 {
481 	g_return_val_if_fail (E_IS_SOURCE_LDAP (extension), FALSE);
482 
483 	return extension->priv->can_browse;
484 }
485 
486 void
e_source_ldap_set_can_browse(ESourceLDAP * extension,gboolean can_browse)487 e_source_ldap_set_can_browse (ESourceLDAP *extension,
488                               gboolean can_browse)
489 {
490 	g_return_if_fail (E_IS_SOURCE_LDAP (extension));
491 
492 	if (extension->priv->can_browse == can_browse)
493 		return;
494 
495 	extension->priv->can_browse = can_browse;
496 
497 	g_object_notify (G_OBJECT (extension), "can-browse");
498 }
499 
500 const gchar *
e_source_ldap_get_filter(ESourceLDAP * extension)501 e_source_ldap_get_filter (ESourceLDAP *extension)
502 {
503 	g_return_val_if_fail (E_IS_SOURCE_LDAP (extension), NULL);
504 
505 	return extension->priv->filter;
506 }
507 
508 gchar *
e_source_ldap_dup_filter(ESourceLDAP * extension)509 e_source_ldap_dup_filter (ESourceLDAP *extension)
510 {
511 	const gchar *protected;
512 	gchar *duplicate;
513 
514 	g_return_val_if_fail (E_IS_SOURCE_LDAP (extension), NULL);
515 
516 	e_source_extension_property_lock (E_SOURCE_EXTENSION (extension));
517 
518 	protected = e_source_ldap_get_filter (extension);
519 	duplicate = g_strdup (protected);
520 
521 	e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
522 
523 	return duplicate;
524 }
525 
526 void
e_source_ldap_set_filter(ESourceLDAP * extension,const gchar * filter)527 e_source_ldap_set_filter (ESourceLDAP *extension,
528                           const gchar *filter)
529 {
530 	gboolean needs_parens;
531 	gchar *new_filter;
532 
533 	g_return_if_fail (E_IS_SOURCE_LDAP (extension));
534 
535 	needs_parens =
536 		(filter != NULL) && (*filter != '\0') &&
537 		!g_str_has_prefix (filter, "(") &&
538 		!g_str_has_suffix (filter, ")");
539 
540 	e_source_extension_property_lock (E_SOURCE_EXTENSION (extension));
541 
542 	if (needs_parens)
543 		new_filter = g_strdup_printf ("(%s)", filter);
544 	else
545 		new_filter = g_strdup (filter);
546 
547 	if (g_strcmp0 (extension->priv->filter, new_filter) == 0) {
548 		e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
549 		g_free (new_filter);
550 		return;
551 	}
552 
553 	g_free (extension->priv->filter);
554 	extension->priv->filter = new_filter;
555 
556 	e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
557 
558 	g_object_notify (G_OBJECT (extension), "filter");
559 }
560 
561 guint
e_source_ldap_get_limit(ESourceLDAP * extension)562 e_source_ldap_get_limit (ESourceLDAP *extension)
563 {
564 	g_return_val_if_fail (E_IS_SOURCE_LDAP (extension), 0);
565 
566 	return extension->priv->limit;
567 }
568 
569 void
e_source_ldap_set_limit(ESourceLDAP * extension,guint limit)570 e_source_ldap_set_limit (ESourceLDAP *extension,
571                          guint limit)
572 {
573 	g_return_if_fail (E_IS_SOURCE_LDAP (extension));
574 
575 	if (extension->priv->limit == limit)
576 		return;
577 
578 	extension->priv->limit = limit;
579 
580 	g_object_notify (G_OBJECT (extension), "limit");
581 }
582 
583 const gchar *
e_source_ldap_get_root_dn(ESourceLDAP * extension)584 e_source_ldap_get_root_dn (ESourceLDAP *extension)
585 {
586 	g_return_val_if_fail (E_IS_SOURCE_LDAP (extension), NULL);
587 
588 	return extension->priv->root_dn;
589 }
590 
591 gchar *
e_source_ldap_dup_root_dn(ESourceLDAP * extension)592 e_source_ldap_dup_root_dn (ESourceLDAP *extension)
593 {
594 	const gchar *protected;
595 	gchar *duplicate;
596 
597 	g_return_val_if_fail (E_IS_SOURCE_LDAP (extension), NULL);
598 
599 	e_source_extension_property_lock (E_SOURCE_EXTENSION (extension));
600 
601 	protected = e_source_ldap_get_root_dn (extension);
602 	duplicate = g_strdup (protected);
603 
604 	e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
605 
606 	return duplicate;
607 }
608 
609 void
e_source_ldap_set_root_dn(ESourceLDAP * extension,const gchar * root_dn)610 e_source_ldap_set_root_dn (ESourceLDAP *extension,
611                            const gchar *root_dn)
612 {
613 	g_return_if_fail (E_IS_SOURCE_LDAP (extension));
614 
615 	e_source_extension_property_lock (E_SOURCE_EXTENSION (extension));
616 
617 	if (e_util_strcmp0 (extension->priv->root_dn, root_dn) == 0) {
618 		e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
619 		return;
620 	}
621 
622 	g_free (extension->priv->root_dn);
623 	extension->priv->root_dn = e_util_strdup_strip (root_dn);
624 
625 	e_source_extension_property_unlock (E_SOURCE_EXTENSION (extension));
626 
627 	g_object_notify (G_OBJECT (extension), "root-dn");
628 }
629 
630 ESourceLDAPScope
e_source_ldap_get_scope(ESourceLDAP * extension)631 e_source_ldap_get_scope (ESourceLDAP *extension)
632 {
633 	g_return_val_if_fail (E_IS_SOURCE_LDAP (extension), 0);
634 
635 	return extension->priv->scope;
636 }
637 
638 void
e_source_ldap_set_scope(ESourceLDAP * extension,ESourceLDAPScope scope)639 e_source_ldap_set_scope (ESourceLDAP *extension,
640                          ESourceLDAPScope scope)
641 {
642 	g_return_if_fail (E_IS_SOURCE_LDAP (extension));
643 
644 	if (extension->priv->scope == scope)
645 		return;
646 
647 	extension->priv->scope = scope;
648 
649 	g_object_notify (G_OBJECT (extension), "scope");
650 }
651 
652 ESourceLDAPSecurity
e_source_ldap_get_security(ESourceLDAP * extension)653 e_source_ldap_get_security (ESourceLDAP *extension)
654 {
655 	g_return_val_if_fail (E_IS_SOURCE_LDAP (extension), 0);
656 
657 	return extension->priv->security;
658 }
659 
660 void
e_source_ldap_set_security(ESourceLDAP * extension,ESourceLDAPSecurity security)661 e_source_ldap_set_security (ESourceLDAP *extension,
662                             ESourceLDAPSecurity security)
663 {
664 	g_return_if_fail (E_IS_SOURCE_LDAP (extension));
665 
666 	if (extension->priv->security == security)
667 		return;
668 
669 	extension->priv->security = security;
670 
671 	g_object_notify (G_OBJECT (extension), "security");
672 }
673