1 /* $NetBSD: homedir.c,v 1.2 2021/08/14 16:15:02 christos Exp $ */
2
3 /* homedir.c - create/remove user home directories */
4 /* $OpenLDAP$ */
5 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
6 *
7 * Copyright 2009-2010 The OpenLDAP Foundation.
8 * Portions copyright 2009-2010 Symas Corporation.
9 * All rights reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted only as authorized by the OpenLDAP
13 * Public License.
14 *
15 * A copy of this license is available in the file LICENSE in the
16 * top-level directory of the distribution or, alternatively, at
17 * <http://www.OpenLDAP.org/license.html>.
18 */
19 /* ACKNOWLEDGEMENTS:
20 * This work was initially developed by Emily Backes at Symas
21 * Corp. for inclusion in OpenLDAP Software.
22 */
23
24 #include <sys/cdefs.h>
25 __RCSID("$NetBSD: homedir.c,v 1.2 2021/08/14 16:15:02 christos Exp $");
26
27 #include "portable.h"
28
29 #ifdef SLAPD_OVER_HOMEDIR
30
31 #define _FILE_OFFSET_BITS 64
32
33 #include <stdio.h>
34 #include <fcntl.h>
35
36 #include <ac/string.h>
37 #include <ac/ctype.h>
38 #include <ac/errno.h>
39 #include <sys/stat.h>
40 #include <ac/unistd.h>
41 #include <ac/dirent.h>
42 #include <ac/time.h>
43
44 #include "slap.h"
45 #include "slap-config.h"
46
47 #define DEFAULT_MIN_UID ( 100 )
48 #define DEFAULT_SKEL ( LDAP_DIRSEP "etc" LDAP_DIRSEP "skel" )
49
50 typedef struct homedir_regexp {
51 char *match;
52 char *replace;
53 regex_t compiled;
54 struct homedir_regexp *next;
55 } homedir_regexp;
56
57 typedef enum {
58 DEL_IGNORE,
59 DEL_DELETE,
60 DEL_ARCHIVE
61 } delete_style;
62
63 typedef struct homedir_data {
64 char *skeleton_path;
65 unsigned min_uid;
66 AttributeDescription *home_ad;
67 AttributeDescription *uidn_ad;
68 AttributeDescription *gidn_ad;
69 homedir_regexp *regexps;
70 delete_style style;
71 char *archive_path;
72 } homedir_data;
73
74 typedef struct homedir_cb_data {
75 slap_overinst *on;
76 Entry *entry;
77 } homedir_cb_data;
78
79 typedef struct name_list {
80 char *name;
81 struct stat st;
82 struct name_list *next;
83 } name_list;
84
85 typedef struct name_list_list {
86 name_list *list;
87 struct name_list_list *next;
88 } name_list_list;
89
90 typedef enum {
91 TRAVERSE_CB_CONTINUE,
92 TRAVERSE_CB_DONE,
93 TRAVERSE_CB_FAIL
94 } traverse_cb_ret;
95
96 /* private, file info, context */
97 typedef traverse_cb_ret (*traverse_cb_func)(
98 void *,
99 const char *,
100 const struct stat *,
101 void * );
102 typedef struct traverse_cb {
103 traverse_cb_func pre_func;
104 traverse_cb_func post_func;
105 void *pre_private;
106 void *post_private;
107 } traverse_cb;
108
109 typedef struct copy_private {
110 int source_prefix_len;
111 const char *dest_prefix;
112 int dest_prefix_len;
113 uid_t uidn;
114 gid_t gidn;
115 } copy_private;
116
117 typedef struct chown_private {
118 uid_t old_uidn;
119 uid_t new_uidn;
120 gid_t old_gidn;
121 gid_t new_gidn;
122 } chown_private;
123
124 typedef struct ustar_header {
125 char name[100];
126 char mode[8];
127 char uid[8];
128 char gid[8];
129 char size[12];
130 char mtime[12];
131 char checksum[8];
132 char typeflag[1];
133 char linkname[100];
134 char magic[6];
135 char version[2];
136 char uname[32];
137 char gname[32];
138 char devmajor[8];
139 char devminor[8];
140 char prefix[155];
141 char pad[12];
142 } ustar_header;
143
144 typedef struct tar_private {
145 FILE *file;
146 const char *name;
147 } tar_private;
148
149 /* FIXME: This mutex really needs to be executable-global, but this
150 * will have to do for now.
151 */
152 static ldap_pvt_thread_mutex_t readdir_mutex;
153 static ConfigDriver homedir_regexp_cfg;
154 static ConfigDriver homedir_style_cfg;
155 static slap_overinst homedir;
156
157 static ConfigTable homedircfg[] = {
158 { "homedir-skeleton-path", "pathname", 2, 2, 0,
159 ARG_STRING|ARG_OFFSET,
160 (void *)offsetof(homedir_data, skeleton_path),
161 "( OLcfgCtAt:8.1 "
162 "NAME 'olcSkeletonPath' "
163 "DESC 'Pathname for home directory skeleton template' "
164 "SYNTAX OMsDirectoryString "
165 "SINGLE-VALUE )",
166 NULL, { .v_string = DEFAULT_SKEL }
167 },
168
169 { "homedir-min-uidnumber", "uid number", 2, 2, 0,
170 ARG_UINT|ARG_OFFSET,
171 (void *)offsetof(homedir_data, min_uid),
172 "( OLcfgCtAt:8.2 "
173 "NAME 'olcMinimumUidNumber' "
174 "DESC 'Minimum uidNumber attribute to consider' "
175 "SYNTAX OMsInteger "
176 "SINGLE-VALUE )",
177 NULL, { .v_uint = DEFAULT_MIN_UID }
178 },
179
180 { "homedir-regexp", "regexp> <path", 3, 3, 0,
181 ARG_MAGIC,
182 homedir_regexp_cfg,
183 "( OLcfgCtAt:8.3 "
184 "NAME 'olcHomedirRegexp' "
185 "DESC 'Regular expression for matching and transforming paths' "
186 "SYNTAX OMsDirectoryString "
187 "X-ORDERED 'VALUES' )",
188 NULL, NULL
189 },
190
191 { "homedir-delete-style", "style", 2, 2, 0,
192 ARG_MAGIC,
193 homedir_style_cfg,
194 "( OLcfgCtAt:8.4 "
195 "NAME 'olcHomedirDeleteStyle' "
196 "DESC 'Action to perform when removing a home directory' "
197 "SYNTAX OMsDirectoryString "
198 "SINGLE-VALUE )",
199 NULL, NULL
200 },
201
202 { "homedir-archive-path", "pathname", 2, 2, 0,
203 ARG_STRING|ARG_OFFSET,
204 (void *)offsetof(homedir_data, archive_path),
205 "( OLcfgCtAt:8.5 "
206 "NAME 'olcHomedirArchivePath' "
207 "DESC 'Pathname for home directory archival' "
208 "SYNTAX OMsDirectoryString "
209 "SINGLE-VALUE )",
210 NULL, NULL
211 },
212
213 { NULL, NULL, 0, 0, 0, ARG_IGNORED }
214 };
215
216 static ConfigOCs homedirocs[] = {
217 { "( OLcfgCtOc:8.1 "
218 "NAME 'olcHomedirConfig' "
219 "DESC 'Homedir configuration' "
220 "SUP olcOverlayConfig "
221 "MAY ( olcSkeletonPath $ olcMinimumUidNumber "
222 "$ olcHomedirRegexp $ olcHomedirDeleteStyle "
223 "$ olcHomedirArchivePath ) )",
224 Cft_Overlay, homedircfg },
225
226 { NULL, 0, NULL }
227 };
228
229 static int
homedir_regexp_cfg(ConfigArgs * c)230 homedir_regexp_cfg( ConfigArgs *c )
231 {
232 slap_overinst *on = (slap_overinst *)c->bi;
233 homedir_data *data = (homedir_data *)on->on_bi.bi_private;
234 int rc = ARG_BAD_CONF;
235
236 assert( data != NULL );
237
238 switch ( c->op ) {
239 case SLAP_CONFIG_EMIT: {
240 int i;
241 homedir_regexp *r;
242 struct berval bv;
243 char buf[4096];
244
245 bv.bv_val = buf;
246 for ( i = 0, r = data->regexps; r != NULL; ++i, r = r->next ) {
247 bv.bv_len = snprintf( buf, sizeof(buf), "{%d}%s %s", i,
248 r->match, r->replace );
249 if ( bv.bv_len >= sizeof(buf) ) {
250 Debug( LDAP_DEBUG_ANY, "homedir_regexp_cfg: "
251 "emit serialization failed: size %lu\n",
252 (unsigned long)bv.bv_len );
253 return ARG_BAD_CONF;
254 }
255 value_add_one( &c->rvalue_vals, &bv );
256 }
257 rc = 0;
258 } break;
259
260 case LDAP_MOD_DELETE:
261 if ( c->valx < 0 ) { /* delete all values */
262 homedir_regexp *r, *rnext;
263
264 for ( r = data->regexps; r != NULL; r = rnext ) {
265 rnext = r->next;
266 ch_free( r->match );
267 ch_free( r->replace );
268 regfree( &r->compiled );
269 ch_free( r );
270 }
271 data->regexps = NULL;
272 rc = 0;
273
274 } else { /* delete value by index*/
275 homedir_regexp **rp, *r;
276 int i;
277
278 for ( i = 0, rp = &data->regexps; i < c->valx;
279 ++i, rp = &(*rp)->next )
280 ;
281
282 r = *rp;
283 *rp = r->next;
284 ch_free( r->match );
285 ch_free( r->replace );
286 regfree( &r->compiled );
287 ch_free( r );
288
289 rc = 0;
290 }
291 break;
292
293 case LDAP_MOD_ADD: /* fallthrough */
294 case SLAP_CONFIG_ADD: { /* add values */
295 char *match = c->argv[1];
296 char *replace = c->argv[2];
297 regex_t compiled;
298 homedir_regexp **rp, *r;
299
300 memset( &compiled, 0, sizeof(compiled) );
301 rc = regcomp( &compiled, match, REG_EXTENDED );
302 if ( rc ) {
303 regerror( rc, &compiled, c->cr_msg, sizeof(c->cr_msg) );
304 regfree( &compiled );
305 return ARG_BAD_CONF;
306 }
307
308 r = ch_calloc( 1, sizeof(homedir_regexp) );
309 r->match = strdup( match );
310 r->replace = strdup( replace );
311 r->compiled = compiled;
312
313 if ( c->valx == -1 ) { /* append */
314 for ( rp = &data->regexps; ( *rp ) != NULL;
315 rp = &(*rp)->next )
316 ;
317 *rp = r;
318
319 } else { /* insert at valx */
320 int i;
321 for ( i = 0, rp = &data->regexps; i < c->valx;
322 rp = &(*rp)->next, ++i )
323 ;
324 r->next = *rp;
325 *rp = r;
326 }
327 rc = 0;
328 break;
329 }
330 default:
331 abort();
332 }
333
334 return rc;
335 }
336
337 static int
homedir_style_cfg(ConfigArgs * c)338 homedir_style_cfg( ConfigArgs *c )
339 {
340 slap_overinst *on = (slap_overinst *)c->bi;
341 homedir_data *data = (homedir_data *)on->on_bi.bi_private;
342 int rc = ARG_BAD_CONF;
343 struct berval bv;
344
345 assert( data != NULL );
346
347 switch ( c->op ) {
348 case SLAP_CONFIG_EMIT:
349 bv.bv_val = data->style == DEL_IGNORE ? "IGNORE" :
350 data->style == DEL_DELETE ? "DELETE" :
351 "ARCHIVE";
352 bv.bv_len = strlen( bv.bv_val );
353 rc = value_add_one( &c->rvalue_vals, &bv );
354 if ( rc != 0 ) return ARG_BAD_CONF;
355 break;
356
357 case LDAP_MOD_DELETE:
358 data->style = DEL_IGNORE;
359 rc = 0;
360 break;
361
362 case LDAP_MOD_ADD: /* fallthrough */
363 case SLAP_CONFIG_ADD: /* add values */
364 if ( strcasecmp( c->argv[1], "IGNORE" ) == 0 )
365 data->style = DEL_IGNORE;
366 else if ( strcasecmp( c->argv[1], "DELETE" ) == 0 )
367 data->style = DEL_DELETE;
368 else if ( strcasecmp( c->argv[1], "ARCHIVE" ) == 0 )
369 data->style = DEL_ARCHIVE;
370 else {
371 Debug( LDAP_DEBUG_ANY, "homedir_style_cfg: "
372 "unrecognized style keyword\n" );
373 return ARG_BAD_CONF;
374 }
375 rc = 0;
376 break;
377
378 default:
379 abort();
380 }
381
382 return rc;
383 }
384
385 #define HOMEDIR_NULLWRAP(x) ( ( x ) == NULL ? "unknown" : (x) )
386 static void
report_errno(const char * parent_func,const char * func,const char * filename)387 report_errno( const char *parent_func, const char *func, const char *filename )
388 {
389 int save_errno = errno;
390 char ebuf[1024];
391
392 Debug( LDAP_DEBUG_ANY, "homedir: "
393 "%s: %s: \"%s\": %d (%s)\n",
394 HOMEDIR_NULLWRAP(parent_func), HOMEDIR_NULLWRAP(func),
395 HOMEDIR_NULLWRAP(filename), save_errno,
396 AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
397 }
398
399 static int
copy_link(const char * dest_file,const char * source_file,const struct stat * st,uid_t uidn,gid_t gidn,void * ctx)400 copy_link(
401 const char *dest_file,
402 const char *source_file,
403 const struct stat *st,
404 uid_t uidn,
405 gid_t gidn,
406 void *ctx )
407 {
408 char *buf = NULL;
409 int rc;
410
411 assert( dest_file != NULL );
412 assert( source_file != NULL );
413 assert( st != NULL );
414 assert( (st->st_mode & S_IFMT) == S_IFLNK );
415
416 Debug( LDAP_DEBUG_TRACE, "homedir: "
417 "copy_link: %s to %s\n",
418 source_file, dest_file );
419 Debug( LDAP_DEBUG_TRACE, "homedir: "
420 "copy_link: %s uid %ld gid %ld\n",
421 dest_file, (long)uidn, (long)gidn );
422
423 /* calloc +1 for terminator */
424 buf = ber_memcalloc_x( 1, st->st_size + 1, ctx );
425 if ( buf == NULL ) {
426 Debug( LDAP_DEBUG_ANY, "homedir: "
427 "copy_link: alloc failed\n" );
428 return 1;
429 }
430 rc = readlink( source_file, buf, st->st_size );
431 if ( rc == -1 ) {
432 report_errno( "copy_link", "readlink", source_file );
433 goto fail;
434 }
435 rc = symlink( buf, dest_file );
436 if ( rc ) {
437 report_errno( "copy_link", "symlink", dest_file );
438 goto fail;
439 }
440 rc = lchown( dest_file, uidn, gidn );
441 if ( rc ) {
442 report_errno( "copy_link", "lchown", dest_file );
443 goto fail;
444 }
445 goto out;
446
447 fail:
448 rc = 1;
449
450 out:
451 if ( buf != NULL ) ber_memfree_x( buf, ctx );
452 return rc;
453 }
454
455 static int
copy_blocks(FILE * source,FILE * dest,const char * source_file,const char * dest_file)456 copy_blocks(
457 FILE *source,
458 FILE *dest,
459 const char *source_file,
460 const char *dest_file )
461 {
462 char buf[4096];
463 size_t nread = 0;
464 int done = 0;
465
466 while ( !done ) {
467 nread = fread( buf, 1, sizeof(buf), source );
468 if ( nread == 0 ) {
469 if ( feof( source ) ) {
470 done = 1;
471 } else if ( ferror( source ) ) {
472 if ( source_file != NULL )
473 Debug( LDAP_DEBUG_ANY, "homedir: "
474 "read error on %s\n",
475 source_file );
476 goto fail;
477 }
478 } else {
479 size_t nwritten = 0;
480 nwritten = fwrite( buf, 1, nread, dest );
481 if ( nwritten < nread ) {
482 if ( dest_file != NULL )
483 Debug( LDAP_DEBUG_ANY, "homedir: "
484 "write error on %s\n",
485 dest_file );
486 goto fail;
487 }
488 }
489 }
490 return 0;
491 fail:
492 return 1;
493 }
494
495 static int
copy_file(const char * dest_file,const char * source_file,uid_t uid,gid_t gid,int mode)496 copy_file(
497 const char *dest_file,
498 const char *source_file,
499 uid_t uid,
500 gid_t gid,
501 int mode )
502 {
503 FILE *source = NULL;
504 FILE *dest = NULL;
505 int rc;
506
507 assert( dest_file != NULL );
508 assert( source_file != NULL );
509
510 Debug( LDAP_DEBUG_TRACE, "homedir: "
511 "copy_file: %s to %s mode 0%o\n",
512 source_file, dest_file, mode );
513 Debug( LDAP_DEBUG_TRACE, "homedir: "
514 "copy_file: %s uid %ld gid %ld\n",
515 dest_file, (long)uid, (long)gid );
516
517 source = fopen( source_file, "rb" );
518 if ( source == NULL ) {
519 report_errno( "copy_file", "fopen", source_file );
520 goto fail;
521 }
522 dest = fopen( dest_file, "wb" );
523 if ( dest == NULL ) {
524 report_errno( "copy_file", "fopen", dest_file );
525 goto fail;
526 }
527
528 rc = copy_blocks( source, dest, source_file, dest_file );
529 if ( rc != 0 ) goto fail;
530
531 fclose( source );
532 source = NULL;
533 rc = fclose( dest );
534 dest = NULL;
535 if ( rc != 0 ) {
536 report_errno( "copy_file", "fclose", dest_file );
537 goto fail;
538 }
539
540 /* set owner/permission */
541 rc = lchown( dest_file, uid, gid );
542 if ( rc != 0 ) {
543 report_errno( "copy_file", "lchown", dest_file );
544 goto fail;
545 }
546 rc = chmod( dest_file, mode );
547 if ( rc != 0 ) {
548 report_errno( "copy_file", "chmod", dest_file );
549 goto fail;
550 }
551
552 rc = 0;
553 goto out;
554 fail:
555 rc = 1;
556 out:
557 if ( source != NULL ) fclose( source );
558 if ( dest != NULL ) fclose( dest );
559 Debug( LDAP_DEBUG_TRACE, "homedir: "
560 "copy_file: %s to %s exit %d\n",
561 source_file, dest_file, rc );
562 return rc;
563 }
564
565 static void
free_name_list(name_list * names,void * ctx)566 free_name_list( name_list *names, void *ctx )
567 {
568 name_list *next;
569
570 while ( names != NULL ) {
571 next = names->next;
572 if ( names->name != NULL ) ber_memfree_x( names->name, ctx );
573 ber_memfree_x( names, ctx );
574 names = next;
575 }
576 }
577
578 static int
grab_names(const char * dir_path,name_list ** names,void * ctx)579 grab_names( const char *dir_path, name_list **names, void *ctx )
580 {
581 int locked = 0;
582 DIR *dir = NULL;
583 struct dirent *entry = NULL;
584 name_list **tail = NULL;
585 int dir_path_len = 0;
586 int rc = 0;
587
588 assert( dir_path != NULL );
589 assert( names != NULL );
590 assert( *names == NULL );
591
592 Debug( LDAP_DEBUG_TRACE, "homedir: "
593 "grab_names: %s\n", dir_path );
594
595 tail = names;
596 dir_path_len = strlen( dir_path );
597 ldap_pvt_thread_mutex_lock( &readdir_mutex );
598 locked = 1;
599
600 dir = opendir( dir_path );
601 if ( dir == NULL ) {
602 report_errno( "grab_names", "opendir", dir_path );
603 goto fail;
604 }
605
606 while ( ( entry = readdir( dir ) ) != NULL ) {
607 /* no d_namelen in ac/dirent.h */
608 int d_namelen = strlen( entry->d_name );
609 int full_len;
610
611 /* Skip . and .. */
612 if ( ( d_namelen == 1 && entry->d_name[0] == '.' ) ||
613 ( d_namelen == 2 && entry->d_name[0] == '.' &&
614 entry->d_name[1] == '.' ) ) {
615 continue;
616 }
617
618 *tail = ber_memcalloc_x( 1, sizeof(**tail), ctx );
619 if ( *tail == NULL ) {
620 Debug( LDAP_DEBUG_ANY, "homedir: "
621 "grab_names: list alloc failed\n" );
622 goto fail;
623 }
624 (*tail)->next = NULL;
625
626 /* +1 for dirsep, +1 for term */
627 full_len = dir_path_len + 1 + d_namelen + 1;
628 (*tail)->name = ber_memalloc_x( full_len, ctx );
629 if ( (*tail)->name == NULL ) {
630 Debug( LDAP_DEBUG_ANY, "homedir: "
631 "grab_names: name alloc failed\n" );
632 goto fail;
633 }
634 snprintf( (*tail)->name, full_len, "%s" LDAP_DIRSEP "%s",
635 dir_path, entry->d_name );
636 Debug( LDAP_DEBUG_TRACE, "homedir: "
637 "grab_names: found \"%s\"\n",
638 (*tail)->name );
639
640 rc = lstat( (*tail)->name, &(*tail)->st );
641 if ( rc ) {
642 report_errno( "grab_names", "lstat", (*tail)->name );
643 goto fail;
644 }
645
646 tail = &(*tail)->next;
647 }
648 closedir( dir );
649 ldap_pvt_thread_mutex_unlock( &readdir_mutex );
650 locked = 0;
651
652 dir = NULL;
653 goto success;
654
655 success:
656 rc = 0;
657 goto out;
658 fail:
659 rc = 1;
660 goto out;
661 out:
662 if ( dir != NULL ) closedir( dir );
663 if ( locked ) ldap_pvt_thread_mutex_unlock( &readdir_mutex );
664 if ( rc != 0 && *names != NULL ) {
665 free_name_list( *names, ctx );
666 *names = NULL;
667 }
668 Debug( LDAP_DEBUG_TRACE, "homedir: "
669 "grab_names: %s exit %d\n",
670 dir_path, rc );
671 return rc;
672 }
673
674 static int
traverse(const char * path,const traverse_cb * cb,void * ctx)675 traverse( const char *path, const traverse_cb *cb, void *ctx )
676 {
677 name_list *next_name = NULL;
678 name_list_list *dir_stack = NULL;
679 name_list_list *next_dir;
680 int rc = 0;
681
682 assert( path != NULL );
683 assert( cb != NULL );
684 assert( cb->pre_func || cb->post_func );
685
686 Debug( LDAP_DEBUG_TRACE, "homedir: "
687 "traverse: %s\n", path );
688
689 dir_stack = ber_memcalloc_x( 1, sizeof(*dir_stack), ctx );
690 if ( dir_stack == NULL ) goto alloc_fail;
691 dir_stack->next = NULL;
692 dir_stack->list = ber_memcalloc_x( 1, sizeof(name_list), ctx );
693 if ( dir_stack->list == NULL ) goto alloc_fail;
694 rc = lstat( path, &dir_stack->list->st );
695 if ( rc != 0 ) {
696 report_errno( "traverse", "lstat", path );
697 goto fail;
698 }
699 dir_stack->list->next = NULL;
700 dir_stack->list->name = ber_strdup_x( path, ctx );
701 if ( dir_stack->list->name == NULL ) goto alloc_fail;
702
703 while ( dir_stack != NULL ) {
704 while ( dir_stack->list != NULL ) {
705 Debug( LDAP_DEBUG_TRACE, "homedir: "
706 "traverse: top of loop with \"%s\"\n",
707 dir_stack->list->name );
708
709 if ( cb->pre_func != NULL ) {
710 traverse_cb_ret cb_rc;
711 cb_rc = cb->pre_func( cb->pre_private, dir_stack->list->name,
712 &dir_stack->list->st, ctx );
713
714 if ( cb_rc == TRAVERSE_CB_DONE ) goto cb_done;
715 if ( cb_rc == TRAVERSE_CB_FAIL ) goto cb_fail;
716 }
717 if ( (dir_stack->list->st.st_mode & S_IFMT) == S_IFDIR ) {
718 /* push dir onto stack */
719 next_dir = dir_stack;
720 dir_stack = ber_memalloc_x( sizeof(*dir_stack), ctx );
721 if ( dir_stack == NULL ) {
722 dir_stack = next_dir;
723 goto alloc_fail;
724 }
725 dir_stack->list = NULL;
726 dir_stack->next = next_dir;
727 rc = grab_names(
728 dir_stack->next->list->name, &dir_stack->list, ctx );
729 if ( rc != 0 ) {
730 Debug( LDAP_DEBUG_ANY, "homedir: "
731 "traverse: grab_names %s failed\n",
732 dir_stack->next->list->name );
733 goto fail;
734 }
735 } else {
736 /* just a file */
737 if ( cb->post_func != NULL ) {
738 traverse_cb_ret cb_rc;
739 cb_rc = cb->post_func( cb->post_private,
740 dir_stack->list->name, &dir_stack->list->st, ctx );
741
742 if ( cb_rc == TRAVERSE_CB_DONE ) goto cb_done;
743 if ( cb_rc == TRAVERSE_CB_FAIL ) goto cb_fail;
744 }
745 next_name = dir_stack->list->next;
746 ber_memfree_x( dir_stack->list->name, ctx );
747 ber_memfree_x( dir_stack->list, ctx );
748 dir_stack->list = next_name;
749 }
750 }
751 /* Time to pop a directory off the stack */
752 next_dir = dir_stack->next;
753 ber_memfree_x( dir_stack, ctx );
754 dir_stack = next_dir;
755 if ( dir_stack != NULL ) {
756 if ( cb->post_func != NULL ) {
757 traverse_cb_ret cb_rc;
758 cb_rc = cb->post_func( cb->post_private, dir_stack->list->name,
759 &dir_stack->list->st, ctx );
760
761 if ( cb_rc == TRAVERSE_CB_DONE ) goto cb_done;
762 if ( cb_rc == TRAVERSE_CB_FAIL ) goto cb_fail;
763 }
764 next_name = dir_stack->list->next;
765 ber_memfree_x( dir_stack->list->name, ctx );
766 ber_memfree_x( dir_stack->list, ctx );
767 dir_stack->list = next_name;
768 }
769 }
770
771 goto success;
772
773 cb_done:
774 Debug( LDAP_DEBUG_TRACE, "homedir: "
775 "traverse: cb signaled completion\n" );
776 success:
777 rc = 0;
778 goto out;
779
780 cb_fail:
781 Debug( LDAP_DEBUG_ANY, "homedir: "
782 "traverse: cb signaled failure\n" );
783 goto fail;
784 alloc_fail:
785 Debug( LDAP_DEBUG_ANY, "homedir: "
786 "traverse: allocation failed\n" );
787 fail:
788 rc = 1;
789 goto out;
790
791 out:
792 while ( dir_stack != NULL ) {
793 free_name_list( dir_stack->list, ctx );
794 next_dir = dir_stack->next;
795 ber_memfree_x( dir_stack, ctx );
796 dir_stack = next_dir;
797 }
798 return rc;
799 }
800
801 static traverse_cb_ret
traverse_copy_pre(void * private,const char * name,const struct stat * st,void * ctx)802 traverse_copy_pre(
803 void *private,
804 const char *name,
805 const struct stat *st,
806 void *ctx )
807 {
808 copy_private *cp = private;
809 char *dest_name = NULL;
810 int source_name_len;
811 int dest_name_len;
812 int rc;
813
814 assert( private != NULL );
815 assert( name != NULL );
816 assert( st != NULL );
817
818 Debug( LDAP_DEBUG_TRACE, "homedir: "
819 "traverse_copy_pre: %s entering\n",
820 name );
821
822 assert( cp->source_prefix_len >= 0 );
823 assert( cp->dest_prefix != NULL );
824 assert( cp->dest_prefix_len > 1 );
825
826 source_name_len = strlen( name );
827 assert( source_name_len >= cp->source_prefix_len );
828 /* +1 for terminator */
829 dest_name_len =
830 source_name_len + cp->dest_prefix_len - cp->source_prefix_len + 1;
831 dest_name = ber_memalloc_x( dest_name_len, ctx );
832 if ( dest_name == NULL ) goto alloc_fail;
833
834 snprintf( dest_name, dest_name_len, "%s%s", cp->dest_prefix,
835 name + cp->source_prefix_len );
836
837 switch ( st->st_mode & S_IFMT ) {
838 case S_IFDIR:
839 rc = mkdir( dest_name, st->st_mode & 06775 );
840 if ( rc ) {
841 int save_errno = errno;
842 switch ( save_errno ) {
843 case EEXIST:
844 /* directory already present; nothing to do */
845 goto exists;
846 break;
847 case ENOENT:
848 /* FIXME: should mkdir -p here */
849 /* fallthrough for now */
850 default:
851 report_errno( "traverse_copy_pre", "mkdir", dest_name );
852 goto fail;
853 }
854 }
855 rc = lchown( dest_name, cp->uidn, cp->gidn );
856 if ( rc ) {
857 report_errno( "traverse_copy_pre", "lchown", dest_name );
858 goto fail;
859 }
860 rc = chmod( dest_name, st->st_mode & 07777 );
861 if ( rc ) {
862 report_errno( "traverse_copy_pre", "chmod", dest_name );
863 goto fail;
864 }
865 break;
866 case S_IFREG:
867 rc = copy_file(
868 dest_name, name, cp->uidn, cp->gidn, st->st_mode & 07777 );
869 if ( rc ) goto fail;
870 break;
871 case S_IFIFO:
872 rc = mkfifo( dest_name, 0700 );
873 if ( rc ) {
874 report_errno( "traverse_copy_pre", "mkfifo", dest_name );
875 goto fail;
876 }
877 rc = lchown( dest_name, cp->uidn, cp->gidn );
878 if ( rc ) {
879 report_errno( "traverse_copy_pre", "lchown", dest_name );
880 goto fail;
881 }
882 rc = chmod( dest_name, st->st_mode & 07777 );
883 if ( rc ) {
884 report_errno( "traverse_copy_pre", "chmod", dest_name );
885 goto fail;
886 }
887 break;
888 case S_IFLNK:
889 rc = copy_link( dest_name, name, st, cp->uidn, cp->gidn, ctx );
890 if ( rc ) goto fail;
891 break;
892 default:
893 Debug( LDAP_DEBUG_TRACE, "homedir: "
894 "traverse_copy_pre: skipping special: %s\n",
895 name );
896 }
897
898 goto success;
899
900 alloc_fail:
901 Debug( LDAP_DEBUG_ANY, "homedir: "
902 "traverse_copy_pre: allocation failed\n" );
903 fail:
904 rc = TRAVERSE_CB_FAIL;
905 goto out;
906
907 exists:
908 Debug( LDAP_DEBUG_TRACE, "homedir: "
909 "traverse_copy_pre: \"%s\" already exists,"
910 " skipping the rest\n",
911 dest_name );
912 rc = TRAVERSE_CB_DONE;
913 goto out;
914
915 success:
916 rc = TRAVERSE_CB_CONTINUE;
917 out:
918 if ( dest_name != NULL ) ber_memfree_x( dest_name, ctx );
919 Debug( LDAP_DEBUG_TRACE, "homedir: "
920 "traverse_copy_pre: exit %d\n", rc );
921 return rc;
922 }
923
924 static int
copy_tree(const char * dest_path,const char * source_path,uid_t uidn,gid_t gidn,void * ctx)925 copy_tree(
926 const char *dest_path,
927 const char *source_path,
928 uid_t uidn,
929 gid_t gidn,
930 void *ctx )
931 {
932 traverse_cb cb;
933 copy_private cp;
934 int rc;
935
936 assert( dest_path != NULL );
937 assert( source_path != NULL );
938
939 Debug( LDAP_DEBUG_TRACE, "homedir: "
940 "copy_tree: %s to %s entering\n",
941 source_path, dest_path );
942
943 cb.pre_func = traverse_copy_pre;
944 cb.post_func = NULL;
945 cb.pre_private = &cp;
946 cb.post_private = NULL;
947
948 cp.source_prefix_len = strlen( source_path );
949 cp.dest_prefix = dest_path;
950 cp.dest_prefix_len = strlen( dest_path );
951 cp.uidn = uidn;
952 cp.gidn = gidn;
953
954 if ( cp.source_prefix_len <= cp.dest_prefix_len &&
955 strncmp( source_path, dest_path, cp.source_prefix_len ) == 0 &&
956 ( cp.source_prefix_len == cp.dest_prefix_len ||
957 dest_path[cp.source_prefix_len] == LDAP_DIRSEP[0] ) ) {
958 Debug( LDAP_DEBUG_ANY, "homedir: "
959 "copy_tree: aborting: %s contains %s\n",
960 source_path, dest_path );
961 return 1;
962 }
963
964 rc = traverse( source_path, &cb, ctx );
965
966 Debug( LDAP_DEBUG_TRACE, "homedir: "
967 "copy_tree: %s exit %d\n", source_path,
968 rc );
969
970 return rc;
971 }
972
973 static int
homedir_provision(const char * dest_path,const char * skel_path,uid_t uidn,gid_t gidn,void * ctx)974 homedir_provision(
975 const char *dest_path,
976 const char *skel_path,
977 uid_t uidn,
978 gid_t gidn,
979 void *ctx )
980 {
981 int rc;
982
983 assert( dest_path != NULL );
984
985 Debug( LDAP_DEBUG_TRACE, "homedir: "
986 "homedir_provision: %s from skeleton %s\n",
987 dest_path, skel_path == NULL ? "(none)" : skel_path );
988 Debug( LDAP_DEBUG_TRACE, "homedir: "
989 "homedir_provision: %s uidn %ld gidn %ld\n",
990 dest_path, (long)uidn, (long)gidn );
991
992 if ( skel_path == NULL ) {
993 rc = mkdir( dest_path, 0700 );
994 if ( rc ) {
995 int save_errno = errno;
996 switch ( save_errno ) {
997 case EEXIST:
998 /* directory already present; nothing to do */
999 /* but down chown either */
1000 rc = 0;
1001 goto out;
1002 break;
1003 default:
1004 report_errno( "provision_homedir", "mkdir", dest_path );
1005 goto fail;
1006 }
1007 }
1008 rc = lchown( dest_path, uidn, gidn );
1009 if ( rc ) {
1010 report_errno( "provision_homedir", "lchown", dest_path );
1011 goto fail;
1012 }
1013
1014 } else {
1015 rc = copy_tree( dest_path, skel_path, uidn, gidn, ctx );
1016 }
1017
1018 goto out;
1019
1020 fail:
1021 rc = 1;
1022 goto out;
1023 out:
1024 Debug( LDAP_DEBUG_TRACE, "homedir: "
1025 "homedir_provision: %s to %s exit %d\n",
1026 skel_path, dest_path, rc );
1027 return rc;
1028 }
1029
1030 /* traverse func for rm -rf */
1031 static traverse_cb_ret
traverse_remove_post(void * private,const char * name,const struct stat * st,void * ctx)1032 traverse_remove_post(
1033 void *private,
1034 const char *name,
1035 const struct stat *st,
1036 void *ctx )
1037 {
1038 int rc;
1039
1040 Debug( LDAP_DEBUG_TRACE, "homedir: "
1041 "traverse_remove_post: %s entering\n",
1042 name );
1043
1044 if ( (st->st_mode & S_IFMT) == S_IFDIR ) {
1045 rc = rmdir( name );
1046 if ( rc != 0 ) {
1047 report_errno( "traverse_remove_post", "rmdir", name );
1048 goto fail;
1049 }
1050 } else {
1051 rc = unlink( name );
1052 if ( rc != 0 ) {
1053 report_errno( "traverse_remove_post", "unlink", name );
1054 goto fail;
1055 }
1056 }
1057
1058 Debug( LDAP_DEBUG_TRACE, "homedir: "
1059 "traverse_remove_post: %s exit continue\n",
1060 name );
1061 return TRAVERSE_CB_CONTINUE;
1062
1063 fail:
1064 Debug( LDAP_DEBUG_TRACE, "homedir: "
1065 "traverse_remove_post: %s exit failure\n",
1066 name );
1067 return TRAVERSE_CB_FAIL;
1068 }
1069
1070 static int
delete_tree(const char * path,void * ctx)1071 delete_tree( const char *path, void *ctx )
1072 {
1073 const static traverse_cb cb = { NULL, traverse_remove_post, NULL, NULL };
1074 int rc;
1075
1076 assert( path != NULL );
1077
1078 Debug( LDAP_DEBUG_TRACE, "homedir: "
1079 "delete_tree: %s entering\n", path );
1080
1081 rc = traverse( path, &cb, ctx );
1082
1083 Debug( LDAP_DEBUG_TRACE, "homedir: "
1084 "delete_tree: %s exit %d\n", path, rc );
1085
1086 return rc;
1087 }
1088
1089 static int
get_tar_name(const char * path,const char * tar_path,char * tar_name,int name_size)1090 get_tar_name(
1091 const char *path,
1092 const char *tar_path,
1093 char *tar_name,
1094 int name_size )
1095 {
1096 int rc = 0;
1097 const char *ch;
1098 int fd = -1;
1099 int counter = 0;
1100 time_t now;
1101
1102 assert( path != NULL );
1103 assert( tar_path != NULL );
1104 assert( tar_name != NULL );
1105
1106 for ( ch = path + strlen( path );
1107 *ch != LDAP_DIRSEP[0] && ch > path;
1108 --ch )
1109 ;
1110 if ( ch <= path || strlen( ch ) < 2 ) {
1111 Debug( LDAP_DEBUG_ANY, "homedir: "
1112 "get_tar_name: unable to construct a tar name from input "
1113 "path \"%s\"\n",
1114 path );
1115 goto fail;
1116 }
1117 ++ch; /* skip past sep */
1118 time( &now );
1119
1120 while ( fd < 0 ) {
1121 snprintf( tar_name, name_size, "%s" LDAP_DIRSEP "%s-%ld-%d.tar",
1122 tar_path, ch, (long)now, counter );
1123 fd = open( tar_name, O_WRONLY|O_CREAT|O_EXCL, 0600 );
1124 if ( fd < 0 ) {
1125 int save_errno = errno;
1126 if ( save_errno != EEXIST ) {
1127 report_errno( "get_tar_name", "open", tar_name );
1128 goto fail;
1129 }
1130 ++counter;
1131 }
1132 }
1133
1134 rc = 0;
1135 goto out;
1136
1137 fail:
1138 rc = 1;
1139 *tar_name = '\0';
1140 out:
1141 if ( fd >= 0 ) close( fd );
1142 return rc;
1143 }
1144
1145 /* traverse func for rechown */
1146 static traverse_cb_ret
traverse_chown_pre(void * private,const char * name,const struct stat * st,void * ctx)1147 traverse_chown_pre(
1148 void *private,
1149 const char *name,
1150 const struct stat *st,
1151 void *ctx )
1152 {
1153 int rc;
1154 chown_private *cp = private;
1155 uid_t set_uidn = -1;
1156 gid_t set_gidn = -1;
1157
1158 assert( private != NULL );
1159 assert( name != NULL );
1160 assert( st != NULL );
1161
1162 Debug( LDAP_DEBUG_TRACE, "homedir: "
1163 "traverse_chown_pre: %s entering\n",
1164 name );
1165
1166 if ( st->st_uid == cp->old_uidn ) set_uidn = cp->new_uidn;
1167 if ( st->st_gid == cp->old_gidn ) set_gidn = cp->new_gidn;
1168
1169 if ( set_uidn != (uid_t)-1 || set_gidn != (gid_t)-1 ) {
1170 rc = lchown( name, set_uidn, set_gidn );
1171 if ( rc ) {
1172 report_errno( "traverse_chown_pre", "lchown", name );
1173 goto fail;
1174 }
1175 }
1176
1177 Debug( LDAP_DEBUG_TRACE, "homedir: "
1178 "traverse_chown_pre: %s exit continue\n",
1179 name );
1180 return TRAVERSE_CB_CONTINUE;
1181
1182 fail:
1183 Debug( LDAP_DEBUG_TRACE, "homedir: "
1184 "traverse_chown_pre: %s exit failure\n",
1185 name );
1186 return TRAVERSE_CB_FAIL;
1187 }
1188
1189 static int
chown_tree(const char * path,uid_t old_uidn,uid_t new_uidn,gid_t old_gidn,gid_t new_gidn,void * ctx)1190 chown_tree(
1191 const char *path,
1192 uid_t old_uidn,
1193 uid_t new_uidn,
1194 gid_t old_gidn,
1195 gid_t new_gidn,
1196 void *ctx )
1197 {
1198 traverse_cb cb;
1199 chown_private cp;
1200 int rc;
1201
1202 assert( path != NULL );
1203
1204 Debug( LDAP_DEBUG_TRACE, "homedir: "
1205 "chown_tree: %s entering\n", path );
1206
1207 cb.pre_func = traverse_chown_pre;
1208 cb.post_func = NULL;
1209 cb.pre_private = &cp;
1210 cb.post_private = NULL;
1211
1212 cp.old_uidn = old_uidn;
1213 cp.new_uidn = new_uidn;
1214 cp.old_gidn = old_gidn;
1215 cp.new_gidn = new_gidn;
1216
1217 rc = traverse( path, &cb, ctx );
1218
1219 Debug( LDAP_DEBUG_TRACE, "homedir: "
1220 "chown_tree: %s exit %d\n", path, rc );
1221
1222 return rc;
1223 }
1224
1225 static int
homedir_rename(const char * source_path,const char * dest_path)1226 homedir_rename( const char *source_path, const char *dest_path )
1227 {
1228 int rc = 0;
1229
1230 assert( source_path != NULL );
1231 assert( dest_path != NULL );
1232
1233 Debug( LDAP_DEBUG_TRACE, "homedir: "
1234 "homedir_rename: %s to %s\n",
1235 source_path, dest_path );
1236 rc = rename( source_path, dest_path );
1237 if ( rc != 0 ) {
1238 char ebuf[1024];
1239 int save_errno = errno;
1240
1241 Debug( LDAP_DEBUG_ANY, "homedir: "
1242 "homedir_rename: rename(\"%s\", \"%s\"): (%s)\n",
1243 source_path, dest_path,
1244 AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
1245 }
1246
1247 Debug( LDAP_DEBUG_TRACE, "homedir: "
1248 "homedir_rename: %s to %s exit %d\n",
1249 source_path, dest_path, rc );
1250 return rc;
1251 }
1252
1253 /* FIXME: This assumes ASCII; needs fixing for z/OS */
1254 static int
tar_set_header(ustar_header * tar,const struct stat * st,const char * name)1255 tar_set_header( ustar_header *tar, const struct stat *st, const char *name )
1256 {
1257 int name_len;
1258 int rc;
1259 const char *ch, *end;
1260
1261 assert( tar != NULL );
1262 assert( st != NULL );
1263 assert( name != NULL );
1264 assert( sizeof(*tar) == 512 );
1265 assert( sizeof(tar->name) == 100 );
1266 assert( sizeof(tar->prefix) == 155 );
1267 assert( sizeof(tar->checksum) == 8 );
1268
1269 memset( tar, 0, sizeof(*tar) );
1270
1271 assert( name[0] == LDAP_DIRSEP[0] );
1272 name += 1; /* skip leading / */
1273
1274 name_len = strlen( name );
1275
1276 /* fits in tar->name? */
1277 /* Yes, name and prefix do not need a trailing nul. */
1278 if ( name_len <= 100 ) {
1279 strncpy( tar->name, name, 100 );
1280
1281 /* try fit in tar->name + tar->prefix */
1282 } else {
1283 /* try to find something to stick into tar->name */
1284 for ( ch = name + name_len - 100, end = name + name_len;
1285 ch < end && *ch != LDAP_DIRSEP[0];
1286 ++ch )
1287 ;
1288 if ( end - ch > 0 ) /* +1 skip past sep */
1289 ch++;
1290 else {
1291 /* reset; name too long for UStar */
1292 Debug( LDAP_DEBUG_ANY, "homedir: "
1293 "tar_set_header: name too long: \"%s\"\n",
1294 name );
1295 ch = name + name_len - 100;
1296 }
1297 strncpy( tar->name, ch + 1, 100 );
1298 {
1299 int prefix_len = ( ch - 1 ) - name;
1300 if ( prefix_len > 155 ) prefix_len = 155;
1301 strncpy( tar->prefix, name, prefix_len );
1302 }
1303 }
1304
1305 snprintf( tar->mode, 8, "%06lo ", (long)st->st_mode & 07777 );
1306 snprintf( tar->uid, 8, "%06lo ", (long)st->st_uid );
1307 snprintf( tar->gid, 8, "%06lo ", (long)st->st_gid );
1308 snprintf( tar->mtime, 12, "%010lo ", (long)st->st_mtime );
1309 snprintf( tar->size, 12, "%010lo ", (long)0 );
1310 switch ( st->st_mode & S_IFMT ) {
1311 case S_IFREG:
1312 tar->typeflag[0] = '0';
1313 snprintf( tar->size, 12, "%010lo ", (long)st->st_size );
1314 break;
1315 case S_IFLNK:
1316 tar->typeflag[0] = '2';
1317 rc = readlink( name - 1, tar->linkname, 99 );
1318 if ( rc == -1 ) {
1319 report_errno( "tar_set_header", "readlink", name );
1320 goto fail;
1321 }
1322 break;
1323 case S_IFCHR:
1324 tar->typeflag[0] = '3';
1325 /* FIXME: this is probably wrong but shouldn't likely be an issue */
1326 snprintf( tar->devmajor, 8, "%06lo ", (long)st->st_rdev >> 16 );
1327 snprintf( tar->devminor, 8, "%06lo ", (long)st->st_rdev & 0xffff );
1328 break;
1329 case S_IFBLK:
1330 tar->typeflag[0] = '4';
1331 /* FIXME: this is probably wrong but shouldn't likely be an issue */
1332 snprintf( tar->devmajor, 8, "%06lo ", (long)st->st_rdev >> 16 );
1333 snprintf( tar->devminor, 8, "%06lo ", (long)st->st_rdev & 0xffff );
1334 break;
1335 case S_IFDIR:
1336 tar->typeflag[0] = '5';
1337 break;
1338 case S_IFIFO:
1339 tar->typeflag[0] = '6';
1340 break;
1341 default:
1342 goto fail;
1343 }
1344 snprintf( tar->magic, 6, "ustar" );
1345 tar->version[0] = '0';
1346 tar->version[1] = '0';
1347
1348 {
1349 unsigned char *uch = (unsigned char *)tar;
1350 unsigned char *uend = uch + 512;
1351 unsigned long sum = 0;
1352
1353 memset( &tar->checksum, ' ', sizeof(tar->checksum) );
1354
1355 for ( ; uch < uend; ++uch )
1356 sum += *uch;
1357
1358 /* zero-padded, six octal digits, followed by NUL then space (!) */
1359 /* Yes, that's terminated exactly reverse of the others. */
1360 snprintf( tar->checksum, sizeof(tar->checksum) - 1, "%06lo", sum );
1361 }
1362
1363 return 0;
1364 fail:
1365 return 1;
1366 }
1367
1368 static traverse_cb_ret
traverse_tar_pre(void * private,const char * name,const struct stat * st,void * ctx)1369 traverse_tar_pre(
1370 void *private,
1371 const char *name,
1372 const struct stat *st,
1373 void *ctx )
1374 {
1375 int rc;
1376 traverse_cb_ret cbrc;
1377 tar_private *tp = private;
1378 ustar_header tar;
1379 FILE *source = NULL;
1380
1381 assert( private != NULL );
1382 assert( name != NULL );
1383 assert( st != NULL );
1384
1385 Debug( LDAP_DEBUG_TRACE, "homedir: "
1386 "traverse_tar_pre: %s entering\n", name );
1387
1388 switch ( st->st_mode & S_IFMT ) {
1389 case S_IFREG:
1390 if ( sizeof(st->st_size) > 4 && ( st->st_size >> 33 ) >= 1 ) {
1391 Debug( LDAP_DEBUG_TRACE, "homedir: "
1392 "traverse_tar_pre: %s is larger than 8GiB POSIX UStar "
1393 "file size limit\n",
1394 name );
1395 goto fail;
1396 }
1397 /* fallthrough */
1398 case S_IFDIR:
1399 case S_IFLNK:
1400 case S_IFIFO:
1401 case S_IFCHR:
1402 case S_IFBLK:
1403 rc = tar_set_header( &tar, st, name );
1404 if ( rc ) goto fail;
1405 break;
1406 default:
1407 Debug( LDAP_DEBUG_TRACE, "homedir: "
1408 "traverse_tar_pre: skipping \"%s\" mode %o\n",
1409 name, st->st_mode );
1410 goto done;
1411 }
1412
1413 rc = fwrite( &tar, 1, 512, tp->file );
1414 if ( rc != 512 ) {
1415 Debug( LDAP_DEBUG_TRACE, "homedir: "
1416 "traverse_tar_pre: write error in tar header\n" );
1417 goto fail;
1418 }
1419
1420 if ( (st->st_mode & S_IFMT) == S_IFREG ) {
1421 source = fopen( name, "rb" );
1422 if ( source == NULL ) {
1423 report_errno( "traverse_tar_pre", "fopen", name );
1424 goto fail;
1425 }
1426 rc = copy_blocks( source, tp->file, name, tp->name );
1427 if ( rc != 0 ) goto fail;
1428 fclose( source );
1429 source = NULL;
1430 }
1431
1432 { /* advance to end of record */
1433 off_t pos = ftello( tp->file );
1434 if ( pos == -1 ) {
1435 report_errno( "traverse_tar_pre", "ftello", tp->name );
1436 goto fail;
1437 }
1438 pos += ( 512 - ( pos % 512 ) ) % 512;
1439 rc = fseeko( tp->file, pos, SEEK_SET );
1440 if ( rc != 0 ) {
1441 report_errno( "traverse_tar_pre", "fseeko", tp->name );
1442 goto fail;
1443 }
1444 }
1445
1446 done:
1447 Debug( LDAP_DEBUG_TRACE, "homedir: "
1448 "traverse_tar_pre: %s exit continue\n",
1449 name );
1450 cbrc = TRAVERSE_CB_CONTINUE;
1451 goto out;
1452 fail:
1453 Debug( LDAP_DEBUG_TRACE, "homedir: "
1454 "traverse_tar_pre: %s exit failure\n",
1455 name );
1456 cbrc = TRAVERSE_CB_FAIL;
1457
1458 out:
1459 if ( source != NULL ) fclose( source );
1460 return cbrc;
1461 }
1462
1463 static int
tar_tree(const char * path,const char * tar_name,void * ctx)1464 tar_tree( const char *path, const char *tar_name, void *ctx )
1465 {
1466 traverse_cb cb;
1467 tar_private tp;
1468 int rc;
1469
1470 assert( path != NULL );
1471 assert( tar_name != NULL );
1472
1473 Debug( LDAP_DEBUG_TRACE, "homedir: "
1474 "tar_tree: %s into %s entering\n", path,
1475 tar_name );
1476
1477 cb.pre_func = traverse_tar_pre;
1478 cb.post_func = NULL;
1479 cb.pre_private = &tp;
1480 cb.post_private = NULL;
1481
1482 tp.name = tar_name;
1483 tp.file = fopen( tar_name, "wb" );
1484 if ( tp.file == NULL ) {
1485 report_errno( "tar_tree", "fopen", tar_name );
1486 goto fail;
1487 }
1488
1489 rc = traverse( path, &cb, ctx );
1490 if ( rc != 0 ) goto fail;
1491
1492 {
1493 off_t pos = ftello( tp.file );
1494 if ( pos == -1 ) {
1495 report_errno( "tar_tree", "ftello", tp.name );
1496 goto fail;
1497 }
1498 pos += 1024; /* two zero records */
1499 pos += ( 10240 - ( pos % 10240 ) ) % 10240;
1500 rc = ftruncate( fileno( tp.file ), pos );
1501 if ( rc != 0 ) {
1502 report_errno( "tar_tree", "ftrunctate", tp.name );
1503 goto fail;
1504 }
1505 }
1506
1507 rc = fclose( tp.file );
1508 tp.file = NULL;
1509 if ( rc != 0 ) {
1510 report_errno( "tar_tree", "fclose", tp.name );
1511 goto fail;
1512 }
1513 goto out;
1514
1515 fail:
1516 rc = 1;
1517 out:
1518 Debug( LDAP_DEBUG_TRACE, "homedir: "
1519 "tar_tree: %s exit %d\n", path, rc );
1520 if ( tp.file != NULL ) fclose( tp.file );
1521 return rc;
1522 }
1523
1524 static int
homedir_deprovision(const homedir_data * data,const char * path,void * ctx)1525 homedir_deprovision( const homedir_data *data, const char *path, void *ctx )
1526 {
1527 int rc = 0;
1528 char tar_name[1024];
1529
1530 assert( data != NULL );
1531 assert( path != NULL );
1532
1533 Debug( LDAP_DEBUG_TRACE, "homedir: "
1534 "homedir_deprovision: %s entering\n",
1535 path );
1536
1537 switch ( data->style ) {
1538 case DEL_IGNORE:
1539 Debug( LDAP_DEBUG_TRACE, "homedir: "
1540 "homedir_deprovision: style is ignore\n" );
1541 break;
1542 case DEL_ARCHIVE:
1543 if ( data->archive_path == NULL ) {
1544 Debug( LDAP_DEBUG_ANY, "homedir: "
1545 "homedir_deprovision: archive path not set\n" );
1546 goto fail;
1547 }
1548 rc = get_tar_name( path, data->archive_path, tar_name, 1024 );
1549 if ( rc != 0 ) goto fail;
1550 rc = tar_tree( path, tar_name, ctx );
1551 if ( rc != 0 ) {
1552 Debug( LDAP_DEBUG_ANY, "homedir: "
1553 "homedir_deprovision: archive failed, not deleting\n" );
1554 goto fail;
1555 }
1556 /* fall-through */
1557 case DEL_DELETE:
1558 rc = delete_tree( path, ctx );
1559 break;
1560 default:
1561 abort();
1562 }
1563
1564 rc = 0;
1565 goto out;
1566
1567 fail:
1568 rc = 1;
1569 out:
1570 Debug( LDAP_DEBUG_TRACE, "homedir: "
1571 "homedir_deprovision: %s leaving\n",
1572 path );
1573
1574 return rc;
1575 }
1576
1577 /* FIXME: This assumes ASCII; needs fixing for z/OS */
1578 /* FIXME: This should also be in a slapd library function somewhere */
1579 #define MAX_MATCHES ( 10 )
1580 static int
homedir_match(const homedir_regexp * r,const char * homedir,char * result,size_t result_size)1581 homedir_match(
1582 const homedir_regexp *r,
1583 const char *homedir,
1584 char *result,
1585 size_t result_size )
1586 {
1587 int rc;
1588 int n;
1589 regmatch_t matches[MAX_MATCHES];
1590 char *resc, *repc;
1591
1592 assert( r != NULL );
1593 assert( homedir != NULL );
1594 assert( result_size > 1 );
1595
1596 memset( matches, 0, sizeof(matches) );
1597 rc = regexec( &r->compiled, homedir, MAX_MATCHES, matches, 0 );
1598 if ( rc ) {
1599 if ( rc != REG_NOMATCH ) {
1600 char msg[256];
1601 regerror( rc, &r->compiled, msg, sizeof(msg) );
1602 Debug( LDAP_DEBUG_ANY, "homedir_match: "
1603 "%s\n", msg );
1604 }
1605 return rc;
1606 }
1607
1608 for ( resc = result, repc = r->replace;
1609 result_size > 1 && *repc != '\0';
1610 ++repc, ++resc, --result_size ) {
1611 switch ( *repc ) {
1612 case '$':
1613 ++repc;
1614 n = ( *repc ) - '0';
1615 if ( n < 0 || n > ( MAX_MATCHES - 1 ) ||
1616 matches[n].rm_so < 0 ) {
1617 Debug( LDAP_DEBUG_ANY, "homedir: "
1618 "invalid regex term expansion in \"%s\" "
1619 "at char %ld, n is %d\n",
1620 r->replace, (long)( repc - r->replace ), n );
1621 return 1;
1622 }
1623 {
1624 size_t match_len = matches[n].rm_eo - matches[n].rm_so;
1625 const char *match_start = homedir + matches[n].rm_so;
1626 if ( match_len >= result_size ) goto too_long;
1627
1628 memcpy( resc, match_start, match_len );
1629 result_size -= match_len;
1630 resc += match_len - 1;
1631 }
1632 break;
1633
1634 case '\\':
1635 ++repc;
1636 /* fallthrough */
1637
1638 default:
1639 *resc = *repc;
1640 }
1641 }
1642 *resc = '\0';
1643 if ( *repc != '\0' ) goto too_long;
1644
1645 return 0;
1646
1647 too_long:
1648 Debug( LDAP_DEBUG_ANY, "homedir: "
1649 "regex expansion of %s too long\n",
1650 r->replace );
1651 *result = '\0';
1652 return 1;
1653 }
1654
1655 /* Sift through an entry for interesting values
1656 * return 0 on success and set vars
1657 * return 1 if homedir is not present or not valid
1658 * sets presence if any homedir attributes are noticed
1659 */
1660 static int
harvest_values(const homedir_data * data,const Entry * e,char * home_buf,int home_buf_size,uid_t * uidn,gid_t * gidn,int * presence)1661 harvest_values(
1662 const homedir_data *data,
1663 const Entry *e,
1664 char *home_buf,
1665 int home_buf_size,
1666 uid_t *uidn,
1667 gid_t *gidn,
1668 int *presence )
1669 {
1670 Attribute *a;
1671 char *homedir = NULL;
1672
1673 assert( data != NULL );
1674 assert( e != NULL );
1675 assert( home_buf != NULL );
1676 assert( home_buf_size > 1 );
1677 assert( uidn != NULL );
1678 assert( gidn != NULL );
1679 assert( presence != NULL );
1680
1681 *presence = 0;
1682 if ( e == NULL ) return 1;
1683 *uidn = 0;
1684 *gidn = 0;
1685
1686 for ( a = e->e_attrs; a->a_next != NULL; a = a->a_next ) {
1687 if ( a->a_desc == data->home_ad ) {
1688 homedir = a->a_vals[0].bv_val;
1689 *presence = 1;
1690 } else if ( a->a_desc == data->uidn_ad ) {
1691 *uidn = (uid_t)strtol( a->a_vals[0].bv_val, NULL, 10 );
1692 *presence = 1;
1693 } else if ( a->a_desc == data->gidn_ad ) {
1694 *gidn = (gid_t)strtol( a->a_vals[0].bv_val, NULL, 10 );
1695 *presence = 1;
1696 }
1697 }
1698 if ( homedir != NULL ) {
1699 homedir_regexp *r;
1700
1701 for ( r = data->regexps; r != NULL; r = r->next ) {
1702 int rc = homedir_match( r, homedir, home_buf, home_buf_size );
1703 if ( rc == 0 ) return 0;
1704 }
1705 }
1706
1707 return 1;
1708 }
1709
1710 static int
homedir_mod_cleanup(Operation * op,SlapReply * rs)1711 homedir_mod_cleanup( Operation *op, SlapReply *rs )
1712 {
1713 slap_callback *cb = NULL;
1714 slap_callback **cbp = NULL;
1715 homedir_cb_data *cb_data = NULL;
1716 Entry *e = NULL;
1717
1718 Debug( LDAP_DEBUG_TRACE, "homedir: "
1719 "homedir_mod_cleanup: entering\n" );
1720
1721 for ( cbp = &op->o_callback;
1722 *cbp != NULL && (*cbp)->sc_cleanup != homedir_mod_cleanup;
1723 cbp = &(*cbp)->sc_next )
1724 ;
1725
1726 if ( *cbp == NULL ) goto out;
1727 cb = *cbp;
1728
1729 cb_data = (homedir_cb_data *)cb->sc_private;
1730 e = cb_data->entry;
1731
1732 Debug( LDAP_DEBUG_TRACE, "homedir: "
1733 "homedir_mod_cleanup: found <%s>\n",
1734 e->e_nname.bv_val );
1735 entry_free( e );
1736 op->o_tmpfree( cb_data, op->o_tmpmemctx );
1737 *cbp = cb->sc_next;
1738 op->o_tmpfree( cb, op->o_tmpmemctx );
1739
1740 out:
1741
1742 Debug( LDAP_DEBUG_TRACE, "homedir: "
1743 "homedir_mod_cleanup: leaving\n" );
1744 return SLAP_CB_CONTINUE;
1745 }
1746
1747 static int
homedir_mod_response(Operation * op,SlapReply * rs)1748 homedir_mod_response( Operation *op, SlapReply *rs )
1749 {
1750 slap_overinst *on = NULL;
1751 homedir_data *data = NULL;
1752 slap_callback *cb = NULL;
1753 homedir_cb_data *cb_data = NULL;
1754 Entry *e = NULL;
1755 int rc = SLAP_CB_CONTINUE;
1756
1757 Debug( LDAP_DEBUG_TRACE, "homedir: "
1758 "homedir_mod_response: entering\n" );
1759
1760 if ( rs->sr_err != LDAP_SUCCESS ) {
1761 Debug( LDAP_DEBUG_TRACE, "homedir: "
1762 "homedir_mod_response: op was not successful\n" );
1763 goto out;
1764 }
1765
1766 /* Retrieve stashed entry */
1767 for ( cb = op->o_callback;
1768 cb != NULL && cb->sc_cleanup != homedir_mod_cleanup;
1769 cb = cb->sc_next )
1770 ;
1771 if ( cb == NULL ) goto out;
1772 cb_data = (homedir_cb_data *)cb->sc_private;
1773 e = cb_data->entry;
1774 on = cb_data->on;
1775 data = on->on_bi.bi_private;
1776 assert( e != NULL );
1777 assert( data != NULL );
1778 Debug( LDAP_DEBUG_TRACE, "homedir: "
1779 "homedir_mod_response: found <%s>\n",
1780 e->e_nname.bv_val );
1781
1782 switch ( op->o_tag ) {
1783 case LDAP_REQ_DELETE: {
1784 char home_buf[1024];
1785 uid_t uidn = 0;
1786 gid_t gidn = 0;
1787 int presence;
1788
1789 Debug( LDAP_DEBUG_TRACE, "homedir: "
1790 "homedir_mod_response: successful delete found\n" );
1791 rc = harvest_values( data, e, home_buf, sizeof(home_buf), &uidn,
1792 &gidn, &presence );
1793 if ( rc == 0 && uidn >= data->min_uid ) {
1794 homedir_deprovision( data, home_buf, op->o_tmpmemctx );
1795 } else {
1796 Debug( LDAP_DEBUG_TRACE, "homedir: "
1797 "homedir_mod_response: skipping\n" );
1798 }
1799 rc = SLAP_CB_CONTINUE;
1800 break;
1801 }
1802
1803 case LDAP_REQ_MODIFY:
1804 case LDAP_REQ_MODRDN: {
1805 Operation nop = *op;
1806 Entry *old_entry = e;
1807 Entry *new_entry = NULL;
1808 Entry *etmp;
1809 char old_home[1024];
1810 char new_home[1024];
1811 uid_t old_uidn, new_uidn;
1812 uid_t old_gidn, new_gidn;
1813 int old_valid = 0;
1814 int new_valid = 0;
1815 int old_presence, new_presence;
1816
1817 Debug( LDAP_DEBUG_TRACE, "homedir: "
1818 "homedir_mod_response: successful modify/modrdn found\n" );
1819
1820 /* retrieve the revised entry */
1821 nop.o_bd = on->on_info->oi_origdb;
1822 rc = overlay_entry_get_ov(
1823 &nop, &op->o_req_ndn, NULL, NULL, 0, &etmp, on );
1824 if ( etmp != NULL ) {
1825 new_entry = entry_dup( etmp );
1826 overlay_entry_release_ov( &nop, etmp, 0, on );
1827 }
1828 if ( rc || new_entry == NULL ) {
1829 Debug( LDAP_DEBUG_ANY, "homedir: "
1830 "homedir_mod_response: unable to get revised <%s>\n",
1831 op->o_req_ndn.bv_val );
1832 if ( new_entry != NULL ) {
1833 entry_free( new_entry );
1834 new_entry = NULL;
1835 }
1836 }
1837
1838 /* analyze old and new */
1839 rc = harvest_values( data, old_entry, old_home, 1024, &old_uidn,
1840 &old_gidn, &old_presence );
1841 if ( rc == 0 && old_uidn >= data->min_uid ) old_valid = 1;
1842 if ( new_entry != NULL ) {
1843 rc = harvest_values( data, new_entry, new_home, 1024, &new_uidn,
1844 &new_gidn, &new_presence );
1845 if ( rc == 0 && new_uidn >= data->min_uid ) new_valid = 1;
1846 entry_free( new_entry );
1847 new_entry = NULL;
1848 }
1849
1850 if ( new_valid && !old_valid ) { /* like an add */
1851 if ( old_presence )
1852 Debug( LDAP_DEBUG_TRACE, "homedir: "
1853 "homedir_mod_response: old entry is now valid\n" );
1854 Debug( LDAP_DEBUG_TRACE, "homedir: "
1855 "homedir_mod_response: treating like an add\n" );
1856 homedir_provision( new_home, data->skeleton_path, new_uidn,
1857 new_gidn, op->o_tmpmemctx );
1858
1859 } else if ( old_valid && !new_valid &&
1860 !new_presence ) { /* like a del */
1861 Debug( LDAP_DEBUG_TRACE, "homedir: "
1862 "homedir_mod_response: treating like a del\n" );
1863 homedir_deprovision( data, old_home, op->o_tmpmemctx );
1864
1865 } else if ( new_valid && old_valid ) { /* change */
1866 int did_something = 0;
1867
1868 if ( strcmp( old_home, new_home ) != 0 ) {
1869 Debug( LDAP_DEBUG_TRACE, "homedir: "
1870 "homedir_mod_response: treating like a rename\n" );
1871 homedir_rename( old_home, new_home );
1872 did_something = 1;
1873 }
1874 if ( old_uidn != new_uidn || old_gidn != new_gidn ) {
1875 Debug( LDAP_DEBUG_ANY, "homedir: "
1876 "homedir_mod_response: rechowning\n" );
1877 chown_tree( new_home, old_uidn, new_uidn, old_gidn,
1878 new_gidn, op->o_tmpmemctx );
1879 did_something = 1;
1880 }
1881 if ( !did_something ) {
1882 Debug( LDAP_DEBUG_TRACE, "homedir: "
1883 "homedir_mod_response: nothing to do\n" );
1884 }
1885 } else if ( old_presence || new_presence ) {
1886 Debug( LDAP_DEBUG_ANY, "homedir: "
1887 "homedir_mod_response: <%s> values present "
1888 "but invalid; ignoring\n",
1889 op->o_req_ndn.bv_val );
1890 }
1891 rc = SLAP_CB_CONTINUE;
1892 break;
1893 }
1894
1895 default:
1896 rc = SLAP_CB_CONTINUE;
1897 }
1898
1899 out:
1900 Debug( LDAP_DEBUG_TRACE, "homedir: "
1901 "homedir_mod_response: leaving\n" );
1902 return rc;
1903 }
1904
1905 static int
homedir_op_mod(Operation * op,SlapReply * rs)1906 homedir_op_mod( Operation *op, SlapReply *rs )
1907 {
1908 slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
1909 slap_callback *cb = NULL;
1910 homedir_cb_data *cb_data = NULL;
1911 Entry *e = NULL;
1912 Entry *se = NULL;
1913 Operation nop = *op;
1914 int rc;
1915
1916 Debug( LDAP_DEBUG_TRACE, "homedir: "
1917 "homedir_op_mod: entering\n" );
1918
1919 /* retrieve the entry */
1920 nop.o_bd = on->on_info->oi_origdb;
1921 rc = overlay_entry_get_ov( &nop, &op->o_req_ndn, NULL, NULL, 0, &e, on );
1922 if ( e != NULL ) {
1923 se = entry_dup( e );
1924 overlay_entry_release_ov( &nop, e, 0, on );
1925 e = se;
1926 }
1927 if ( rc || e == NULL ) {
1928 Debug( LDAP_DEBUG_ANY, "homedir: "
1929 "homedir_op_mod: unable to get <%s>\n",
1930 op->o_req_ndn.bv_val );
1931 goto out;
1932 }
1933
1934 /* Allocate the callback to hold the entry */
1935 cb = op->o_tmpalloc( sizeof(slap_callback), op->o_tmpmemctx );
1936 cb_data = op->o_tmpalloc( sizeof(homedir_cb_data), op->o_tmpmemctx );
1937 cb->sc_cleanup = homedir_mod_cleanup;
1938 cb->sc_response = homedir_mod_response;
1939 cb->sc_private = cb_data;
1940 cb_data->entry = e;
1941 e = NULL;
1942 cb_data->on = on;
1943 cb->sc_next = op->o_callback;
1944 op->o_callback = cb;
1945
1946 out:
1947 if ( e != NULL ) entry_free( e );
1948 Debug( LDAP_DEBUG_TRACE, "homedir: "
1949 "homedir_op_mod: leaving\n" );
1950 return SLAP_CB_CONTINUE;
1951 }
1952
1953 static int
homedir_response(Operation * op,SlapReply * rs)1954 homedir_response( Operation *op, SlapReply *rs )
1955 {
1956 slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
1957 homedir_data *data = on->on_bi.bi_private;
1958
1959 Debug( LDAP_DEBUG_TRACE, "homedir: "
1960 "homedir_response: entering\n" );
1961 if ( rs->sr_err != LDAP_SUCCESS || data == NULL ) return SLAP_CB_CONTINUE;
1962
1963 switch ( op->o_tag ) {
1964 case LDAP_REQ_ADD: { /* Check for new homedir */
1965 char home_buf[1024];
1966 uid_t uidn = 0;
1967 gid_t gidn = 0;
1968 int rc, presence;
1969
1970 rc = harvest_values( data, op->ora_e, home_buf, sizeof(home_buf),
1971 &uidn, &gidn, &presence );
1972 if ( rc == 0 && uidn >= data->min_uid ) {
1973 homedir_provision( home_buf, data->skeleton_path, uidn, gidn,
1974 op->o_tmpmemctx );
1975 }
1976 return SLAP_CB_CONTINUE;
1977 }
1978
1979 default:
1980 return SLAP_CB_CONTINUE;
1981 }
1982
1983 return SLAP_CB_CONTINUE;
1984 }
1985
1986 static int
homedir_db_init(BackendDB * be,ConfigReply * cr)1987 homedir_db_init( BackendDB *be, ConfigReply *cr )
1988 {
1989 slap_overinst *on = (slap_overinst *)be->bd_info;
1990 homedir_data *data = ch_calloc( 1, sizeof(homedir_data) );
1991 const char *text;
1992
1993 if ( slap_str2ad( "homeDirectory", &data->home_ad, &text ) ||
1994 slap_str2ad( "uidNumber", &data->uidn_ad, &text ) ||
1995 slap_str2ad( "gidNumber", &data->gidn_ad, &text ) ) {
1996 Debug( LDAP_DEBUG_ANY, "homedir: "
1997 "nis schema not available\n" );
1998 return 1;
1999 }
2000
2001 data->skeleton_path = strdup( DEFAULT_SKEL );
2002 data->min_uid = DEFAULT_MIN_UID;
2003 data->archive_path = NULL;
2004
2005 on->on_bi.bi_private = data;
2006 return 0;
2007 }
2008
2009 static int
homedir_db_destroy(BackendDB * be,ConfigReply * cr)2010 homedir_db_destroy( BackendDB *be, ConfigReply *cr )
2011 {
2012 slap_overinst *on = (slap_overinst *)be->bd_info;
2013 homedir_data *data = on->on_bi.bi_private;
2014 homedir_regexp *r, *rnext;
2015
2016 if ( data != NULL ) {
2017 for ( r = data->regexps; r != NULL; r = rnext ) {
2018 rnext = r->next;
2019 ch_free( r->match );
2020 ch_free( r->replace );
2021 regfree( &r->compiled );
2022 ch_free( r );
2023 }
2024 data->regexps = NULL;
2025 if ( data->skeleton_path != NULL ) ch_free( data->skeleton_path );
2026 if ( data->archive_path != NULL ) ch_free( data->archive_path );
2027 ch_free( data );
2028 }
2029
2030 return 0;
2031 }
2032
2033 int
homedir_initialize()2034 homedir_initialize()
2035 {
2036 int rc;
2037
2038 assert( ' ' == 32 ); /* Lots of ASCII requirements for now */
2039
2040 memset( &homedir, 0, sizeof(homedir) );
2041
2042 homedir.on_bi.bi_type = "homedir";
2043 homedir.on_bi.bi_db_init = homedir_db_init;
2044 homedir.on_bi.bi_db_destroy = homedir_db_destroy;
2045 homedir.on_bi.bi_op_delete = homedir_op_mod;
2046 homedir.on_bi.bi_op_modify = homedir_op_mod;
2047 homedir.on_response = homedir_response;
2048
2049 homedir.on_bi.bi_cf_ocs = homedirocs;
2050 rc = config_register_schema( homedircfg, homedirocs );
2051 if ( rc ) return rc;
2052
2053 ldap_pvt_thread_mutex_init( &readdir_mutex );
2054
2055 return overlay_register( &homedir );
2056 }
2057
2058 int
homedir_terminate()2059 homedir_terminate()
2060 {
2061 ldap_pvt_thread_mutex_destroy( &readdir_mutex );
2062 return 0;
2063 }
2064
2065 #if SLAPD_OVER_HOMEDIR == SLAPD_MOD_DYNAMIC && defined(PIC)
2066 int
init_module(int argc,char * argv[])2067 init_module( int argc, char *argv[] )
2068 {
2069 return homedir_initialize();
2070 }
2071
2072 int
term_module()2073 term_module()
2074 {
2075 return homedir_terminate();
2076 }
2077 #endif
2078
2079 #endif /* SLAPD_OVER_HOMEDIR */
2080