summaryrefslogtreecommitdiffstats
path: root/libtecla/homedir.c
blob: 29257f847dd87e38fde872617bd90ed9f2bb144d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
/*
 * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2012 by Martin C. Shepherd.
 * 
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons
 * to whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
 * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
 * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 * 
 * Except as contained in this notice, the name of a copyright holder
 * shall not be used in advertising or otherwise to promote the sale, use
 * or other dealings in this Software without prior written authorization
 * of the copyright holder.
 */

/*
 * If file-system access is to be excluded, this module has no function,
 * so all of its code should be excluded.
 */
#ifndef WITHOUT_FILE_SYSTEM

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>

#include "pathutil.h"
#include "homedir.h"
#include "errmsg.h"

/*
 * Use the reentrant POSIX threads versions of the password lookup functions?
 */
#if defined(PREFER_REENTRANT) && defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199506L
#define THREAD_COMPATIBLE 1
/*
 * Under Solaris we can use thr_main() to determine whether
 * threads are actually running, and thus when it is necessary
 * to avoid non-reentrant features.
 */
#if defined __sun && defined __SVR4
#include <thread.h>                      /* Solaris thr_main() */
#endif
#endif

/*
 * Provide a password buffer size fallback in case the max size reported
 * by sysconf() is said to be indeterminate.
 */
#define DEF_GETPW_R_SIZE_MAX 1024

/*
 * The resources needed to lookup and record a home directory are
 * maintained in objects of the following type.
 */
struct HomeDir {
  ErrMsg *err;             /* The error message report buffer */
  char *buffer;            /* A buffer for reading password entries and */
                           /*  directory paths. */
  int buflen;              /* The allocated size of buffer[] */
#ifdef THREAD_COMPATIBLE
  struct passwd pwd;       /* The password entry of a user */
#endif
};

static const char *hd_getpwd(HomeDir *home);

/*.......................................................................
 * Create a new HomeDir object.
 *
 * Output:
 *  return  HomeDir *  The new object, or NULL on error.
 */
HomeDir *_new_HomeDir(void)
{
  HomeDir *home;  /* The object to be returned */
  size_t pathlen; /* The estimated maximum size of a pathname */
/*
 * Allocate the container.
 */
  home = (HomeDir *) malloc(sizeof(HomeDir));
  if(!home) {
    errno = ENOMEM;
    return NULL;
  };
/*
 * Before attempting any operation that might fail, initialize the
 * container at least up to the point at which it can safely be passed
 * to _del_HomeDir().
 */
  home->err = NULL;
  home->buffer = NULL;
  home->buflen = 0;
/*
 * Allocate a place to record error messages.
 */
  home->err = _new_ErrMsg();
  if(!home->err)
    return _del_HomeDir(home);
/*
 * Allocate the buffer that is used by the reentrant POSIX password-entry
 * lookup functions.
 */
#ifdef THREAD_COMPATIBLE
/*
 * Get the length of the buffer needed by the reentrant version
 * of getpwnam().
 */
#ifndef _SC_GETPW_R_SIZE_MAX
  home->buflen = DEF_GETPW_R_SIZE_MAX;
#else
  errno = 0;
  home->buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
/*
 * If the limit isn't available, substitute a suitably large fallback value.
 */
  if(home->buflen < 0 || errno)
    home->buflen = DEF_GETPW_R_SIZE_MAX;
#endif
#endif
/*
 * If the existing buffer length requirement is too restrictive to record
 * a pathname, increase its length.
 */
  pathlen = _pu_pathname_dim();
  if(pathlen > home->buflen)
    home->buflen = pathlen;
/*
 * Allocate a work buffer.
 */
  home->buffer = (char *) malloc(home->buflen);
  if(!home->buffer) {
    errno = ENOMEM;
    return _del_HomeDir(home);
  };
  return home;
}

/*.......................................................................
 * Delete a HomeDir object.
 *
 * Input:
 *  home   HomeDir *  The object to be deleted.
 * Output:
 *  return HomeDir *  The deleted object (always NULL).
 */
HomeDir *_del_HomeDir(HomeDir *home)
{
  if(home) {
    home->err = _del_ErrMsg(home->err);
    if(home->buffer)
      free(home->buffer);
    free(home);
  };
  return NULL;
}

/*.......................................................................
 * Lookup the home directory of a given user in the password file.
 *
 * Input:
 *  home      HomeDir *   The resources needed to lookup the home directory.
 *  user   const char *   The name of the user to lookup, or "" to lookup
 *                        the home directory of the person running the
 *                        program.
 * Output:
 *  return const char *   The home directory. If the library was compiled
 *                        with threads, this string is part of the HomeDir
 *                        object and will change on subsequent calls. If
 *                        the library wasn't compiled to be reentrant,
 *                        then the string is a pointer into a static string
 *                        in the C library and will change not only on
 *                        subsequent calls to this function, but also if
 *                        any calls are made to the C library password
 *                        file lookup functions. Thus to be safe, you should
 *                        make a copy of this string before calling any
 *                        other function that might do a password file
 *                        lookup.
 *
 *                        On error, NULL is returned and a description
 *                        of the error can be acquired by calling
 *                        _hd_last_home_dir_error().
 */
const char *_hd_lookup_home_dir(HomeDir *home, const char *user)
{
  const char *home_dir;   /* A pointer to the home directory of the user */
/*
 * If no username has been specified, arrange to lookup the current
 * user.
 */
  int login_user = !user || *user=='\0';
/*
 * Check the arguments.
 */
  if(!home) {
    errno = EINVAL;
    return NULL;
  };
/*
 * Handle the ksh "~+". This expands to the absolute path of the
 * current working directory.
 */
  if(!login_user && strcmp(user, "+") == 0) {
    home_dir = hd_getpwd(home);
    if(!home_dir) {
      _err_record_msg(home->err, "Can't determine current directory",
		      END_ERR_MSG);
      return NULL;
    }
    return home_dir;
  };
/*
 * When looking up the home directory of the current user, see if the
 * HOME environment variable is set, and if so, return its value.
 */
  if(login_user) {
    home_dir = getenv("HOME");
    if(home_dir)
      return home_dir;
  };
/*
 * Look up the password entry of the user.
 * First the POSIX threads version - this is painful!
 */
#ifdef THREAD_COMPATIBLE
  {
    struct passwd *ret; /* The returned pointer to pwd */
    int status;         /* The return value of getpwnam_r() */
/*
 * Look up the password entry of the specified user.
 */
    if(login_user)
      status = getpwuid_r(geteuid(), &home->pwd, home->buffer, home->buflen,
			  &ret);
    else
      status = getpwnam_r(user, &home->pwd, home->buffer, home->buflen, &ret);
    if(status || !ret) {
      _err_record_msg(home->err, "User '", user, "' doesn't exist.",
		      END_ERR_MSG);
      return NULL;
    };
/*
 * Get a pointer to the string that holds the home directory.
 */
    home_dir = home->pwd.pw_dir;
  };
/*
 * Now the classic unix version.
 */
#else
  {
    struct passwd *pwd = login_user ? getpwuid(geteuid()) : getpwnam(user);
    if(!pwd) {
      _err_record_msg(home->err, "User '", user, "' doesn't exist.",
		      END_ERR_MSG);
      return NULL;
    };
/*
 * Get a pointer to the home directory.
 */
    home_dir = pwd->pw_dir;
  };
#endif
  return home_dir;
}

/*.......................................................................
 * Return a description of the last error that caused _hd_lookup_home_dir()
 * to return NULL.
 *
 * Input:
 *  home   HomeDir *  The resources needed to record the home directory.
 * Output:
 *  return    char *  The description of the last error.
 */
const char *_hd_last_home_dir_error(HomeDir *home)
{
  return home ? _err_get_msg(home->err) : "NULL HomeDir argument";
}

/*.......................................................................
 * The _hd_scan_user_home_dirs() function calls a user-provided function
 * for each username known by the system, passing the function both
 * the name and the home directory of the user.
 *
 * Input:
 *  home             HomeDir *  The resource object for reading home
 *                              directories.
 *  prefix        const char *  Only information for usernames that
 *                              start with this prefix will be
 *                              returned. Note that the empty
 &                              string "", matches all usernames.
 *  data                void *  Anonymous data to be passed to the
 *                              callback function.
 *  callback_fn  HOME_DIR_FN(*) The function to call for each user.
 * Output:
 *  return               int    0 - Successful completion.
 *                              1 - An error occurred. A description
 *                                  of the error can be obtained by
 *                                  calling _hd_last_home_dir_error().
 */
int _hd_scan_user_home_dirs(HomeDir *home, const char *prefix,
			    void *data, HOME_DIR_FN(*callback_fn))
{
  int waserr = 0;       /* True after errors */
  int prefix_len;       /* The length of prefix[] */
/*
 * Check the arguments.
 */
  if(!home || !prefix || !callback_fn) {
    if(home) {
      _err_record_msg(home->err,
		      "_hd_scan_user_home_dirs: Missing callback function",
		      END_ERR_MSG);
    };
    return 1;
  };
/*
 * Get the length of the username prefix.
 */
  prefix_len = strlen(prefix);
/*
 * There are no reentrant versions of getpwent() etc for scanning
 * the password file, so disable username completion when the
 * library is compiled to be reentrant.
 */
#if defined(PREFER_REENTRANT) && defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199506L
#if defined __sun && defined __SVR4
  if(thr_main() >= 0) /* thread library is linked in */
#else
  if(1)
#endif
  {
    struct passwd pwd_buffer;  /* A returned password entry */
    struct passwd *pwd;        /* A pointer to pwd_buffer */
    char buffer[512];          /* The buffer in which the string members of */
                               /* pwd_buffer are stored. */
/*
 * See if the prefix that is being completed is a complete username.
 */
    if(!waserr && getpwnam_r(prefix, &pwd_buffer, buffer, sizeof(buffer),
			     &pwd) == 0 && pwd != NULL) {
      waserr = callback_fn(data, pwd->pw_name, pwd->pw_dir,
			   _err_get_msg(home->err), ERR_MSG_LEN);
    };
/*
 * See if the username of the current user minimally matches the prefix.
 */
    if(!waserr && getpwuid_r(getuid(), &pwd_buffer, buffer, sizeof(buffer),
			     &pwd) == 0 && pwd != NULL &&
                             strncmp(prefix, pwd->pw_name, prefix_len)==0) {
      waserr = callback_fn(data, pwd->pw_name, pwd->pw_dir,
			   _err_get_msg(home->err), ERR_MSG_LEN);
    };
/*
 * Reentrancy not required?
 */
  } else
#endif
  {
    struct passwd *pwd;   /* The pointer to the latest password entry */
/*
 * Open the password file.
 */
    setpwent();
/*
 * Read the contents of the password file, looking for usernames
 * that start with the specified prefix, and adding them to the
 * list of matches.
 */
    while((pwd = getpwent()) != NULL && !waserr) {
      if(strncmp(prefix, pwd->pw_name, prefix_len) == 0) {
	waserr = callback_fn(data, pwd->pw_name, pwd->pw_dir,
			     _err_get_msg(home->err), ERR_MSG_LEN);
      };
    };
/*
 * Close the password file.
 */
    endpwent();
  };
/*
 * Under ksh ~+ stands for the absolute pathname of the current working
 * directory.
 */
  if(!waserr && strncmp(prefix, "+", prefix_len) == 0) {
    const char *pwd = hd_getpwd(home);
    if(pwd) {
      waserr = callback_fn(data, "+", pwd, _err_get_msg(home->err),ERR_MSG_LEN);
    } else {
      waserr = 1;
      _err_record_msg(home->err, "Can't determine current directory.",
		      END_ERR_MSG);
    };
  };
  return waserr;
}

/*.......................................................................
 * Return the value of getenv("PWD") if this points to the current
 * directory, or the return value of getcwd() otherwise. The reason for
 * prefering PWD over getcwd() is that the former preserves the history
 * of symbolic links that have been traversed to reach the current
 * directory. This function is designed to provide the equivalent
 * expansion of the ksh ~+ directive, which normally returns its value
 * of PWD.
 *
 * Input:
 *  home      HomeDir *  The resource object for reading home directories.
 * Output:
 *  return const char *  A pointer to either home->buffer, where the
 *                       pathname is recorded, the string returned by
 *                       getenv("PWD"), or NULL on error.
 */
static const char *hd_getpwd(HomeDir *home)
{
/*
 * Get the absolute path of the current working directory.
 */
  char *cwd = getcwd(home->buffer, home->buflen);
/*
 * Some shells set PWD with the path of the current working directory.
 * This will differ from cwd in that it won't have had symbolic links
 * expanded.
 */
  const char *pwd = getenv("PWD");
/*
 * If PWD was set, and it points to the same directory as cwd, return
 * its value. Note that it won't be the same if the current shell or
 * the current program has changed directories, after inheriting PWD
 * from a parent shell.
 */
  struct stat cwdstat, pwdstat;
  if(pwd && cwd && stat(cwd, &cwdstat)==0 && stat(pwd, &pwdstat)==0 &&
     cwdstat.st_dev == pwdstat.st_dev && cwdstat.st_ino == pwdstat.st_ino)
    return pwd;
/*
 * Also return pwd if getcwd() failed, since it represents the best
 * information that we have access to.
 */
  if(!cwd)
    return pwd;
/*
 * In the absence of a valid PWD, return cwd.
 */
  return cwd;
}

#endif  /* ifndef WITHOUT_FILE_SYSTEM */