1 #include <config.h>
2 #include <stdio.h>
3 #include <pwd.h>
4 #ifdef HAVE_SHADOW_H
5 #include <shadow.h>
6 #endif
7 #include <sys/stat.h>
8 #include <sys/types.h>
9 #include <fcntl.h>
10 #include <unistd.h>
11 #include <string.h>
12 /* add BSD support */
13 #include <limits.h>
14 
15 #ifdef HAVE_CRYPT_H
16 #include <crypt.h>
17 #endif
18 #ifdef WANT_PAM
19 #include <security/pam_appl.h>
20 #endif
21 #ifdef HAVE_UTMP_H
22 # include <utmp.h>
23 # ifdef HAVE_PATHS_H
24 #  include <paths.h>
25 #  ifndef _PATH_WTMP
26 #   define _PATH_WTMP "/dev/null"
27 #   warning "<paths.h> doesn't set _PATH_WTMP. You can not use wtmp logging"
28 #   warning "with bftpd."
29 #  endif
30 # else
31 #  define _PATH_WTMP "/dev/null"
32 #  warning "<paths.h> was not found. You can not use wtmp logging with bftpd."
33 # endif
34 #endif
35 #include <errno.h>
36 #include <grp.h>
37 #include <stdlib.h>
38 #ifdef HAVE_SYS_TIME_H
39 #include <sys/time.h>
40 #endif
41 #ifdef HAVE_TIME_H
42 #include <time.h>
43 #endif
44 
45 #include "cwd.h"
46 #include "dirlist.h"
47 #include "mystring.h"
48 #include "options.h"
49 #include "login.h"
50 #include "logging.h"
51 #include "bftpdutmp.h"
52 #include "main.h"
53 
54 #ifdef WANT_PAM
55 char usepam = 0;
56 pam_handle_t *pamh = NULL;
57 #endif
58 
59 #ifdef HAVE_UTMP_H
60 FILE *wtmp;
61 #endif
62 
63 struct passwd userinfo;
64 char userinfo_set = 0;
65 
mygetpwuid(int uid,FILE * file,char * name)66 char *mygetpwuid(int uid, FILE * file, char *name)
67 {
68 	int _uid;
69 	char foo[256];
70     int i;
71 	if (file) {
72 		rewind(file);
73         while (fscanf(file, "%255s%*[^\n]\n", foo) != EOF) {
74             if ((foo[0] == '#') || (!strchr(foo, ':')) || (strchr(foo, ':') > foo + USERLEN - 1))
75                 continue;
76             i = strchr(foo, ':') - foo;
77             strncpy(name, foo, i);
78             name[i] = 0;
79 			sscanf(strchr(foo + i + 1, ':') + 1, "%i", &_uid);
80 			if (_uid == uid) {
81 				if (name[0] == '\n')
82 					cutto(name, 1);
83 				return name;
84 			}
85 		}
86 	}
87 	sprintf(name, "%i", uid);
88 	return name;
89 }
90 
mygetpwnam(char * name,FILE * file)91 int mygetpwnam(char *name, FILE * file)
92 {
93 	char _name[USERLEN + 1];
94 	char foo[256];
95 	int uid, i;
96 	if (file) {
97 		rewind(file);
98         while (fscanf(file, "%255s%*[^\n]\n", foo) != EOF) {
99             if ((foo[0] == '#') || (!strchr(foo, ':')) || (strchr(foo, ':') > foo + USERLEN - 1))
100                 continue;
101             i = strchr(foo, ':') - foo;
102             strncpy(_name, foo, i);
103             _name[i] = 0;
104 			sscanf(strchr(foo + i + 1, ':') + 1, "%i", &uid);
105 			if (_name[0] == '\n')
106 				cutto(_name, 1);
107 			if (!strcmp(name, _name))
108 				return uid;
109 		}
110 	}
111 	return -1;
112 }
113 
114 #ifdef HAVE_UTMP_H
wtmp_init()115 void wtmp_init()
116 {
117 	if (strcasecmp(config_getoption("LOG_WTMP"), "no")) {
118 		if (!((wtmp = fopen(_PATH_WTMP, "a"))))
119 			bftpd_log("Warning: Unable to open %s.\n", _PATH_WTMP);
120 	}
121 }
122 
bftpd_logwtmp(char type)123 void bftpd_logwtmp(char type)
124 {
125 	struct utmp ut;
126 #ifndef __minix
127         struct timeval tv;
128 #endif
129 	if (!wtmp)
130 		return;
131 	memset((void *) &ut, 0, sizeof(ut));
132 #ifdef _HAVE_UT_PID
133 	ut.ut_pid = getpid();
134 #endif
135 	sprintf(ut.ut_line, "ftp%i", (int) getpid());
136 	if (type) {
137 #ifdef _HAVE_UT_TYPE
138 		ut.ut_type = USER_PROCESS;
139 #endif
140 		strncpy(ut.ut_name, user, sizeof(ut.ut_name));
141 #ifdef _HAVE_UT_HOST
142 		strncpy(ut.ut_host, remotehostname, sizeof(ut.ut_host));
143 #endif
144 	} else {
145 #ifdef _HAVE_UT_TYPE
146 		ut.ut_type = DEAD_PROCESS;
147 #endif
148 	}
149 	/*
150         Using time() here is not strictly 64-bit compatible.
151         Will use timeval structure to get time instead.
152         time(&(ut.ut_time));
153         */
154 #if !defined(__minix) && !defined(__NetBSD__) && !defined(__DragonFly__)
155         gettimeofday(&tv, NULL);
156         ut.ut_tv.tv_sec = tv.tv_sec;
157         ut.ut_tv.tv_usec = tv.tv_usec;
158 #else
159         time(&(ut.ut_time));
160 #endif
161 
162 	fseek(wtmp, 0, SEEK_END);
163 	fwrite((void *) &ut, sizeof(ut), 1, wtmp);
164 	fflush(wtmp);
165 }
166 
wtmp_end()167 void wtmp_end()
168 {
169 	if (wtmp) {
170 		if (state >= STATE_AUTHENTICATED)
171 			bftpd_logwtmp(0);
172 		fclose(wtmp);
173 	}
174 }
175 #endif
176 
login_init()177 void login_init()
178 {
179     char *foo = config_getoption("INITIAL_CHROOT");
180 #ifdef HAVE_UTMP_H
181 	wtmp_init();
182 #endif
183     if (foo[0]) { /* Initial chroot */
184         if (chroot(foo) == -1) {
185             control_printf(SL_FAILURE, "421 Initial chroot failed.\r\n.");
186             exit(1);
187         }
188     }
189 }
190 
bftpd_setuid(uid_t uid)191 int bftpd_setuid(uid_t uid)
192 {
193     /* If we must open the data connections from port 20,
194      * we have to keep the possibility to regain root privileges */
195     if (!strcasecmp(config_getoption("DATAPORT20"), "yes"))
196         return seteuid(uid);
197     else
198         return setuid(uid);
199 }
200 
201 
202 /*
203 Returns 0 on success and non-zero (-1) on failure
204 */
bftpd_login(char * password)205 int bftpd_login(char *password)
206 {
207 	char str[MAX_STRING_LENGTH + 1];
208 	char *foo;
209 	int maxusers;
210         char *file_auth;   /* if used, points to file used to auth users */
211         char *home_directory = NULL;   /* retrieved from auth_file */
212         char *anonymous = NULL;
213         char *change_uid_text = NULL;
214         char *time_zone = NULL;
215         unsigned long get_maxusers;
216         int anon_ok = FALSE;
217         int change_uid = FALSE;
218 
219         str[0] = '\0';     /* avoid garbage in str */
220         file_auth = config_getoption("FILE_AUTH");
221         anonymous = config_getoption("ANONYMOUS_USER");
222         change_uid_text = config_getoption("CHANGE_UID");
223 
224 	if (! strcasecmp(anonymous, "yes") )
225 	{
226 	   anon_ok = TRUE;
227 	}
228 
229 	if (! strcasecmp(change_uid_text, "yes") )
230 	{
231 	   change_uid = TRUE;
232 	}
233 
234         time_zone = config_getoption("TIMEZONE_FIX");
235         if (! strcasecmp(time_zone, "no") )
236         {
237            /* we do not need the time zone fix, so do nothing here */
238         }
239         else
240            Get_Time_Zone_Difference();
241         if (! file_auth[0] )    /* not using auth file */
242         {
243            /* check to see if regular authentication is avail */
244            if ( anon_ok && ! change_uid )
245            {
246               home_directory = "/";
247            }
248            #ifndef NO_GETPWNAM
249 	   else if (!getpwnam(user)) {
250                 control_printf(SL_FAILURE, "530 Login incorrect.");
251 		// exit(0);
252                 return -1;
253            }
254            #endif
255         }
256         /* we are using auth_file */
257         else
258         {
259            home_directory = check_file_password(file_auth, user, password);
260            if (! home_directory)
261            {
262                if ( anon_ok && ! change_uid )
263                    home_directory = "/";
264                else
265                {
266                   control_printf(SL_FAILURE, "530 Anonymous user not allowed.");
267                   //exit(0);
268                   return -1;
269                }
270            }
271         }
272 
273 	if (strncasecmp(foo = config_getoption("DENY_LOGIN"), "no", 2)) {
274 		if (foo[0] != '\0') {
275 			if (strncasecmp(foo, "yes", 3))
276 				control_printf(SL_FAILURE, "530 %s", foo);
277 			else
278 				control_printf(SL_FAILURE, "530 Login incorrect.");
279 			bftpd_log("Login as user '%s' failed: Server disabled.\n", user);
280 			// exit(0);
281                         return -1;
282 		}
283 	}
284 	get_maxusers = strtoul(config_getoption("USERLIMIT_GLOBAL"), NULL, 10);
285         if (get_maxusers <= INT_MAX)
286            maxusers = get_maxusers;
287         else
288         {
289            bftpd_log("Error getting max users for GLOBAL in bftpd_login.\n", 0);
290            maxusers = 0;
291         }
292 	if ((maxusers) && (maxusers == bftpdutmp_usercount("*"))) {
293 		control_printf(SL_FAILURE, "421 There are already %i users logged in.", maxusers);
294                 bftpd_log("Login as user '%s' failed. Too many users on server.\n", user);
295 		exit(0);
296 	}
297 	get_maxusers = strtoul(config_getoption("USERLIMIT_SINGLEUSER"), NULL, 10);
298         if (get_maxusers <= INT_MAX)
299            maxusers = get_maxusers;
300         else
301         {
302            bftpd_log("error getting max users (SINGLE USER) in bftpd_login.\n", 0);
303            maxusers = 0;
304         }
305 	if ((maxusers) && (maxusers == bftpdutmp_usercount(user))) {
306 		control_printf(SL_FAILURE, "421 User %s is already logged in %i times.", user, maxusers);
307                 bftpd_log("Login as user '%s' failed. Already logged in %d times.", maxusers);
308 		exit(0);
309 	}
310 
311         /* Check to see if we should block multiple logins from the same machine.
312            -- Jesse <slicer69@hotmail.com>
313         */
314         get_maxusers = strtoul( config_getoption("USERLIMIT_HOST"), NULL, 10);
315         if (get_maxusers <= INT_MAX)
316            maxusers = get_maxusers;
317         else
318         {
319             bftpd_log("Error getting max users per HOST in bftpd_login.\n", 0);
320             maxusers = 0;
321         }
322 
323         if ( (maxusers) && (maxusers == bftpdutmp_dup_ip_count(remotehostname) ) )
324         {
325             control_printf(SL_FAILURE, "421 Too many connections from your IP address.");
326             bftpd_log("Login as user '%s' failed. Already %d connections from %s.\n", user, maxusers, remotehostname);
327             exit(0);
328         }
329 
330         /* disable these checks when logging in via auth file */
331         if ( (! file_auth[0] ) && (!anon_ok || change_uid) )
332         {
333             #ifndef NO_GETPWNAM
334 	    if(checkuser() || checkshell()) {
335 		control_printf(SL_FAILURE, "530 Login incorrect.");
336 		// exit(0);
337                 return -1;
338 	    }
339             #endif
340         }
341 
342         /* do not do this check when we are using auth_file */
343         if ( (! file_auth[0] ) && (! anon_ok) )
344         {
345             #ifndef NO_GETPWNAM
346 	    if (checkpass(password))
347             {
348 		control_printf(SL_FAILURE, "530 Login incorrect.");
349 		return -1;
350             }
351             #endif
352         }
353 
354 	if (strcasecmp((char *) config_getoption("RATIO"), "none")) {
355 		sscanf((char *) config_getoption("RATIO"), "%i/%i",
356 			   &ratio_send, &ratio_recv);
357 	}
358 
359         /* do these checks if logging in via normal methods */
360         if ( (! file_auth[0]) && (!anon_ok || (anon_ok && change_uid)) )
361         {
362 	     strcpy(str, config_getoption("ROOTDIR"));
363 	     if (!str[0])
364 		strcpy(str, "%h");
365 	     replace(str, "%u", userinfo.pw_name, MAX_STRING_LENGTH);
366 	     replace(str, "%h", userinfo.pw_dir, MAX_STRING_LENGTH);
367 	     if (!strcasecmp(config_getoption("RESOLVE_UIDS"), "yes"))
368              {
369 		passwdfile = fopen("/etc/passwd", "r");
370                 if (! passwdfile)
371                     control_printf(SL_FAILURE, "421 Unable to open passwd file.\r\n%s.",
372                                      strerror(errno));
373 		groupfile = fopen("/etc/group", "r");
374                 if (! groupfile)
375                     control_printf(SL_FAILURE, "421 Unable to open group file.\r\n%s.",
376                                      strerror(errno));
377 	     }
378 
379 	if ( setgid(userinfo.pw_gid) < 0 )
380         {
381            control_printf(SL_FAILURE, "421 Unable to change group.\r\n");
382            exit(0);
383         }
384 	initgroups(userinfo.pw_name, userinfo.pw_gid);
385 	if (strcasecmp(config_getoption("DO_CHROOT"), "no")) {
386 		if (chroot(str)) {
387 			control_printf(SL_FAILURE, "421 Unable to change root directory.\r\n%s.",
388 					strerror(errno));
389 			exit(0);
390 		}
391 		if (bftpd_setuid(userinfo.pw_uid)) {
392 			control_printf(SL_FAILURE, "421 Unable to change uid.\r\n");
393 			exit(0);
394 		}
395 		if (chdir("/")) {
396 			control_printf(SL_FAILURE, "421 Unable to change working directory.\r\n%s.",
397 					 strerror(errno));
398 			exit(0);
399 		}
400 	} else {
401 		if (bftpd_setuid(userinfo.pw_uid)) {
402 			control_printf(SL_FAILURE, "421 Unable to change uid.\r\n");
403 			exit(0);
404 		}
405 		if (chdir(str)) {
406 			control_printf(SL_FAILURE, "230 Couldn't change cwd to '%s': %s.\r\n", str,
407 					 strerror(errno));
408 			if (chdir("/") == -1)
409                             control_printf(SL_FAILURE, "421 Unable to change working directory.\r\n%s.",
410                                              strerror(errno));
411 
412 		}
413 	}
414 
415         }   /* end of if we are using regular authentication methods */
416 
417         /* perhaps we are using anonymous logins, but not file_auth? */
418         else if ( (! file_auth[0]) && (anon_ok) && ! change_uid )
419         {
420           strcpy(str, config_getoption("ROOTDIR"));
421           if (! str[0])
422              str[0] = '/';
423           replace(str, "%h", home_directory, MAX_STRING_LENGTH);
424           replace(str, "%u", user, MAX_STRING_LENGTH);
425           /* should we chroot? */
426           if ( strcasecmp(config_getoption("DO_CHROOT"), "no") )
427           {
428              if ( chroot(str) )
429              {
430                  control_printf(SL_FAILURE, "421 Unable to change root directory.\r\n");
431                  exit(0);
432              }
433              if ( chdir("/") )
434              {
435                  control_printf(SL_FAILURE, "421 Unable to change working directory.\r\n");
436                  exit(0);
437              }
438           }
439 
440         }        /* end of using anonymous login */
441         else     /* we are using file authentication */
442         {
443             /* get home directory */
444 	    strcpy(str, config_getoption("ROOTDIR"));
445             if (! str[0])
446                 strcpy(str, "%h");
447 	    replace(str, "%h", home_directory, MAX_STRING_LENGTH);
448             replace(str, "%u", user, MAX_STRING_LENGTH);
449 
450             /* see if we should change root */
451             if ( strcasecmp(config_getoption("DO_CHROOT"), "no") )
452             {
453                 if ( chroot(home_directory) )
454                 {
455                     control_printf(SL_FAILURE, "421 Unable to change root directory.\r\n");
456                     exit(0);
457                 }
458                 if ( chdir("/") )
459                 {
460                     control_printf(SL_FAILURE, "421 Unable to change working directory.\r\n");
461                     exit(0);
462                 }
463             }
464 
465         }      /* end of using file auth */
466 
467         new_umask();
468 	/* print_file(230, config_getoption("MOTD_USER")); */
469         strcpy(str, config_getoption("MOTD_USER"));
470         /* Allow user specific path to MOTD file. */
471         replace(str, "%h", home_directory, MAX_STRING_LENGTH);
472         replace(str, "%u", user, MAX_STRING_LENGTH);
473         print_file(230, str);
474 
475 	control_printf(SL_SUCCESS, "230 User logged in.");
476 #ifdef HAVE_UTMP_H
477 	bftpd_logwtmp(1);
478 #endif
479         bftpdutmp_log(1);
480 	bftpd_log("Successfully logged in as user '%s'.\n", user);
481         if (config_getoption("AUTO_CHDIR")[0])
482             if ( chdir(config_getoption("AUTO_CHDIR")) == -1)
483                 bftpd_log("Chdir failed: %s.\n", strerror(errno));
484 
485 	state = STATE_AUTHENTICATED;
486 	bftpd_cwd_init();
487 
488         /* a little clean up before we go */
489         if ( (home_directory) && ( strcmp(home_directory, "/" ) ) )
490             free(home_directory);
491 
492 	return 0;
493 }
494 
495 
496 /* Return 1 on failure and 0 on success. */
checkpass(char * password)497 int checkpass(char *password)
498 {
499     #ifndef NO_GETPWNAM
500     if (!getpwnam(user))
501 		return 1;
502     #endif
503 
504 	if (!strcasecmp(config_getoption("ANONYMOUS_USER"), "yes"))
505 		return 0;
506 
507 #ifdef WANT_PAM
508 	if (!strcasecmp(config_getoption("AUTH"), "pam"))
509 		return checkpass_pam(password);
510 	else
511 #endif
512 		return checkpass_pwd(password);
513 }
514 
515 
516 
login_end()517 void login_end()
518 {
519 #ifdef WANT_PAM
520 	if (usepam)
521 		return end_pam();
522 #endif
523 #ifdef HAVE_UTMP_H
524 	wtmp_end();
525 #endif
526 }
527 
528 
529 /*
530 Notes for this function.
531 1. Returned values fom crypt() should not be freed, it
532 causes a segfault.
533 2. Values returned from crypt() cannot be assumed to be
534 valid and need to be checked.
535 3. This function returns 0 on success and 1 on error.
536 -- Jesse
537 */
checkpass_pwd(char * password)538 int checkpass_pwd(char *password)
539 {
540    char *crypt_value = NULL;
541 #ifdef HAVE_SHADOW_H
542    char *shadow_value = NULL;
543 #endif
544 #ifdef HAVE_SHADOW_H
545 	struct spwd *shd;
546 #endif
547         crypt_value = crypt(password, userinfo.pw_passwd);
548 	// if (strcmp(userinfo.pw_passwd, (char *) crypt(password, userinfo.pw_passwd))) {
549         if (!crypt_value || strcmp(userinfo.pw_passwd, crypt_value) )
550         {
551 #ifdef HAVE_SHADOW_H
552 		if (!(shd = getspnam(user)))
553 			return 1;
554                 shadow_value = crypt(password, shd->sp_pwdp);
555                 if (! shadow_value)
556                    return 1;
557 		// if (strcmp(shd->sp_pwdp, (char *) crypt(password, shd->sp_pwdp)))
558                 if ( strcmp(shd->sp_pwdp, shadow_value) )
559 #endif
560 			return 1;
561 	}
562 	return 0;
563 }
564 
565 #ifdef WANT_PAM
conv_func(int num_msg,const struct pam_message ** msgm,struct pam_response ** resp,void * appdata_ptr)566 int conv_func(int num_msg, const struct pam_message **msgm,
567 			  struct pam_response **resp, void *appdata_ptr)
568 {
569 	struct pam_response *response;
570 	int i;
571 	response = (struct pam_response *) malloc(sizeof(struct pam_response) * num_msg);
572 	for (i = 0; i < num_msg; i++) {
573 		response[i].resp = (char *) strdup(appdata_ptr);
574 		response[i].resp_retcode = 0;
575 	}
576 	*resp = response;
577 	return 0;
578 }
579 
checkpass_pam(char * password)580 int checkpass_pam(char *password)
581 {
582 	struct pam_conv conv = { conv_func, password };
583 	int retval = pam_start("bftpd", user, (struct pam_conv *) &conv,
584 						   (pam_handle_t **) & pamh);
585 	if (retval != PAM_SUCCESS) {
586 		printf("Error while initializing PAM: %s\n",
587 			   pam_strerror(pamh, retval));
588 		return 1;
589 	}
590         /*
591         Allow Bftpd to build with OpenPAM
592 	pam_fail_delay(pamh, 0);
593         */
594 	retval = pam_authenticate(pamh, 0);
595 	if (retval == PAM_SUCCESS)
596 		retval = pam_acct_mgmt(pamh, 0);
597 	if (retval == PAM_SUCCESS)
598 		pam_open_session(pamh, 0);
599 	if (retval != PAM_SUCCESS)
600 		return 1;
601 	else
602 		return 0;
603 }
604 
end_pam()605 void end_pam()
606 {
607 	if (pamh) {
608 		pam_close_session(pamh, 0);
609 		pam_end(pamh, 0);
610 	}
611 }
612 #endif
613 
checkuser()614 int checkuser()
615 {
616 
617 	FILE *fd;
618 	char *p;
619 	char line[256];
620 
621 	if ((fd = fopen(config_getoption("PATH_FTPUSERS"), "r"))) {
622 		while (fgets(line, sizeof(line), fd))
623 			if ((p = strchr(line, '\n'))) {
624 				*p = '\0';
625 				if (line[0] == '#')
626 					continue;
627 				if (!strcasecmp(line, user)) {
628 					fclose(fd);
629 					return 1;
630 				}
631 			}
632 		fclose(fd);
633 	}
634 	return 0;
635 }
636 
637 /*
638 Returns zero when everything is okay and 1 to
639 indicate an error or lack of shell.
640 */
checkshell()641 int checkshell()
642 {
643 #ifdef HAVE_GETUSERSHELL
644 	char *cp;
645 	struct passwd *pwd;
646 
647         if (!strcasecmp(config_getoption("AUTH_ETCSHELLS"), "no"))
648              return 0;
649 
650 	pwd = getpwnam(user);
651         /* make sure we do not try to use an invalid return */
652         if (! pwd)
653            return 1;
654 	while ((cp = getusershell()))
655 		if (!strcmp(cp, pwd->pw_shell))
656 			break;
657 	endusershell();
658 
659 	if (!cp)
660 		return 1;
661 	else
662 		return 0;
663 #else
664     return 0;
665 #   warning "Your system doesn't have getusershell(). You can not"
666 #   warning "use /etc/shells authentication with bftpd."
667 #endif
668 }
669 
670 
671 
672 
673 /*
674 This function searches through a text file for a matching
675 username. If a match is found, the password in the
676 text file is compared to the password passed in to
677 the function. If the password matches, the function
678 returns the fourth field (home directory). On failure,
679 it returns NULL.
680 -- Jesse
681 */
check_file_password(char * my_filename,char * my_username,char * my_password)682 char *check_file_password(char *my_filename, char *my_username, char *my_password)
683 {
684    FILE *my_file;
685    int found_user = 0;
686    char user[33], password[33], group[33], home_dir[65];
687    char *my_home_dir = NULL;
688    int return_value;
689 
690    my_file = fopen(my_filename, "r");
691    if (! my_file)
692       return NULL;
693 
694    return_value = fscanf(my_file, "%32s %32s %32s %64s", user, password, group, home_dir);
695    if (! strcmp(user, my_username) )
696       found_user = 1;
697 
698    while ( (! found_user) && ( return_value != EOF) )
699    {
700        return_value = fscanf(my_file, "%32s %32s %32s %64s", user, password, group, home_dir);
701        if (! strcmp(user, my_username) )
702           found_user = 1;
703    }
704 
705    fclose(my_file);
706    if (found_user)
707    {
708       /* check password */
709       if (! strcmp(password, "*") )
710       {
711       }
712       else if ( strcmp(password, my_password) )
713          return NULL;
714 
715       my_home_dir = calloc( strlen(home_dir) + 1, sizeof(char) );
716       if (! my_home_dir)
717           return NULL;
718       strcpy(my_home_dir, home_dir);
719    }
720 
721    return my_home_dir;
722 }
723 
724