--- embedaddon/sudo/plugins/sudoers/check.c 2012/05/29 12:26:49 1.1.1.2 +++ embedaddon/sudo/plugins/sudoers/check.c 2014/06/15 16:12:54 1.1.1.6 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1993-1996,1998-2005, 2007-2011 + * Copyright (c) 1993-1996,1998-2005, 2007-2013 * Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any @@ -22,18 +22,7 @@ #include #include -#include #include -#include -#ifdef __linux__ -# include -#endif -#if defined(__sun) && defined(__SVR4) -# include -#endif -#ifndef __TANDEM -# include -#endif #include #ifdef STDC_HEADERS # include @@ -52,149 +41,126 @@ #ifdef HAVE_UNISTD_H # include #endif /* HAVE_UNISTD_H */ -#if TIME_WITH_SYS_TIME +#ifdef TIME_WITH_SYS_TIME # include #endif #include #include -#include #include #include #include "sudoers.h" +#include "check.h" -/* Status codes for timestamp_status() */ -#define TS_CURRENT 0 -#define TS_OLD 1 -#define TS_MISSING 2 -#define TS_NOFILE 3 -#define TS_ERROR 4 +static bool display_lecture(int); +static struct passwd *get_authpw(int); -/* Flags for timestamp_status() */ -#define TS_MAKE_DIRS 1 -#define TS_REMOVE 2 - /* - * Info stored in tty ticket from stat(2) to help with tty matching. + * Returns true if the user successfully authenticates, false if not + * or -1 on error. */ -static struct tty_info { - dev_t dev; /* ID of device tty resides on */ - dev_t rdev; /* tty device ID */ - ino_t ino; /* tty inode number */ - struct timeval ctime; /* tty inode change time */ -} tty_info; - -static int build_timestamp(char **, char **); -static int timestamp_status(char *, char *, char *, int); -static char *expand_prompt(char *, char *, char *); -static void lecture(int); -static void update_timestamp(char *, char *); -static bool tty_is_devpts(const char *); -static struct passwd *get_authpw(void); - -/* - * Returns true if the user successfully authenticates, else false. - */ -int -check_user(int validated, int mode) +static int +check_user_interactive(int validated, int mode, struct passwd *auth_pw) { - struct passwd *auth_pw; - char *timestampdir = NULL; - char *timestampfile = NULL; - char *prompt; - struct stat sb; int status, rval = true; - bool need_pass = def_authenticate; - debug_decl(check_user, SUDO_DEBUG_AUTH) + debug_decl(check_user_interactive, SUDO_DEBUG_AUTH) - /* - * Init authentication system regardless of whether we need a password. - * Required for proper PAM session support. - */ - auth_pw = get_authpw(); - if (sudo_auth_init(auth_pw) == -1) { - rval = -1; - goto done; - } + /* Always need a password when -k was specified with the command. */ + if (ISSET(mode, MODE_IGNORE_TICKET)) + SET(validated, FLAG_CHECK_USER); - if (need_pass) { - /* Always need a password when -k was specified with the command. */ - if (ISSET(mode, MODE_IGNORE_TICKET)) { - SET(validated, FLAG_CHECK_USER); - } else { - /* - * Don't prompt for the root passwd or if the user is exempt. - * If the user is not changing uid/gid, no need for a password. - */ - if (user_uid == 0 || (user_uid == runas_pw->pw_uid && - (!runas_gr || user_in_group(sudo_user.pw, runas_gr->gr_name))) - || user_is_exempt()) - need_pass = false; - } - } - if (!need_pass) - goto done; - - /* Stash the tty's ctime for tty ticket comparison. */ - if (def_tty_tickets && user_ttypath && stat(user_ttypath, &sb) == 0) { - tty_info.dev = sb.st_dev; - tty_info.ino = sb.st_ino; - tty_info.rdev = sb.st_rdev; - if (tty_is_devpts(user_ttypath)) - ctim_get(&sb, &tty_info.ctime); - } - - if (build_timestamp(×tampdir, ×tampfile) == -1) { + if (build_timestamp(auth_pw) == -1) { rval = -1; goto done; } - status = timestamp_status(timestampdir, timestampfile, user_name, - TS_MAKE_DIRS); + status = timestamp_status(auth_pw); if (status != TS_CURRENT || ISSET(validated, FLAG_CHECK_USER)) { + char *prompt; + bool lectured; + /* Bail out if we are non-interactive and a password is required */ if (ISSET(mode, MODE_NONINTERACTIVE)) { - warningx(_("sorry, a password is required to run %s"), getprogname()); + validated |= FLAG_NON_INTERACTIVE; + log_auth_failure(validated, 0); rval = -1; goto done; } /* XXX - should not lecture if askpass helper is being used. */ - lecture(status); + lectured = display_lecture(status); /* Expand any escapes in the prompt. */ prompt = expand_prompt(user_prompt ? user_prompt : def_passprompt, - user_name, user_shost); + auth_pw->pw_name); - rval = verify_user(auth_pw, prompt); + rval = verify_user(auth_pw, prompt, validated); + if (rval == true && lectured) + set_lectured(); + efree(prompt); } /* Only update timestamp if user was validated. */ if (rval == true && ISSET(validated, VALIDATE_OK) && !ISSET(mode, MODE_IGNORE_TICKET) && status != TS_ERROR) - update_timestamp(timestampdir, timestampfile); - efree(timestampdir); - efree(timestampfile); + update_timestamp(auth_pw); +done: + debug_return_bool(rval); +} +/* + * Returns true if the user successfully authenticates, false if not + * or -1 on error. + */ +int +check_user(int validated, int mode) +{ + struct passwd *auth_pw; + int rval = true; + debug_decl(check_user, SUDO_DEBUG_AUTH) + + /* + * Init authentication system regardless of whether we need a password. + * Required for proper PAM session support. + */ + auth_pw = get_authpw(mode); + if (sudo_auth_init(auth_pw) == -1) { + rval = -1; + goto done; + } + + /* + * Don't prompt for the root passwd or if the user is exempt. + * If the user is not changing uid/gid, no need for a password. + */ + if (!def_authenticate || user_is_exempt()) + goto done; + if (user_uid == 0 || (user_uid == runas_pw->pw_uid && + (!runas_gr || user_in_group(sudo_user.pw, runas_gr->gr_name)))) { +#ifdef HAVE_SELINUX + if (user_role == NULL && user_type == NULL) +#endif +#ifdef HAVE_PRIV_SET + if (runas_privs == NULL && runas_limitprivs == NULL) +#endif + goto done; + } + + rval = check_user_interactive(validated, mode, auth_pw); + done: sudo_auth_cleanup(auth_pw); - pw_delref(auth_pw); + sudo_pw_delref(auth_pw); debug_return_bool(rval); } -#define DEFAULT_LECTURE "\n" \ - "We trust you have received the usual lecture from the local System\n" \ - "Administrator. It usually boils down to these three things:\n\n" \ - " #1) Respect the privacy of others.\n" \ - " #2) Think before you type.\n" \ - " #3) With great power comes great responsibility.\n\n" - /* - * Standard sudo lecture. + * Display sudo lecture (standard or custom). + * Returns true if the user was lectured, else false. */ -static void -lecture(int status) +static bool +display_lecture(int status) { FILE *fp; char buf[BUFSIZ]; @@ -204,8 +170,8 @@ lecture(int status) debug_decl(lecture, SUDO_DEBUG_AUTH) if (def_lecture == never || - (def_lecture == once && status != TS_MISSING && status != TS_ERROR)) - debug_return; + (def_lecture == once && already_lectured(status))) + debug_return_bool(false); memset(&msg, 0, sizeof(msg)); memset(&repl, 0, sizeof(repl)); @@ -220,183 +186,18 @@ lecture(int status) fclose(fp); } else { msg.msg_type = SUDO_CONV_ERROR_MSG; - msg.msg = _(DEFAULT_LECTURE); + msg.msg = _("\n" + "We trust you have received the usual lecture from the local System\n" + "Administrator. It usually boils down to these three things:\n\n" + " #1) Respect the privacy of others.\n" + " #2) Think before you type.\n" + " #3) With great power comes great responsibility.\n\n"); sudo_conv(1, &msg, &repl); } - debug_return; + debug_return_bool(true); } /* - * Update the time on the timestamp file/dir or create it if necessary. - */ -static void -update_timestamp(char *timestampdir, char *timestampfile) -{ - debug_decl(update_timestamp, SUDO_DEBUG_AUTH) - - /* If using tty timestamps but we have no tty there is nothing to do. */ - if (def_tty_tickets && !user_ttypath) - debug_return; - - if (timestamp_uid != 0) - set_perms(PERM_TIMESTAMP); - if (timestampfile) { - /* - * Store tty info in timestamp file - */ - int fd = open(timestampfile, O_WRONLY|O_CREAT, 0600); - if (fd == -1) - log_error(USE_ERRNO, _("unable to open %s"), timestampfile); - else { - lock_file(fd, SUDO_LOCK); - if (write(fd, &tty_info, sizeof(tty_info)) != sizeof(tty_info)) { - log_error(USE_ERRNO, _("unable to write to %s"), - timestampfile); - } - close(fd); - } - } else { - if (touch(-1, timestampdir, NULL) == -1) { - if (mkdir(timestampdir, 0700) == -1) { - log_error(USE_ERRNO, _("unable to mkdir %s"), - timestampdir); - } - } - } - if (timestamp_uid != 0) - restore_perms(); - debug_return; -} - -/* - * Expand %h and %u escapes in the prompt and pass back the dynamically - * allocated result. Returns the same string if there are no escapes. - */ -static char * -expand_prompt(char *old_prompt, char *user, char *host) -{ - size_t len, n; - int subst; - char *p, *np, *new_prompt, *endp; - debug_decl(expand_prompt, SUDO_DEBUG_AUTH) - - /* How much space do we need to malloc for the prompt? */ - subst = 0; - for (p = old_prompt, len = strlen(old_prompt); *p; p++) { - if (p[0] =='%') { - switch (p[1]) { - case 'h': - p++; - len += strlen(user_shost) - 2; - subst = 1; - break; - case 'H': - p++; - len += strlen(user_host) - 2; - subst = 1; - break; - case 'p': - p++; - if (def_rootpw) - len += 2; - else if (def_targetpw || def_runaspw) - len += strlen(runas_pw->pw_name) - 2; - else - len += strlen(user_name) - 2; - subst = 1; - break; - case 'u': - p++; - len += strlen(user_name) - 2; - subst = 1; - break; - case 'U': - p++; - len += strlen(runas_pw->pw_name) - 2; - subst = 1; - break; - case '%': - p++; - len--; - subst = 1; - break; - default: - break; - } - } - } - - if (subst) { - new_prompt = emalloc(++len); - endp = new_prompt + len; - for (p = old_prompt, np = new_prompt; *p; p++) { - if (p[0] =='%') { - switch (p[1]) { - case 'h': - p++; - n = strlcpy(np, user_shost, np - endp); - if (n >= np - endp) - goto oflow; - np += n; - continue; - case 'H': - p++; - n = strlcpy(np, user_host, np - endp); - if (n >= np - endp) - goto oflow; - np += n; - continue; - case 'p': - p++; - if (def_rootpw) - n = strlcpy(np, "root", np - endp); - else if (def_targetpw || def_runaspw) - n = strlcpy(np, runas_pw->pw_name, np - endp); - else - n = strlcpy(np, user_name, np - endp); - if (n >= np - endp) - goto oflow; - np += n; - continue; - case 'u': - p++; - n = strlcpy(np, user_name, np - endp); - if (n >= np - endp) - goto oflow; - np += n; - continue; - case 'U': - p++; - n = strlcpy(np, runas_pw->pw_name, np - endp); - if (n >= np - endp) - goto oflow; - np += n; - continue; - case '%': - /* convert %% -> % */ - p++; - break; - default: - /* no conversion */ - break; - } - } - *np++ = *p; - if (np >= endp) - goto oflow; - } - *np = '\0'; - } else - new_prompt = old_prompt; - - debug_return_str(new_prompt); - -oflow: - /* We pre-allocate enough space, so this should never happen. */ - errorx(1, _("internal error, expand_prompt() overflow")); -} - -/* * Checks if the user is exempt from supplying a password. */ bool @@ -411,357 +212,37 @@ user_is_exempt(void) } /* - * Fills in timestampdir as well as timestampfile if using tty tickets. - */ -static int -build_timestamp(char **timestampdir, char **timestampfile) -{ - char *dirparent; - int len; - debug_decl(build_timestamp, SUDO_DEBUG_AUTH) - - dirparent = def_timestampdir; - len = easprintf(timestampdir, "%s/%s", dirparent, user_name); - if (len >= PATH_MAX) - goto bad; - - /* - * Timestamp file may be a file in the directory or NUL to use - * the directory as the timestamp. - */ - if (def_tty_tickets) { - char *p; - - if ((p = strrchr(user_tty, '/'))) - p++; - else - p = user_tty; - if (def_targetpw) - len = easprintf(timestampfile, "%s/%s/%s:%s", dirparent, user_name, - p, runas_pw->pw_name); - else - len = easprintf(timestampfile, "%s/%s/%s", dirparent, user_name, p); - if (len >= PATH_MAX) - goto bad; - } else if (def_targetpw) { - len = easprintf(timestampfile, "%s/%s/%s", dirparent, user_name, - runas_pw->pw_name); - if (len >= PATH_MAX) - goto bad; - } else - *timestampfile = NULL; - - debug_return_int(len); -bad: - log_fatal(0, _("timestamp path too long: %s"), *timestampfile); - debug_return_int(-1); -} - -/* - * Check the timestamp file and directory and return their status. - */ -static int -timestamp_status(char *timestampdir, char *timestampfile, char *user, int flags) -{ - struct stat sb; - struct timeval boottime, mtime; - time_t now; - char *dirparent = def_timestampdir; - int status = TS_ERROR; /* assume the worst */ - debug_decl(timestamp_status, SUDO_DEBUG_AUTH) - - if (timestamp_uid != 0) - set_perms(PERM_TIMESTAMP); - - /* - * Sanity check dirparent and make it if it doesn't already exist. - * We start out assuming the worst (that the dir is not sane) and - * if it is ok upgrade the status to ``no timestamp file''. - * Note that we don't check the parent(s) of dirparent for - * sanity since the sudo dir is often just located in /tmp. - */ - if (lstat(dirparent, &sb) == 0) { - if (!S_ISDIR(sb.st_mode)) - log_error(0, _("%s exists but is not a directory (0%o)"), - dirparent, (unsigned int) sb.st_mode); - else if (sb.st_uid != timestamp_uid) - log_error(0, _("%s owned by uid %u, should be uid %u"), - dirparent, (unsigned int) sb.st_uid, - (unsigned int) timestamp_uid); - else if ((sb.st_mode & 0000022)) - log_error(0, - _("%s writable by non-owner (0%o), should be mode 0700"), - dirparent, (unsigned int) sb.st_mode); - else { - if ((sb.st_mode & 0000777) != 0700) - (void) chmod(dirparent, 0700); - status = TS_MISSING; - } - } else if (errno != ENOENT) { - log_error(USE_ERRNO, _("unable to stat %s"), dirparent); - } else { - /* No dirparent, try to make one. */ - if (ISSET(flags, TS_MAKE_DIRS)) { - if (mkdir(dirparent, S_IRWXU)) - log_error(USE_ERRNO, _("unable to mkdir %s"), - dirparent); - else - status = TS_MISSING; - } - } - if (status == TS_ERROR) - goto done; - - /* - * Sanity check the user's ticket dir. We start by downgrading - * the status to TS_ERROR. If the ticket dir exists and is sane - * this will be upgraded to TS_OLD. If the dir does not exist, - * it will be upgraded to TS_MISSING. - */ - status = TS_ERROR; /* downgrade status again */ - if (lstat(timestampdir, &sb) == 0) { - if (!S_ISDIR(sb.st_mode)) { - if (S_ISREG(sb.st_mode)) { - /* convert from old style */ - if (unlink(timestampdir) == 0) - status = TS_MISSING; - } else - log_error(0, _("%s exists but is not a directory (0%o)"), - timestampdir, (unsigned int) sb.st_mode); - } else if (sb.st_uid != timestamp_uid) - log_error(0, _("%s owned by uid %u, should be uid %u"), - timestampdir, (unsigned int) sb.st_uid, - (unsigned int) timestamp_uid); - else if ((sb.st_mode & 0000022)) - log_error(0, - _("%s writable by non-owner (0%o), should be mode 0700"), - timestampdir, (unsigned int) sb.st_mode); - else { - if ((sb.st_mode & 0000777) != 0700) - (void) chmod(timestampdir, 0700); - status = TS_OLD; /* do date check later */ - } - } else if (errno != ENOENT) { - log_error(USE_ERRNO, _("unable to stat %s"), timestampdir); - } else - status = TS_MISSING; - - /* - * If there is no user ticket dir, AND we are in tty ticket mode, - * AND the TS_MAKE_DIRS flag is set, create the user ticket dir. - */ - if (status == TS_MISSING && timestampfile && ISSET(flags, TS_MAKE_DIRS)) { - if (mkdir(timestampdir, S_IRWXU) == -1) { - status = TS_ERROR; - log_error(USE_ERRNO, _("unable to mkdir %s"), timestampdir); - } - } - - /* - * Sanity check the tty ticket file if it exists. - */ - if (timestampfile && status != TS_ERROR) { - if (status != TS_MISSING) - status = TS_NOFILE; /* dir there, file missing */ - if (def_tty_tickets && !user_ttypath) - goto done; /* no tty, always prompt */ - if (lstat(timestampfile, &sb) == 0) { - if (!S_ISREG(sb.st_mode)) { - status = TS_ERROR; - log_error(0, _("%s exists but is not a regular file (0%o)"), - timestampfile, (unsigned int) sb.st_mode); - } else { - /* If bad uid or file mode, complain and kill the bogus file. */ - if (sb.st_uid != timestamp_uid) { - log_error(0, - _("%s owned by uid %u, should be uid %u"), - timestampfile, (unsigned int) sb.st_uid, - (unsigned int) timestamp_uid); - (void) unlink(timestampfile); - } else if ((sb.st_mode & 0000022)) { - log_error(0, - _("%s writable by non-owner (0%o), should be mode 0600"), - timestampfile, (unsigned int) sb.st_mode); - (void) unlink(timestampfile); - } else { - /* If not mode 0600, fix it. */ - if ((sb.st_mode & 0000777) != 0600) - (void) chmod(timestampfile, 0600); - - /* - * Check for stored tty info. If the file is zero-sized - * it is an old-style timestamp with no tty info in it. - * If removing, we don't care about the contents. - * The actual mtime check is done later. - */ - if (ISSET(flags, TS_REMOVE)) { - status = TS_OLD; - } else if (sb.st_size != 0) { - struct tty_info info; - int fd = open(timestampfile, O_RDONLY, 0644); - if (fd != -1) { - if (read(fd, &info, sizeof(info)) == sizeof(info) && - memcmp(&info, &tty_info, sizeof(info)) == 0) { - status = TS_OLD; - } - close(fd); - } - } - } - } - } else if (errno != ENOENT) { - log_error(USE_ERRNO, _("unable to stat %s"), timestampfile); - status = TS_ERROR; - } - } - - /* - * If the file/dir exists and we are not removing it, check its mtime. - */ - if (status == TS_OLD && !ISSET(flags, TS_REMOVE)) { - mtim_get(&sb, &mtime); - /* Negative timeouts only expire manually (sudo -k). */ - if (def_timestamp_timeout < 0 && mtime.tv_sec != 0) - status = TS_CURRENT; - else { - now = time(NULL); - if (def_timestamp_timeout && - now - mtime.tv_sec < 60 * def_timestamp_timeout) { - /* - * Check for bogus time on the stampfile. The clock may - * have been set back or someone could be trying to spoof us. - */ - if (mtime.tv_sec > now + 60 * def_timestamp_timeout * 2) { - time_t tv_sec = (time_t)mtime.tv_sec; - log_error(0, - _("timestamp too far in the future: %20.20s"), - 4 + ctime(&tv_sec)); - if (timestampfile) - (void) unlink(timestampfile); - else - (void) rmdir(timestampdir); - status = TS_MISSING; - } else if (get_boottime(&boottime) && timevalcmp(&mtime, &boottime, <)) { - status = TS_OLD; - } else { - status = TS_CURRENT; - } - } - } - } - -done: - if (timestamp_uid != 0) - restore_perms(); - debug_return_int(status); -} - -/* - * Remove the timestamp ticket file/dir. - */ -void -remove_timestamp(bool remove) -{ - struct timeval tv; - char *timestampdir, *timestampfile, *path; - int status; - debug_decl(remove_timestamp, SUDO_DEBUG_AUTH) - - if (build_timestamp(×tampdir, ×tampfile) == -1) - debug_return; - - status = timestamp_status(timestampdir, timestampfile, user_name, - TS_REMOVE); - if (status != TS_MISSING && status != TS_ERROR) { - path = timestampfile ? timestampfile : timestampdir; - if (remove) { - if (timestampfile) - status = unlink(timestampfile); - else - status = rmdir(timestampdir); - if (status == -1 && errno != ENOENT) { - log_error(0, - _("unable to remove %s (%s), will reset to the epoch"), - path, strerror(errno)); - remove = false; - } - } - if (!remove) { - timevalclear(&tv); - if (touch(-1, path, &tv) == -1 && errno != ENOENT) - error(1, _("unable to reset %s to the epoch"), path); - } - } - efree(timestampdir); - efree(timestampfile); - - debug_return; -} - -/* - * Returns true if tty lives on a devpts, /dev or /devices filesystem, else - * false. Unlike most filesystems, the ctime of devpts nodes is not updated - * when the device node is written to, only when the inode's status changes, - * typically via the chmod, chown, link, rename, or utimes system calls. - * Since the ctime is "stable" in this case, we can stash it the tty ticket - * file and use it to determine whether the tty ticket file is stale. - */ -static bool -tty_is_devpts(const char *tty) -{ - bool retval = false; -#ifdef __linux__ - struct statfs sfs; - debug_decl(tty_is_devpts, SUDO_DEBUG_PTY) - -#ifndef DEVPTS_SUPER_MAGIC -# define DEVPTS_SUPER_MAGIC 0x1cd1 -#endif - - if (statfs(tty, &sfs) == 0) { - if (sfs.f_type == DEVPTS_SUPER_MAGIC) - retval = true; - } -#elif defined(__sun) && defined(__SVR4) - struct statvfs sfs; - debug_decl(tty_is_devpts, SUDO_DEBUG_PTY) - - if (statvfs(tty, &sfs) == 0) { - if (strcmp(sfs.f_fstr, "dev") == 0 || strcmp(sfs.f_fstr, "devices") == 0) - retval = true; - } -#else - debug_decl(tty_is_devpts, SUDO_DEBUG_PTY) -#endif /* __linux__ */ - debug_return_bool(retval); -} - -/* * Get passwd entry for the user we are going to authenticate as. * By default, this is the user invoking sudo. In the most common * case, this matches sudo_user.pw or runas_pw. */ static struct passwd * -get_authpw(void) +get_authpw(int mode) { struct passwd *pw; debug_decl(get_authpw, SUDO_DEBUG_AUTH) - if (def_rootpw) { - if ((pw = sudo_getpwuid(ROOT_UID)) == NULL) - log_fatal(0, _("unknown uid: %u"), ROOT_UID); - } else if (def_runaspw) { - if ((pw = sudo_getpwnam(def_runas_default)) == NULL) - log_fatal(0, _("unknown user: %s"), def_runas_default); - } else if (def_targetpw) { - if (runas_pw->pw_name == NULL) - log_fatal(NO_MAIL|MSG_ONLY, _("unknown uid: %u"), - (unsigned int) runas_pw->pw_uid); - pw_addref(runas_pw); - pw = runas_pw; - } else { - pw_addref(sudo_user.pw); + if (ISSET(mode, (MODE_CHECK|MODE_LIST))) { + /* In list mode we always prompt for the user's password. */ + sudo_pw_addref(sudo_user.pw); pw = sudo_user.pw; + } else { + if (def_rootpw) { + if ((pw = sudo_getpwuid(ROOT_UID)) == NULL) + log_fatal(0, N_("unknown uid: %u"), ROOT_UID); + } else if (def_runaspw) { + if ((pw = sudo_getpwnam(def_runas_default)) == NULL) + log_fatal(0, N_("unknown user: %s"), def_runas_default); + } else if (def_targetpw) { + if (runas_pw->pw_name == NULL) + log_fatal(NO_MAIL|MSG_ONLY, N_("unknown uid: %u"), + (unsigned int) runas_pw->pw_uid); + sudo_pw_addref(runas_pw); + pw = runas_pw; + } else { + sudo_pw_addref(sudo_user.pw); + pw = sudo_user.pw; + } } debug_return_ptr(pw);