2 * Main coding by Elliot Lee <sopwith@redhat.com>, Red Hat Software.
4 * Copyright (c) Jan Rêkorajski, 1999.
5 * Copyright (c) Red Hat, Inc., 2007, 2008.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, and the entire permission notice in its entirety,
12 * including the disclaimer of warranties.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. The name of the author may not be used to endorse or promote
17 * products derived from this software without specific prior
20 * ALTERNATIVELY, this product may be distributed under the terms of
21 * the GNU Public License, in which case the provisions of the GPL are
22 * required INSTEAD OF the above restrictions. (This clause is
23 * necessary due to a potential bad interaction between the GPL and
24 * the restrictions contained in a BSD-style copyright.)
26 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
27 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
30 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
36 * OF THE POSSIBILITY OF SUCH DAMAGE.
48 #include <sys/types.h>
52 #include <time.h> /* for time() */
61 #include <sys/resource.h>
63 #include <security/_pam_macros.h>
65 /* indicate the following groups are defined */
67 #define PAM_SM_PASSWORD
69 #include <security/pam_modules.h>
70 #include <security/pam_ext.h>
71 #include <security/pam_modutil.h>
75 #include "passverify.h"
78 #if (HAVE_YP_GET_DEFAULT_DOMAIN || HAVE_GETDOMAINNAME) && HAVE_YP_MASTER
85 # if HAVE_RPCSVC_YP_PROT_H
86 # include <rpcsvc/yp_prot.h>
89 # if HAVE_RPCSVC_YPCLNT_H
90 # include <rpcsvc/ypclnt.h>
93 # include "yppasswd.h"
95 # if !HAVE_DECL_GETRPCPORT &&!HAVE_RPCB_GETADDR
96 extern int getrpcport(const char *host, unsigned long prognum,
97 unsigned long versnum, unsigned int proto);
98 # endif /* GNU libc 2.1 */
103 Gets in username (has to be done) from the calling program
104 Does authentication of user (only if we are not running as root)
105 Gets new password/checks for sanity
111 #define _UNIX_OLD_AUTHTOK "-UN*X-OLD-PASS"
112 #define _UNIX_NEW_AUTHTOK "-UN*X-NEW-PASS"
114 #define MAX_PASSWD_TRIES 3
117 #ifdef HAVE_RPCB_GETADDR
118 static unsigned short
119 __taddr2port (const struct netconfig *nconf, const struct netbuf *nbuf)
121 unsigned short port = 0;
122 struct __rpc_sockinfo si;
123 struct sockaddr_in *sin;
124 struct sockaddr_in6 *sin6;
125 if (!__rpc_nconf2sockinfo(nconf, &si))
132 port = sin->sin_port;
136 port = sin6->sin6_port;
146 static char *getNISserver(pam_handle_t *pamh, unsigned int ctrl)
151 #if defined(HAVE_RPCB_GETADDR)
152 struct netconfig *nconf;
153 struct netbuf svcaddr;
154 char addrbuf[INET6_ADDRSTRLEN];
160 #ifdef HAVE_YP_GET_DEFAULT_DOMAIN
161 if ((err = yp_get_default_domain(&domainname)) != 0) {
162 pam_syslog(pamh, LOG_WARNING, "can't get local yp domain: %s",
166 #elif defined(HAVE_GETDOMAINNAME)
167 char domainname_res[256];
169 if (getdomainname (domainname_res, sizeof (domainname_res)) == 0)
171 if (strcmp (domainname_res, "(none)") == 0)
173 /* If domainname is not set, some systems will return "(none)" */
174 domainname_res[0] = '\0';
176 domainname = domainname_res;
178 else domainname = NULL;
181 if ((err = yp_master(domainname, "passwd.byname", &master)) != 0) {
182 pam_syslog(pamh, LOG_WARNING, "can't find the master ypserver: %s",
186 #ifdef HAVE_RPCB_GETADDR
188 svcaddr.maxlen = sizeof (addrbuf);
189 svcaddr.buf = addrbuf;
193 handle = setnetconfig();
194 while ((nconf = getnetconfig(handle)) != NULL) {
195 if (!strcmp(nconf->nc_proto, "udp")) {
196 if (rpcb_getaddr(YPPASSWDPROG, YPPASSWDPROC_UPDATE,
197 nconf, &svcaddr, master)) {
198 port = __taddr2port (nconf, &svcaddr);
199 endnetconfig (handle);
204 if (rpc_createerr.cf_stat != RPC_UNKNOWNHOST) {
205 clnt_pcreateerror (master);
206 pam_syslog (pamh, LOG_ERR,
207 "rpcb_getaddr (%s) failed!", master);
214 pam_syslog (pamh, LOG_ERR,
215 "Cannot find suitable transport for protocol 'udp'");
219 port = getrpcport(master, YPPASSWDPROG, YPPASSWDPROC_UPDATE, IPPROTO_UDP);
222 pam_syslog(pamh, LOG_WARNING,
223 "yppasswdd not running on NIS master host");
226 if (port >= IPPORT_RESERVED) {
227 pam_syslog(pamh, LOG_WARNING,
228 "yppasswd daemon running on illegal port");
231 if (on(UNIX_DEBUG, ctrl)) {
232 pam_syslog(pamh, LOG_DEBUG, "Use NIS server on %s with port %d",
241 static int _unix_run_update_binary(pam_handle_t *pamh, unsigned int ctrl, const char *user,
242 const char *fromwhat, const char *towhat, int remember)
244 int retval, child, fds[2];
245 struct sigaction newsa, oldsa;
248 /* create a pipe for the password */
249 if (pipe(fds) != 0) {
250 D(("could not make pipe"));
254 if (off(UNIX_NOREAP, ctrl)) {
256 * This code arranges that the demise of the child does not cause
257 * the application to receive a signal it is not expecting - which
258 * may kill the application or worse.
260 * The "noreap" module argument is provided so that the admin can
261 * override this behavior.
263 memset(&newsa, '\0', sizeof(newsa));
264 newsa.sa_handler = SIG_DFL;
265 sigaction(SIGCHLD, &newsa, &oldsa);
271 static char *envp[] = { NULL };
272 const char *args[] = { NULL, NULL, NULL, NULL, NULL, NULL };
275 /* XXX - should really tidy up PAM here too */
277 /* reopen stdin as pipe */
278 if (dup2(fds[0], STDIN_FILENO) != STDIN_FILENO) {
279 pam_syslog(pamh, LOG_ERR, "dup2 of %s failed: %m", "stdin");
280 _exit(PAM_AUTHINFO_UNAVAIL);
283 if (pam_modutil_sanitize_helper_fds(pamh, PAM_MODUTIL_IGNORE_FD,
285 PAM_MODUTIL_PIPE_FD) < 0) {
286 _exit(PAM_AUTHINFO_UNAVAIL);
289 /* exec binary helper */
290 args[0] = UPDATE_HELPER;
293 if (on(UNIX_SHADOW, ctrl))
298 snprintf(buffer, sizeof(buffer), "%d", remember);
301 execve(UPDATE_HELPER, (char *const *) args, envp);
303 /* should not get here: exit with error */
304 D(("helper binary is not available"));
305 _exit(PAM_AUTHINFO_UNAVAIL);
306 } else if (child > 0) {
308 /* if the stored password is NULL */
311 int len = strlen(fromwhat);
313 if (len > PAM_MAX_RESP_SIZE)
314 len = PAM_MAX_RESP_SIZE;
315 pam_modutil_write(fds[1], fromwhat, len);
317 pam_modutil_write(fds[1], "", 1);
319 int len = strlen(towhat);
321 if (len > PAM_MAX_RESP_SIZE)
322 len = PAM_MAX_RESP_SIZE;
323 pam_modutil_write(fds[1], towhat, len);
325 pam_modutil_write(fds[1], "", 1);
327 close(fds[0]); /* close here to avoid possible SIGPIPE above */
329 /* wait for helper to complete: */
330 while ((rc=waitpid(child, &retval, 0)) < 0 && errno == EINTR);
332 pam_syslog(pamh, LOG_ERR, "unix_update waitpid failed: %m");
333 retval = PAM_AUTHTOK_ERR;
334 } else if (!WIFEXITED(retval)) {
335 pam_syslog(pamh, LOG_ERR, "unix_update abnormal exit: %d", retval);
336 retval = PAM_AUTHTOK_ERR;
338 retval = WEXITSTATUS(retval);
344 retval = PAM_AUTH_ERR;
347 if (off(UNIX_NOREAP, ctrl)) {
348 sigaction(SIGCHLD, &oldsa, NULL); /* restore old signal handler */
355 static int check_old_password(const char *forwho, const char *newpass)
357 static char buf[16384];
358 char *s_luser, *s_uid, *s_npas, *s_pas;
359 int retval = PAM_SUCCESS;
361 size_t len = strlen(forwho);
363 opwfile = fopen(OLD_PASSWORDS_FILE, "r");
367 while (fgets(buf, 16380, opwfile)) {
368 if (!strncmp(buf, forwho, len) && (buf[len] == ':' ||
371 buf[strlen(buf) - 1] = '\0';
372 s_luser = strtok_r(buf, ":,", &sptr);
373 s_uid = strtok_r(NULL, ":,", &sptr);
374 s_npas = strtok_r(NULL, ":,", &sptr);
375 s_pas = strtok_r(NULL, ":,", &sptr);
376 while (s_pas != NULL) {
377 char *md5pass = Goodcrypt_md5(newpass, s_pas);
378 if (md5pass == NULL || !strcmp(md5pass, s_pas)) {
379 _pam_delete(md5pass);
380 retval = PAM_AUTHTOK_ERR;
383 s_pas = strtok_r(NULL, ":,", &sptr);
384 _pam_delete(md5pass);
394 static int _do_setpass(pam_handle_t* pamh, const char *forwho,
395 const char *fromwhat,
396 char *towhat, unsigned int ctrl, int remember)
398 struct passwd *pwd = NULL;
405 pwd = getpwnam(forwho);
408 retval = PAM_AUTHTOK_ERR;
412 if (on(UNIX_NIS, ctrl) && _unix_comesfromsource(pamh, forwho, 0, 1)) {
414 if ((master=getNISserver(pamh, ctrl)) != NULL) {
415 struct timeval timeout;
416 struct yppasswd yppwd;
421 /* Unlock passwd file to avoid deadlock */
425 /* Initialize password information */
426 yppwd.newpw.pw_passwd = pwd->pw_passwd;
427 yppwd.newpw.pw_name = pwd->pw_name;
428 yppwd.newpw.pw_uid = pwd->pw_uid;
429 yppwd.newpw.pw_gid = pwd->pw_gid;
430 yppwd.newpw.pw_gecos = pwd->pw_gecos;
431 yppwd.newpw.pw_dir = pwd->pw_dir;
432 yppwd.newpw.pw_shell = pwd->pw_shell;
433 yppwd.oldpass = fromwhat ? strdup (fromwhat) : strdup ("");
434 yppwd.newpw.pw_passwd = towhat;
436 D(("Set password %s for %s", yppwd.newpw.pw_passwd, forwho));
438 /* The yppasswd.x file said `unix authentication required',
439 * so I added it. This is the only reason it is in here.
440 * My yppasswdd doesn't use it, but maybe some others out there
443 clnt = clnt_create(master, YPPASSWDPROG, YPPASSWDVERS, "udp");
444 clnt->cl_auth = authunix_create_default();
445 memset((char *) &status, '\0', sizeof(status));
448 err = clnt_call(clnt, YPPASSWDPROC_UPDATE,
449 (xdrproc_t) xdr_yppasswd, (char *) &yppwd,
450 (xdrproc_t) xdr_int, (char *) &status,
453 free (yppwd.oldpass);
456 _make_remark(pamh, ctrl, PAM_TEXT_INFO,
459 D(("Error while changing NIS password.\n"));
461 D(("The password has%s been changed on %s.",
462 (err || status) ? " not" : "", master));
463 pam_syslog(pamh, LOG_NOTICE, "password%s changed for %s on %s",
464 (err || status) ? " not" : "", pwd->pw_name, master);
466 auth_destroy(clnt->cl_auth);
469 _make_remark(pamh, ctrl, PAM_TEXT_INFO,
470 _("NIS password could not be changed."));
471 retval = PAM_TRY_AGAIN;
477 retval = PAM_TRY_AGAIN;
480 if (on(UNIX_DEBUG, ctrl)) {
481 pam_syslog(pamh, LOG_DEBUG, "No NIS support available");
484 retval = PAM_TRY_AGAIN;
488 if (_unix_comesfromsource(pamh, forwho, 1, 0)) {
490 if (lock_pwdf() != PAM_SUCCESS) {
491 return PAM_AUTHTOK_LOCK_BUSY;
495 if (unix_selinux_confined())
496 return _unix_run_update_binary(pamh, ctrl, forwho, fromwhat, towhat, remember);
498 /* first, save old password */
499 if (save_old_password(pamh, forwho, fromwhat, remember)) {
500 retval = PAM_AUTHTOK_ERR;
503 if (on(UNIX_SHADOW, ctrl) || is_pwd_shadowed(pwd)) {
504 retval = unix_update_shadow(pamh, forwho, towhat);
505 if (retval == PAM_SUCCESS)
506 if (!is_pwd_shadowed(pwd))
507 retval = unix_update_passwd(pamh, forwho, "x");
509 retval = unix_update_passwd(pamh, forwho, towhat);
520 static int _unix_verify_shadow(pam_handle_t *pamh, const char *user, unsigned int ctrl)
522 struct passwd *pwent = NULL; /* Password and shadow password */
523 struct spwd *spent = NULL; /* file entries for the user */
527 retval = get_account_info(pamh, user, &pwent, &spent);
528 if (retval == PAM_USER_UNKNOWN) {
532 if (retval == PAM_SUCCESS && spent == NULL)
535 if (retval == PAM_UNIX_RUN_HELPER) {
536 retval = _unix_run_verify_binary(pamh, ctrl, user, &daysleft);
537 if (retval == PAM_AUTH_ERR || retval == PAM_USER_UNKNOWN)
540 else if (retval == PAM_SUCCESS)
541 retval = check_shadow_expiry(pamh, spent, &daysleft);
543 if (on(UNIX__IAMROOT, ctrl) || retval == PAM_NEW_AUTHTOK_REQD)
549 static int _pam_unix_approve_pass(pam_handle_t * pamh
551 ,const char *pass_old
552 ,const char *pass_new,
556 const char *remark = NULL;
557 int retval = PAM_SUCCESS;
559 D(("&new=%p, &old=%p", pass_old, pass_new));
560 D(("new=[%s]", pass_new));
561 D(("old=[%s]", pass_old));
563 if (pass_new == NULL || (pass_old && !strcmp(pass_old, pass_new))) {
564 if (on(UNIX_DEBUG, ctrl)) {
565 pam_syslog(pamh, LOG_DEBUG, "bad authentication token");
567 _make_remark(pamh, ctrl, PAM_ERROR_MSG, pass_new == NULL ?
568 _("No password supplied") : _("Password unchanged"));
569 return PAM_AUTHTOK_ERR;
572 * if one wanted to hardwire authentication token strength
573 * checking this would be the place - AGM
576 retval = pam_get_item(pamh, PAM_USER, &user);
577 if (retval != PAM_SUCCESS) {
578 if (on(UNIX_DEBUG, ctrl)) {
579 pam_syslog(pamh, LOG_ERR, "Can not get username");
580 return PAM_AUTHTOK_ERR;
583 if (off(UNIX__IAMROOT, ctrl)) {
584 if (strlen(pass_new) < pass_min_len)
585 remark = _("You must choose a longer password");
586 D(("length check [%s]", remark));
587 if (on(UNIX_REMEMBER_PASSWD, ctrl)) {
588 if ((retval = check_old_password(user, pass_new)) == PAM_AUTHTOK_ERR)
589 remark = _("Password has been already used. Choose another.");
590 if (retval == PAM_ABORT) {
591 pam_syslog(pamh, LOG_ERR, "can't open %s file to check old passwords",
598 _make_remark(pamh, ctrl, PAM_ERROR_MSG, remark);
599 retval = PAM_AUTHTOK_ERR;
605 pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
607 unsigned int ctrl, lctrl;
611 int pass_min_len = 0;
613 /* <DO NOT free() THESE> */
616 const char *pass_old, *pass_new;
617 /* </DO NOT free() THESE> */
621 ctrl = _set_ctrl(pamh, flags, &remember, &rounds, &pass_min_len,
625 * First get the name of a user
627 retval = pam_get_user(pamh, &user, NULL);
628 if (retval == PAM_SUCCESS) {
630 * Various libraries at various times have had bugs related to
631 * '+' or '-' as the first character of a user name. Don't
634 if (user == NULL || user[0] == '-' || user[0] == '+') {
635 pam_syslog(pamh, LOG_ERR, "bad username [%s]", user);
636 return PAM_USER_UNKNOWN;
638 if (retval == PAM_SUCCESS && on(UNIX_DEBUG, ctrl))
639 pam_syslog(pamh, LOG_DEBUG, "username [%s] obtained",
642 if (on(UNIX_DEBUG, ctrl))
643 pam_syslog(pamh, LOG_DEBUG,
644 "password - could not identify user");
648 D(("Got username of %s", user));
651 * Before we do anything else, check to make sure that the user's
652 * info is in one of the databases we can modify from this module,
653 * which currently is 'files' and 'nis'. We have to do this because
654 * getpwnam() doesn't tell you *where* the information it gives you
655 * came from, nor should it. That's our job.
657 if (_unix_comesfromsource(pamh, user, 1, on(UNIX_NIS, ctrl)) == 0) {
658 pam_syslog(pamh, LOG_DEBUG,
659 "user \"%s\" does not exist in /etc/passwd%s",
660 user, on(UNIX_NIS, ctrl) ? " or NIS" : "");
661 return PAM_USER_UNKNOWN;
664 _unix_getpwnam(pamh, user, 1, 1, &pwd);
666 pam_syslog(pamh, LOG_DEBUG,
667 "user \"%s\" has corrupted passwd entry",
669 return PAM_USER_UNKNOWN;
674 * This is not an AUTH module!
676 if (on(UNIX__NONULL, ctrl))
677 set(UNIX__NULLOK, ctrl);
679 if (on(UNIX__PRELIM, ctrl)) {
681 * obtain and verify the current password (OLDAUTHTOK) for
686 if (_unix_blankpasswd(pamh, ctrl, user)) {
688 } else if (off(UNIX__IAMROOT, ctrl) ||
689 (on(UNIX_NIS, ctrl) && _unix_comesfromsource(pamh, user, 0, 1))) {
690 /* instruct user what is happening */
691 if (off(UNIX__QUIET, ctrl)) {
692 retval = pam_info(pamh, _("Changing password for %s."), user);
693 if (retval != PAM_SUCCESS)
696 retval = pam_get_authtok(pamh, PAM_OLDAUTHTOK, &pass_old, NULL);
698 if (retval != PAM_SUCCESS) {
699 pam_syslog(pamh, LOG_NOTICE,
700 "password - (old) token not obtained");
703 /* verify that this is the password for this user */
705 retval = _unix_verify_password(pamh, user, pass_old, ctrl);
707 D(("process run by root so do nothing this time around"));
709 retval = PAM_SUCCESS; /* root doesn't have too */
712 if (retval != PAM_SUCCESS) {
713 D(("Authentication failed"));
718 retval = _unix_verify_shadow(pamh,user, ctrl);
719 if (retval == PAM_AUTHTOK_ERR) {
720 if (off(UNIX__IAMROOT, ctrl))
721 _make_remark(pamh, ctrl, PAM_ERROR_MSG,
722 _("You must wait longer to change your password"));
724 retval = PAM_SUCCESS;
726 } else if (on(UNIX__UPDATE, ctrl)) {
728 * tpass is used below to store the _pam_md() return; it
729 * should be _pam_delete()'d.
736 * obtain the proposed password
742 * get the old token back. NULL was ok only if root [at this
743 * point we assume that this has already been enforced on a
744 * previous call to this function].
747 retval = pam_get_item(pamh, PAM_OLDAUTHTOK, &item);
749 if (retval != PAM_SUCCESS) {
750 pam_syslog(pamh, LOG_NOTICE, "user not authenticated");
754 D(("pass_old [%s]", pass_old));
756 D(("get new password now"));
760 if (on(UNIX_USE_AUTHTOK, lctrl)) {
761 set(UNIX_USE_FIRST_PASS, lctrl);
763 if (on(UNIX_USE_FIRST_PASS, lctrl)) {
764 retry = MAX_PASSWD_TRIES-1;
766 retval = PAM_AUTHTOK_ERR;
767 while ((retval != PAM_SUCCESS) && (retry++ < MAX_PASSWD_TRIES)) {
769 * use_authtok is to force the use of a previously entered
770 * password -- needed for pluggable password strength checking
773 retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass_new, NULL);
775 if (retval != PAM_SUCCESS) {
776 if (on(UNIX_DEBUG, ctrl)) {
777 pam_syslog(pamh, LOG_ERR,
778 "password - new password not obtained");
780 pass_old = NULL; /* tidy up */
783 D(("returned to _unix_chauthtok"));
786 * At this point we know who the user is and what they
787 * propose as their new password. Verify that the new
788 * password is acceptable.
791 if (*(const char *)pass_new == '\0') { /* "\0" password = NULL */
794 retval = _pam_unix_approve_pass(pamh, ctrl, pass_old,
795 pass_new, pass_min_len);
797 if (retval != PAM_SUCCESS) {
798 pam_set_item(pamh, PAM_AUTHTOK, NULL);
802 if (retval != PAM_SUCCESS) {
803 pam_syslog(pamh, LOG_NOTICE,
804 "new password not acceptable");
805 pass_new = pass_old = NULL; /* tidy up */
808 if (lock_pwdf() != PAM_SUCCESS) {
809 return PAM_AUTHTOK_LOCK_BUSY;
813 retval = _unix_verify_password(pamh, user, pass_old, ctrl);
814 if (retval != PAM_SUCCESS) {
815 pam_syslog(pamh, LOG_NOTICE, "user password changed by another process");
821 retval = _unix_verify_shadow(pamh, user, ctrl);
822 if (retval != PAM_SUCCESS) {
823 pam_syslog(pamh, LOG_NOTICE, "user shadow entry expired");
828 retval = _pam_unix_approve_pass(pamh, ctrl, pass_old, pass_new,
830 if (retval != PAM_SUCCESS) {
831 pam_syslog(pamh, LOG_NOTICE,
832 "new password not acceptable 2");
833 pass_new = pass_old = NULL; /* tidy up */
839 * By reaching here we have approved the passwords and must now
840 * rebuild the password database file.
844 * First we encrypt the new password.
847 tpass = create_password_hash(pamh, pass_new, ctrl, rounds);
849 pam_syslog(pamh, LOG_CRIT,
850 "crypt() failure or out of memory for password");
851 pass_new = pass_old = NULL; /* tidy up */
856 D(("password processed"));
858 /* update the password database(s) -- race conditions..? */
860 retval = _do_setpass(pamh, user, pass_old, tpass, ctrl,
862 /* _do_setpass has called unlock_pwdf for us */
865 pass_old = pass_new = NULL;
866 } else { /* something has broken with the module */
867 pam_syslog(pamh, LOG_CRIT,
868 "password received unknown request");
872 D(("retval was %d", retval));