- prepare 1.4
[m6w6/mod-domaintree] / mod_domaintree.c
1 /*
2 * mod_domaintree.c - Apache2
3 *
4 * $Id$
5 *
6 * Copyright 2005 Michael Wallner <mike@iworks.at>
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 *
20 *
21 * <pre>
22 * DomainTreeEnabled On
23 * DomainTreeMaxdepth 25
24 * DomainTreeStripWWW On
25 * DomainTreePrefix /sites
26 * DomainTreeSuffix /html
27 * DomainTreeCache 5
28 * DomainTreeAliasRecursion Off
29 * DomainTreeAlias /??/exmaple /com/exmaple
30 * DomainTreeAlias /???/example /com/example
31 * DomainTreeAlias /*one/ /anyone/
32 *
33 * /sites
34 * +- /at
35 * | +- /co
36 * | | +- /company
37 * | | +- /html
38 * | | +- /sub1
39 * | | | +- /html
40 * | | +- /sub2
41 * | | +- /html
42 * | +- /or
43 * | +- /organisation
44 * | +- /html
45 * +- /com
46 * +- /example
47 * +- /html
48 * </pre>
49 */
50
51 #ifdef HAVE_CONFIG_H
52 #include "config.h"
53 #endif
54
55 #define MODULE "mod_domaintree"
56 #define AUTHOR "<mike@iworks.at>"
57 #define VERSION "1.4"
58
59 /* {{{ Includes */
60
61 #include "apr.h"
62 #include "apr_lib.h"
63 #include "apr_hash.h"
64 #include "apr_tables.h"
65 #include "apr_strings.h"
66
67 #if APR_HAVE_UNISTD_H
68 #include <unistd.h>
69 #endif
70
71 #define APR_WANT_MEMFUNC
72 #define APR_WANT_STRFUNC
73 #include "apr_want.h"
74
75 #include "httpd.h"
76 #include "http_config.h"
77 #include "http_core.h"
78 #include "http_log.h"
79 #include "http_protocol.h"
80 #include "http_request.h"
81
82 /* }}} */
83 /* {{{ domaintree_module */
84
85 module AP_MODULE_DECLARE_DATA domaintree_module;
86
87 /* }}} */
88 /* {{{ Macros & Types */
89
90 #define MDT_CNF domaintree_conf
91 #define MDT_PTR (&domaintree_module)
92
93 #define GET_MDT_CNF(srv) ((MDT_CNF *) ap_get_module_config((srv)->module_config, MDT_PTR))
94
95 #define IF_SET_ELSE(a, b) (a != -1) ? (a) : (b)
96
97 #define NUL '\0'
98 #define EMPTY(str) ((str == NULL) || (*(str) == NUL))
99 #define local static APR_INLINE
100
101 #define DT_LOG_ERR ap_log_error(APLOG_MARK, APLOG_ERR, APR_SUCCESS, DT->server,
102 #define DT_LOG_WRN ap_log_error(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, DT->server,
103 #define DT_LOG_DBG ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, DT->server,
104 #define DT_LOG_END );
105
106 typedef int STATUS;
107
108 typedef struct {
109 long recursion;
110 apr_table_t *faketable;
111 } aliases_t;
112
113 typedef struct {
114 char *host;
115 char *path;
116 apr_time_t lacc;
117 apr_pool_t *pool;
118 } dircache_entry_t;
119
120 typedef struct {
121 long clim;
122 apr_hash_t *hmap;
123 apr_pool_t *pool;
124 apr_global_mutex_t *lock;
125 } dircache_t;
126
127 typedef struct {
128 server_rec *server;
129 int enabled;
130 int stripwww;
131 int statroot;
132 long maxdepth;
133 char *prefix;
134 char *suffix;
135 aliases_t aliases;
136 dircache_t dircache;
137 } domaintree_conf;
138
139 /* }}} */
140 /* {{{ Helpers */
141
142 local char *strtr(char *string, char from, char to)
143 {
144 char *ptr = string;
145
146 if (from != to) {
147 while ((ptr = strchr(ptr, from))) {
148 *ptr = to;
149 }
150 }
151 return string;
152 }
153
154 #define TRIM_LEFT 1
155 #define TRIM_RIGHT 2
156 #define TRIM_BOTH (TRIM_LEFT|TRIM_RIGHT)
157 local char *trim(char *string, size_t length, char what, int where)
158 {
159 if (where & TRIM_RIGHT) {
160 while (length-- && (string[length] == what)) {
161 string[length] = NUL;
162 }
163 }
164 if (where & TRIM_LEFT) {
165 while (*string == what) {
166 ++string;
167 }
168 }
169 return string;
170 }
171
172 local int strmatch(const char *match, const char *string, const char **begin, const char **end)
173 {
174 *begin = *end = NULL;
175
176 while (*match)
177 {
178 switch (*match)
179 {
180 case '*':
181 while (*match == '*' || *match == '?') {
182 ++match;
183 }
184
185 if (!*begin) {
186 *begin = string;
187 }
188
189 if (!*match) {
190 *end = string + strlen(string);
191 return 1;
192 }
193
194 if (!(string = strchr(string, *match))) {
195 *end = string;
196 return 0;
197 }
198 break;
199
200 case '?':
201 if (!*begin) {
202 *begin = string;
203 }
204 ++string;
205 ++match;
206 break;
207
208 default:
209 if (*match == *string) {
210 if (!*begin) {
211 *begin = string;
212 }
213 ++match;
214 } else {
215 if (*begin) {
216 *end = string - 1;
217 return 0;
218 }
219 }
220 ++string;
221 break;
222 }
223 }
224
225 *end = string;
226 return 1;
227 }
228
229 local char *struniqchr(char *string, char uniq)
230 {
231 char *ptr = string;
232
233 while (*ptr) {
234 if (*ptr == uniq && *(ptr + 1) == uniq) {
235 char *pos = ptr + 1;
236
237 while (*(pos + 1) == uniq) {
238 ++pos;
239 }
240
241 memmove(ptr, pos, strlen(pos) + 1);
242 }
243 ++ptr;
244 }
245
246 return string;
247 }
248
249 local char *domaintree_host(apr_pool_t *pool, MDT_CNF *DT, const char *host_name)
250 {
251 size_t len;
252 char *port, *host;
253
254 if (EMPTY(host_name)) {
255 DT_LOG_WRN
256 "DomainTree: no host/server name"
257 DT_LOG_END
258 return NULL;
259 }
260
261 /* copy hostname */
262 host = apr_pstrdup(pool, host_name);
263
264 /* check for :NN port */
265 if ((port = strchr(host, ':'))) {
266 len = port - host;
267 *port = NUL;
268 } else {
269 len = strlen(host);
270 }
271
272 /* strip leading & trailing dots */
273 host = trim(host, len, '.', TRIM_BOTH);
274
275 DT_LOG_DBG
276 "DomainTree: host name = %s for %s", host, host_name
277 DT_LOG_END
278
279 return host;
280 }
281
282 local char *domaintree_path(apr_pool_t *pool, MDT_CNF *DT, const char *host_name)
283 {
284 long depth = 0, maxdepth = IF_SET_ELSE(DT->maxdepth, 20);
285 const char *host = host_name;
286 char *path = NULL, *host_ptr;
287
288 while ((host_ptr = strchr(host, '.'))) {
289
290 /* check max depth */
291 if (++depth > maxdepth) {
292 DT_LOG_ERR
293 "DomainTree: maxdepth exceeded (%ld)", maxdepth
294 DT_LOG_END
295 return NULL;
296 }
297
298 /* append part */
299 if (host_ptr - host) {
300
301 /* strip WWW */
302 if ((DT->stripwww > 0) && (depth == 1) && (!strncmp(host, "www.", sizeof("www")))) {
303 DT_LOG_DBG
304 "DomainTree: strip www"
305 DT_LOG_END
306 } else {
307 path = apr_pstrcat(pool, apr_pstrndup(pool, host, host_ptr - host), "/", path, NULL);
308 }
309 }
310
311 host = host_ptr + 1;
312 }
313
314 /* append last part if any and duplicate full path */
315 if (*host) {
316 path = apr_pstrcat(pool, host, "/", path, NULL);
317 }
318
319 DT_LOG_DBG
320 "DomainTree: path name = %s for %s", path, host_name
321 DT_LOG_END
322
323 return path;
324 }
325
326 local void domaintree_fake(apr_pool_t *pool, MDT_CNF *DT, char **path)
327 {
328 int i, more;
329 long recurlevel = 0;
330 apr_pool_t *local_pool;
331 const apr_array_header_t *header = apr_table_elts(DT->aliases.faketable);
332 apr_table_entry_t *array = (apr_table_entry_t *) header->elts;
333
334 if (APR_SUCCESS != apr_pool_create(&local_pool, pool)) {
335 return;
336 }
337
338 do {
339 more = 0;
340
341 if (recurlevel++ > DT->aliases.recursion) {
342 DT_LOG_ERR
343 "DomainTree: maximum alias recursion level (%ld) exceeded! "
344 "Check if you have recursive definitions of DomainTreeAlias directives.",
345 DT->aliases.recursion
346 DT_LOG_END
347 break;
348 }
349
350 for (i = 0; i < header->nelts; ++i) {
351 const char *begin, *end;
352
353 DT_LOG_DBG
354 "DomainTree: fake test = %s on %s", array[i].key, *path
355 DT_LOG_END
356
357 if (strmatch(array[i].key, *path, &begin, &end)) {
358 more = 1;
359 *path = apr_pstrcat(local_pool, "/", apr_pstrndup(local_pool, *path, begin - *path), "/", array[i].val, "/", end, NULL);
360
361 DT_LOG_DBG
362 "DomainTree: fake done = %s (%s <> %s)", *path, array[i].key, array[i].val
363 DT_LOG_END
364 }
365 }
366 } while (more && (DT->aliases.recursion > 0));
367
368 *path = apr_pstrdup(pool, struniqchr(*path, '/'));
369
370 apr_pool_destroy(local_pool);
371 }
372
373 local char *domaintree_cache_get(MDT_CNF *DT, apr_time_t atime, const char *host)
374 {
375 char *path = NULL;
376 dircache_entry_t *cache_entry;
377
378 apr_global_mutex_lock(DT->dircache.lock);
379
380 if ((cache_entry = apr_hash_get(DT->dircache.hmap, host, APR_HASH_KEY_STRING))) {
381 cache_entry->lacc = atime;
382 path = cache_entry->path;
383 }
384
385 apr_global_mutex_unlock(DT->dircache.lock);
386
387 if (path) {
388 DT_LOG_DBG
389 "DomainTree: cache hit = %s for %s", path, host
390 DT_LOG_END
391 }
392
393 return path;
394 }
395
396 local void domaintree_cache_set(MDT_CNF *DT, apr_time_t atime, const char *host, const char *path)
397 {
398 apr_pool_t *pool;
399 dircache_entry_t *cache_entry;
400
401 apr_pool_create(&pool, DT->dircache.pool);
402 cache_entry = apr_palloc(pool, sizeof(dircache_entry_t));
403
404 cache_entry->pool = pool;
405 cache_entry->lacc = atime;
406 cache_entry->host = apr_pstrdup(pool, host);
407 cache_entry->path = apr_pstrdup(pool, path);
408
409 apr_global_mutex_lock(DT->dircache.lock);
410
411 if (apr_hash_count(DT->dircache.hmap) >= DT->dircache.clim) {
412 apr_hash_index_t *idx;
413 dircache_entry_t *purge_this = NULL;
414
415 DT_LOG_WRN
416 "DomainTree: reached cache limit (%ld)", DT->dircache.clim
417 DT_LOG_END
418
419 for (idx = apr_hash_first(DT->dircache.pool, DT->dircache.hmap); idx; idx = apr_hash_next(idx)) {
420 dircache_entry_t *current;
421
422 apr_hash_this(idx, NULL, NULL, (void *) &current);
423 if ((!purge_this) || (purge_this->lacc > current->lacc)) {
424 purge_this = current;
425 }
426 }
427
428 if (purge_this) {
429 DT_LOG_DBG
430 "DomainTree: cache del = %s", purge_this->host
431 DT_LOG_END
432 apr_hash_set(DT->dircache.hmap, purge_this->host, APR_HASH_KEY_STRING, NULL);
433 apr_pool_destroy(purge_this->pool);
434 }
435 }
436 apr_hash_set(DT->dircache.hmap, cache_entry->host, APR_HASH_KEY_STRING, cache_entry);
437
438 apr_global_mutex_unlock(DT->dircache.lock);
439
440 DT_LOG_DBG
441 "DomainTree: cache set = %s for %s", path, host
442 DT_LOG_END
443 }
444
445 /* }}} */
446 /* {{{ Hooks */
447
448 static STATUS domaintree_hook_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
449 {
450 ap_add_version_component(pconf, "MDT/" VERSION);
451 return OK;
452 }
453
454 static STATUS domaintree_hook_translate_name(request_rec *r)
455 {
456 MDT_CNF *DT = NULL;
457 char *host, *path, *docroot;
458
459 if ((!(DT = GET_MDT_CNF(r->server))) || (DT->enabled < 1)) {
460 return DECLINED;
461 }
462
463 DT_LOG_DBG
464 "DomainTree: processid = %d", (int) getpid()
465 DT_LOG_END
466
467 /* get a usable host name */
468 if (!(host = domaintree_host(r->pool, DT, ap_get_server_name(r)))) {
469 return DECLINED;
470 }
471
472 /* check cache */
473 if ((DT->dircache.clim < 1) || (!(path = domaintree_cache_get(DT, r->request_time, host)))) {
474 /* build path */
475 if (!(path = domaintree_path(r->pool, DT, host))) {
476 return DECLINED;
477 }
478
479 /* apply any aliases */
480 if (apr_table_elts(DT->aliases.faketable)->nelts) {
481 domaintree_fake(r->pool, DT, &path);
482 }
483
484 /* add to cache */
485 if (DT->dircache.clim) {
486 domaintree_cache_set(DT, r->request_time, host, path);
487 }
488 }
489
490 /* compose virtual docroot */
491 docroot = struniqchr(apr_pstrcat(r->pool, DT->prefix, "/", path, "/", DT->suffix, "/", NULL), '/');
492
493 /* stat docroot */
494 if (DT->statroot > 0) {
495 apr_finfo_t sb;
496
497 switch (apr_stat(&sb, docroot, APR_FINFO_MIN, r->pool))
498 {
499 case APR_SUCCESS:
500 case APR_INCOMPLETE:
501 DT_LOG_DBG
502 "DomainTree: stat path = %s (success)", docroot
503 DT_LOG_END
504 break;
505
506 default:
507 DT_LOG_DBG
508 "DomainTree: stat path = %s (failure)", docroot
509 DT_LOG_END
510 return DECLINED;
511 break;
512 }
513 }
514
515 /* set virtual docroot */
516 apr_table_set(r->subprocess_env, "VIRTUAL_DOCUMENT_ROOT", docroot);
517
518 /* done */
519 r->canonical_filename = "";
520 r->filename = apr_pstrcat(r->pool, docroot, EMPTY(r->uri) ? NULL : ('/' == *r->uri ? r->uri + 1 : r->uri), NULL);
521
522 DT_LOG_DBG
523 "DomainTree: path done = %s", r->filename
524 DT_LOG_END
525
526 return OK;
527 }
528
529 static void domaintree_hooks(apr_pool_t *pool)
530 {
531 static const char * const pre[] = {"mod_alias.c", "mod_userdir.c", NULL};
532
533 ap_hook_post_config(domaintree_hook_post_config, NULL, NULL, APR_HOOK_MIDDLE);
534 ap_hook_translate_name(domaintree_hook_translate_name, pre, NULL, APR_HOOK_FIRST);
535 }
536
537 /* }}} */
538 /* {{{ Configuration */
539
540 static void *domaintree_create_srv(apr_pool_t *p, server_rec *s)
541 {
542 MDT_CNF *DT= (MDT_CNF *) apr_palloc(p, sizeof(MDT_CNF));
543
544 DT->server = s;
545 DT->enabled = -1;
546 DT->stripwww = -1;
547 DT->statroot = -1;
548 DT->maxdepth = -1;
549
550 DT->prefix = "/var/www";
551 DT->suffix = "public_html";
552
553 DT->aliases.recursion = -1;
554 DT->aliases.faketable = apr_table_make(p, 0);
555
556 DT->dircache.clim = -1;
557 DT->dircache.hmap = apr_hash_make(p);
558 apr_pool_create(&DT->dircache.pool, p);
559 apr_global_mutex_create(&DT->dircache.lock, __FILE__, APR_LOCK_DEFAULT, p);
560
561 return DT;
562 }
563
564 static void *domaintree_merge_srv(apr_pool_t *p, void *old_cfg_ptr, void *new_cfg_ptr)
565 {
566 MDT_CNF *old_cfg = (MDT_CNF *) old_cfg_ptr;
567 MDT_CNF *new_cfg = (MDT_CNF *) new_cfg_ptr;
568 MDT_CNF *mrg_cfg = (MDT_CNF *) domaintree_create_srv(p, new_cfg->server);
569
570 mrg_cfg->enabled = IF_SET_ELSE(new_cfg->enabled, old_cfg->enabled);
571 mrg_cfg->stripwww = IF_SET_ELSE(new_cfg->stripwww, old_cfg->stripwww);
572 mrg_cfg->statroot = IF_SET_ELSE(new_cfg->statroot, old_cfg->statroot);
573 mrg_cfg->maxdepth = IF_SET_ELSE(new_cfg->maxdepth, old_cfg->maxdepth);
574
575 mrg_cfg->prefix = EMPTY(new_cfg->prefix) ? EMPTY(old_cfg->prefix) ? "/var/www" : old_cfg->prefix : new_cfg->prefix;
576 mrg_cfg->suffix = EMPTY(new_cfg->suffix) ? EMPTY(old_cfg->suffix) ? "public_html" : old_cfg->suffix : new_cfg->suffix;
577
578 mrg_cfg->aliases.recursion = IF_SET_ELSE(new_cfg->aliases.recursion, old_cfg->aliases.recursion);
579 mrg_cfg->dircache.clim = IF_SET_ELSE(new_cfg->dircache.clim, old_cfg->dircache.clim);
580
581 return mrg_cfg;
582 }
583
584 static const char *domaintree_enable(cmd_parms *cmd, void *conf, int flag)
585 {
586 GET_MDT_CNF(cmd->server)->enabled = flag;
587 return NULL;
588 }
589
590 static const char *domaintree_stripwww(cmd_parms *cmd, void *conf, int flag)
591 {
592 GET_MDT_CNF(cmd->server)->stripwww = flag;
593 return NULL;
594 }
595
596 static const char *domaintree_statroot(cmd_parms *cmd, void *conf, int flag)
597 {
598 GET_MDT_CNF(cmd->server)->statroot = flag;
599 return NULL;
600 }
601
602 static const char *domaintree_prefix(cmd_parms *cmd, void *conf, const char *prefix)
603 {
604 GET_MDT_CNF(cmd->server)->prefix = EMPTY(prefix) ? "/" : trim(apr_pstrdup(cmd->pool, prefix), strlen(prefix), '/', TRIM_RIGHT);
605 return NULL;
606 }
607
608 static const char *domaintree_suffix(cmd_parms *cmd, void *conf, const char *suffix)
609 {
610 GET_MDT_CNF(cmd->server)->suffix = EMPTY(suffix) ? "" : trim(apr_pstrdup(cmd->pool, suffix), strlen(suffix), '/', TRIM_BOTH);
611 return NULL;
612 }
613
614 static const char *domaintree_maxdepth(cmd_parms *cmd, void *conf, const char *max_depth)
615 {
616 long depth;
617
618 if ((depth = atol(max_depth))) {
619 if (depth > 0L) {
620 GET_MDT_CNF(cmd->server)->maxdepth = depth;
621 } else {
622 return "Maximum DomainTree depth cannot be negative.";
623 }
624 }
625
626 return NULL;
627 }
628
629 static const char *domaintree_aliasrecursion(cmd_parms *cmd, void *conf, const char *alias_recursion)
630 {
631 long recursion;
632
633 if ((recursion = atol(alias_recursion))) {
634 if (recursion > 0L) {
635 GET_MDT_CNF(cmd->server)->aliases.recursion = recursion;
636 } else {
637 return "DomainTree alias recursion cannot be negative.";
638 }
639 }
640
641 return NULL;
642 }
643
644 static const char *domaintree_alias(cmd_parms *cmd, void *conf, const char *fake, const char *real)
645 {
646 char *f = strtr(apr_pstrdup(cmd->pool, fake), '.', '/'), *r = strtr(apr_pstrdup(cmd->pool, real), '.', '/');
647
648 apr_table_set(GET_MDT_CNF(cmd->server)->aliases.faketable, trim(f, strlen(f), '/', TRIM_BOTH), trim(r, strlen(r), '/', TRIM_BOTH));
649
650 return NULL;
651 }
652
653 static const char *domaintree_cache(cmd_parms *cmd, void *conf, const char *cache)
654 {
655 long limit;
656
657 if ((limit = atol(cache))) {
658 if (limit > 0L) {
659 GET_MDT_CNF(cmd->server)->dircache.clim = limit;
660 } else {
661 return "DomainTree cache limit cannot be negative.";
662 }
663 }
664
665 return NULL;
666 }
667
668 /* }}} */
669 /* {{{ Commands */
670
671 static command_rec domaintree_commands[] = {
672 AP_INIT_FLAG(
673 "DomainTreeEnabled", domaintree_enable, NULL, RSRC_CONF,
674 "Turn the module on or off."
675 ),
676
677 AP_INIT_FLAG(
678 "DomainTreeStripWWW", domaintree_stripwww, NULL, RSRC_CONF,
679 "Strip leading www from host. (default On)"
680 ),
681
682 AP_INIT_FLAG(
683 "DomainTreeStatRoot", domaintree_statroot, NULL, RSRC_CONF,
684 "Wheter to check for the evaluated virtual document root with a stat call. (default Off)"
685 ),
686
687 AP_INIT_TAKE1(
688 "DomainTreePrefix", domaintree_prefix, NULL, RSRC_CONF,
689 "DomainTree path prefix. (default /var/www) Do not forget the leading slash!"
690 ),
691
692 AP_INIT_TAKE1(
693 "DomainTreeSuffix", domaintree_suffix, NULL, RSRC_CONF,
694 "DomainTree path suffix. (default public_html)"
695 ),
696
697 AP_INIT_TAKE1(
698 "DomainTreeMaxdepth", domaintree_maxdepth, NULL, RSRC_CONF,
699 "DomainTree max path depth. (default 20)"
700 ),
701
702 AP_INIT_TAKE1(
703 "DomainTreeAliasRecursion", domaintree_aliasrecursion, NULL, RSRC_CONF,
704 "Whether (and how often at the maximum) DomainTree should walk recursively "
705 "through the aliases list as long as matching aliases are found. (default: 0 = turned off)"
706 ),
707
708 AP_INIT_TAKE2(
709 "DomainTreeAlias", domaintree_alias, NULL, RSRC_CONF,
710 "DomainTree aliases; e.g. DomainTreeAlias com/example/tickets com/example/support (dots or slashes equal)"
711 ),
712
713 AP_INIT_TAKE1(
714 "DomainTreeCache", domaintree_cache, NULL, RSRC_CONF,
715 "DomainTree server-wide host to directory cache; specify how many cache entries to allow (default: 0 = turned off)"
716 ),
717
718 { NULL }
719 };
720
721 /* }}} */
722 /* {{{ Module Administrativa */
723
724 module AP_MODULE_DECLARE_DATA domaintree_module = {
725 STANDARD20_MODULE_STUFF,
726 NULL, /* create per-dir */
727 NULL, /* merge per-dir */
728 domaintree_create_srv, /* create per-server */
729 domaintree_merge_srv, /* merge per-server */
730 domaintree_commands, /* config commands */
731 domaintree_hooks /* hooks */
732 };
733
734 /* }}} */
735
736 /*
737 * Local variables:
738 * tab-width: 4
739 * c-basic-offset: 4
740 * End:
741 * vim600: noet sw=4 ts=4 fdm=marker
742 * vim<600: noet sw=4 ts=4
743 */