From 0d81263e26ccbb9013205927cf99c0c1cec52c6a Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Sun, 1 Dec 2013 19:12:21 -0700 Subject: [PATCH] Instead of setprogname(), add initprogname() which gets the program name for getprogname() using /proc or pstat() if possible. --- MANIFEST | 2 +- common/Makefile.in | 8 +- common/progname.c | 123 ++++++++++++++++++ compat/Makefile.in | 3 - compat/getprogname.c | 49 ------- configure | 7 - configure.ac | 2 - include/missing.h | 2 +- mkdep.pl | 2 +- plugins/sample/sample_plugin.c | 4 +- plugins/sudoers/policy.c | 4 +- .../regress/check_symbols/check_symbols.c | 6 +- .../regress/iolog_path/check_iolog_path.c | 6 +- plugins/sudoers/regress/logging/check_wrap.c | 6 +- plugins/sudoers/regress/parser/check_addr.c | 6 +- plugins/sudoers/sudoreplay.c | 5 +- plugins/sudoers/testsudoers.c | 4 +- plugins/sudoers/visudo.c | 5 +- src/parse_args.c | 2 +- src/regress/ttyname/check_ttyname.c | 4 +- src/sudo.c | 5 +- 21 files changed, 147 insertions(+), 108 deletions(-) create mode 100644 common/progname.c delete mode 100644 compat/getprogname.c diff --git a/MANIFEST b/MANIFEST index 72de7a00d..193a9868c 100644 --- a/MANIFEST +++ b/MANIFEST @@ -21,6 +21,7 @@ common/fileops.c common/fmt_string.c common/gidlist.c common/lbuf.c +common/progname.c common/regress/sudo_conf/conf_test.c common/regress/sudo_conf/test1.in common/regress/sudo_conf/test1.out.ok @@ -69,7 +70,6 @@ compat/getgrouplist.c compat/getline.c compat/getopt.h compat/getopt_long.c -compat/getprogname.c compat/glob.c compat/glob.h compat/isblank.c diff --git a/common/Makefile.in b/common/Makefile.in index 578c90bd5..3314fd1eb 100644 --- a/common/Makefile.in +++ b/common/Makefile.in @@ -67,9 +67,9 @@ DEFS = @OSDEFS@ -D_PATH_SUDO_CONF=\"$(sysconfdir)/sudo.conf\" SHELL = @SHELL@ LTOBJS = alloc.lo atobool.lo atoid.lo event.lo fatal.lo fileops.lo \ - fmt_string.lo gidlist.lo lbuf.lo secure_path.lo setgroups.lo \ - sudo_conf.lo sudo_debug.lo sudo_dso.lo sudo_printf.lo term.lo \ - ttysize.lo @COMMON_OBJS@ + fmt_string.lo gidlist.lo lbuf.lo progname.lo secure_path.lo \ + setgroups.lo sudo_conf.lo sudo_debug.lo sudo_dso.lo sudo_printf.lo \ + term.lo ttysize.lo @COMMON_OBJS@ PARSELN_TEST_OBJS = parseln_test.lo locale_stub.lo @@ -228,6 +228,8 @@ parseln_test.lo: $(srcdir)/regress/sudo_parseln/parseln_test.c \ $(incdir)/fileops.h $(incdir)/missing.h \ $(top_builddir)/config.h $(top_srcdir)/compat/stdbool.h $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(DEFS) $(srcdir)/regress/sudo_parseln/parseln_test.c +progname.lo: $(srcdir)/progname.c $(incdir)/missing.h $(top_builddir)/config.h + $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(DEFS) $(srcdir)/progname.c secure_path.lo: $(srcdir)/secure_path.c $(incdir)/missing.h \ $(incdir)/secure_path.h $(incdir)/sudo_debug.h \ $(top_builddir)/config.h diff --git a/common/progname.c b/common/progname.c new file mode 100644 index 000000000..59655c416 --- /dev/null +++ b/common/progname.c @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2013 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, 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. + */ + +#include + +/* Large files not supported by procfs.h */ +#if defined(HAVE_PROCFS_H) || defined(HAVE_SYS_PROCFS_H) +# undef _FILE_OFFSET_BITS +# undef _LARGE_FILES +#endif + +#include +#ifdef HAVE_PSTAT_GETPROC +# include +# include +#endif +#if defined(HAVE_PROCFS_H) +# include +#elif defined(HAVE_SYS_PROCFS_H) +# include +#endif + +#include +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif /* STDC_HEADERS */ +#ifdef HAVE_STRING_H +# include +#endif /* HAVE_STRING_H */ +#ifdef HAVE_STRINGS_H +# include +#endif /* HAVE_STRINGS_H */ +#include +#include + +#include "missing.h" + +#if defined(HAVE_GETPROGNAME) || defined(HAVE___PROGNAME) + +/* STUB */ +void +initprogname(const char *name) +{ + return; +} + +#else + +static const char *progname = ""; + +void +initprogname(const char *name) +{ + const char *base; +#ifdef HAVE_PSTAT_GETPROC + static char ucomm[PST_UCOMMLEN]; + struct pst_status pstat; + int rc; + + /* + * Determine the progname from pst_ucomm in struct pst_status. + * We may get EOVERFLOW if the whole thing doesn't fit but that is OK. + */ + rc = pstat_getproc(&pstat, sizeof(pstat), (size_t)0, (int)getpid()); + if (rc != -1 || errno == EOVERFLOW) { + strlcpy(ucomm, pstat.pst_ucomm, sizeof(ucomm)); + progname = ucomm; + return; + } +#elif defined(HAVE_PROCFS_H) || defined(HAVE_SYS_PROCFS_H) + /* XXX - configure check for psinfo.pr_fname */ + static char ucomm[PRFNSZ]; + struct psinfo psinfo; + char path[PATH_MAX]; + ssize_t nread; + int fd; + + /* Try to determine the tty from pr_ttydev in /proc/pid/psinfo. */ + snprintf(path, sizeof(path), "/proc/%u/psinfo", (unsigned int)getpid()); + if ((fd = open(path, O_RDONLY, 0)) != -1) { + nread = read(fd, &psinfo, sizeof(psinfo)); + close(fd); + if (nread == (ssize_t)sizeof(psinfo)) { + strlcpy(ucomm, psinfo.pr_fname, sizeof(ucomm)); + progname = ucomm; + return; + } + } +#endif /* HAVE_PSTAT_GETPROC */ + + if ((base = strrchr(name, '/')) != NULL) { + base++; + } else { + base = name; + } + progname = base; +} + +const char * +getprogname(void) +{ + return progname; +} + +#endif /* !HAVE_GETPROGNAME && !HAVE___PROGNAME */ diff --git a/compat/Makefile.in b/compat/Makefile.in index 30716ff6f..ef11f307e 100644 --- a/compat/Makefile.in +++ b/compat/Makefile.in @@ -185,9 +185,6 @@ getline.lo: $(srcdir)/getline.c $(incdir)/missing.h $(top_builddir)/config.h getopt_long.lo: $(srcdir)/getopt_long.c $(incdir)/fatal.h $(incdir)/missing.h \ $(top_builddir)/config.h $(top_srcdir)/compat/getopt.h $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(DEFS) $(srcdir)/getopt_long.c -getprogname.lo: $(srcdir)/getprogname.c $(incdir)/missing.h \ - $(top_builddir)/config.h - $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(DEFS) $(srcdir)/getprogname.c glob.lo: $(srcdir)/glob.c $(incdir)/missing.h $(top_builddir)/config.h \ $(top_srcdir)/compat/charclass.h $(top_srcdir)/compat/glob.h $(LIBTOOL) --mode=compile $(CC) -c -o $@ $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(DEFS) $(srcdir)/glob.c diff --git a/compat/getprogname.c b/compat/getprogname.c deleted file mode 100644 index 6fa876d55..000000000 --- a/compat/getprogname.c +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2010, 2013 Todd C. Miller - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, 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. - */ - -#include - -#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME) - -#include - -#include -#include - -#include "missing.h" - -static const char *progname = "sudo"; - -void -setprogname(const char *name) -{ - const char *base; - - if ((base = strrchr(name, '/')) != NULL) { - base++; - } else { - base = name; - } - if (strcmp(progname, base) != 0) - progname = base; -} - -const char * -getprogname(void) -{ - return progname; -} -#endif /* !HAVE_GETPROGNAME !HAVE___PROGNAME */ diff --git a/configure b/configure index e67dfa91a..13c656a4a 100755 --- a/configure +++ b/configure @@ -18672,13 +18672,6 @@ fi if test "$sudo_cv___progname" = "yes"; then $as_echo "#define HAVE___PROGNAME 1" >>confdefs.h - else - case " $LIBOBJS " in - *" getprogname.$ac_objext "* ) ;; - *) LIBOBJS="$LIBOBJS getprogname.$ac_objext" - ;; -esac - fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $sudo_cv___progname" >&5 $as_echo "$sudo_cv___progname" >&6; } diff --git a/configure.ac b/configure.ac index 2721b98b0..f972209e1 100644 --- a/configure.ac +++ b/configure.ac @@ -2559,8 +2559,6 @@ AC_CHECK_FUNCS(getprogname, , [ AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[extern char *__progname; (void)puts(__progname);]])], [sudo_cv___progname=yes], [sudo_cv___progname=no])]) if test "$sudo_cv___progname" = "yes"; then AC_DEFINE(HAVE___PROGNAME) - else - AC_LIBOBJ(getprogname) fi AC_MSG_RESULT($sudo_cv___progname) ]) diff --git a/include/missing.h b/include/missing.h index b2cc05e1a..0c89f5e5a 100644 --- a/include/missing.h +++ b/include/missing.h @@ -282,7 +282,6 @@ extern const char *__progname; # define getprogname() (__progname) # else const char *getprogname(void); -void setprogname(const char *); # endif /* HAVE___PROGNAME */ #endif /* !HAVE_GETPROGNAME */ @@ -447,5 +446,6 @@ char *strsignal(int); #ifndef HAVE_SIG2STR int sig2str(int, char *); #endif +void initprogname(const char *); #endif /* _SUDO_MISSING_H */ diff --git a/mkdep.pl b/mkdep.pl index 91a2d9bb9..6b108f783 100755 --- a/mkdep.pl +++ b/mkdep.pl @@ -70,7 +70,7 @@ sub mkdep { $makefile =~ s:\@SUDOERS_OBJS\@:bsm_audit.lo linux_audit.lo ldap.lo sssd.lo:; # XXX - fill in AUTH_OBJS from contents of the auth dir instead $makefile =~ s:\@AUTH_OBJS\@:afs.lo aix_auth.lo bsdauth.lo dce.lo fwtk.lo getspwuid.lo kerb5.lo pam.lo passwd.lo rfc1938.lo secureware.lo securid5.lo sia.lo:; - $makefile =~ s:\@LTLIBOBJS\@:closefrom.lo fnmatch.lo getaddrinfo.lo getcwd.lo getgrouplist.lo getline.lo getprogname.lo getopt_long.lo glob.lo isblank.lo memrchr.lo memset_s.lo mksiglist.lo mksigname.lo mktemp.lo pw_dup.lo sig2str.lo siglist.lo signame.lo snprintf.lo strlcat.lo strlcpy.lo strsignal.lo utimes.lo globtest.o fnm_test.o:; + $makefile =~ s:\@LTLIBOBJS\@:closefrom.lo fnmatch.lo getaddrinfo.lo getcwd.lo getgrouplist.lo getline.lo getopt_long.lo glob.lo isblank.lo memrchr.lo memset_s.lo mksiglist.lo mksigname.lo mktemp.lo pw_dup.lo sig2str.lo siglist.lo signame.lo snprintf.lo strlcat.lo strlcpy.lo strsignal.lo utimes.lo globtest.o fnm_test.o:; # Parse OBJS lines my %objs; diff --git a/plugins/sample/sample_plugin.c b/plugins/sample/sample_plugin.c index faa4ea6ba..f1d6392e7 100644 --- a/plugins/sample/sample_plugin.c +++ b/plugins/sample/sample_plugin.c @@ -138,11 +138,9 @@ policy_open(unsigned int version, sudo_conv_t conversation, if (strncmp(*ui, "runas_group=", sizeof("runas_group=") - 1) == 0) { runas_group = *ui + sizeof("runas_group=") - 1; } -#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME) if (strncmp(*ui, "progname=", sizeof("progname=") - 1) == 0) { - setprogname(*ui + sizeof("progname=") - 1); + initprogname(*ui + sizeof("progname=") - 1); } -#endif /* Check to see if sudo was called as sudoedit or with -e flag. */ if (strncmp(*ui, "sudoedit=", sizeof("sudoedit=") - 1) == 0) { if (strcasecmp(*ui + sizeof("sudoedit=") - 1, "true") == 0) diff --git a/plugins/sudoers/policy.c b/plugins/sudoers/policy.c index d724307fb..6c9286ede 100644 --- a/plugins/sudoers/policy.c +++ b/plugins/sudoers/policy.c @@ -253,12 +253,10 @@ sudoers_policy_deserialize_info(void *v, char **runas_user, char **runas_group) continue; } #endif /* HAVE_BSD_AUTH_H */ -#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME) if (MATCHES(*cur, "progname=")) { - setprogname(*cur + sizeof("progname=") - 1); + initprogname(*cur + sizeof("progname=") - 1); continue; } -#endif if (MATCHES(*cur, "network_addrs=")) { interfaces_string = *cur + sizeof("network_addrs=") - 1; set_interfaces(interfaces_string); diff --git a/plugins/sudoers/regress/check_symbols/check_symbols.c b/plugins/sudoers/regress/check_symbols/check_symbols.c index 034b04813..2a18200ab 100644 --- a/plugins/sudoers/regress/check_symbols/check_symbols.c +++ b/plugins/sudoers/regress/check_symbols/check_symbols.c @@ -51,7 +51,7 @@ __dso_public int main(int argc, char *argv[]); static void usage(void) { - fprintf(stderr, "usage: load_symbols plugin.so symbols_file\n"); + fprintf(stderr, "usage: %s plugin.so symbols_file\n", getprogname()); exit(1); } @@ -65,9 +65,7 @@ main(int argc, char *argv[]) FILE *fp; int ntests = 0, errors = 0; -#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME) - setprogname(argc > 0 ? argv[0] : "check_symbols"); -#endif + initprogname(argc > 0 ? argv[0] : "check_symbols"); if (argc != 3) usage(); diff --git a/plugins/sudoers/regress/iolog_path/check_iolog_path.c b/plugins/sudoers/regress/iolog_path/check_iolog_path.c index f3c07622e..12fb81ee7 100644 --- a/plugins/sudoers/regress/iolog_path/check_iolog_path.c +++ b/plugins/sudoers/regress/iolog_path/check_iolog_path.c @@ -55,7 +55,7 @@ __dso_public int main(int argc, char *argv[]); static void usage(void) { - fprintf(stderr, "usage: check_iolog_path datafile\n"); + fprintf(stderr, "usage: %s datafile\n", getprogname()); exit(1); } @@ -106,9 +106,7 @@ main(int argc, char *argv[]) int errors = 0; int tests = 0; -#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME) - setprogname(argc > 0 ? argv[0] : "check_iolog_path"); -#endif + initprogname(argc > 0 ? argv[0] : "check_iolog_path"); if (argc != 2) usage(); diff --git a/plugins/sudoers/regress/logging/check_wrap.c b/plugins/sudoers/regress/logging/check_wrap.c index bc25868eb..3d5a8195e 100644 --- a/plugins/sudoers/regress/logging/check_wrap.c +++ b/plugins/sudoers/regress/logging/check_wrap.c @@ -49,7 +49,7 @@ __dso_public int main(int argc, char *argv[]); static void usage(void) { - fprintf(stderr, "usage: check_wrap inputfile\n"); + fprintf(stderr, "usage: %s inputfile\n", getprogname()); exit(1); } @@ -61,9 +61,7 @@ main(int argc, char *argv[]) char *cp, *dash, *line, lines[2][2048]; int which = 0; -#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME) - setprogname(argc > 0 ? argv[0] : "check_wrap"); -#endif + initprogname(argc > 0 ? argv[0] : "check_wrap"); if (argc != 2) usage(); diff --git a/plugins/sudoers/regress/parser/check_addr.c b/plugins/sudoers/regress/parser/check_addr.c index 46a5920d7..86576a2da 100644 --- a/plugins/sudoers/regress/parser/check_addr.c +++ b/plugins/sudoers/regress/parser/check_addr.c @@ -79,7 +79,7 @@ check_addr(char *input) static void usage(void) { - fprintf(stderr, "usage: check_addr datafile\n"); + fprintf(stderr, "usage: %s datafile\n", getprogname()); exit(1); } @@ -91,9 +91,7 @@ main(int argc, char *argv[]) size_t len; FILE *fp; -#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME) - setprogname(argc > 0 ? argv[0] : "check_addr"); -#endif + initprogname(argc > 0 ? argv[0] : "check_addr"); if (argc != 2) usage(); diff --git a/plugins/sudoers/sudoreplay.c b/plugins/sudoers/sudoreplay.c index 31e94f251..27eaf55c3 100644 --- a/plugins/sudoers/sudoreplay.c +++ b/plugins/sudoers/sudoreplay.c @@ -243,10 +243,7 @@ main(int argc, char *argv[]) } #endif -#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME) - setprogname(argc > 0 ? argv[0] : "sudoreplay"); -#endif - + initprogname(argc > 0 ? argv[0] : "sudoreplay"); sudoers_setlocale(SUDOERS_LOCALE_USER, NULL); decimal = localeconv()->decimal_point; bindtextdomain("sudoers", LOCALEDIR); /* XXX - should have sudoreplay domain */ diff --git a/plugins/sudoers/testsudoers.c b/plugins/sudoers/testsudoers.c index 67ce2490b..c83659b28 100644 --- a/plugins/sudoers/testsudoers.c +++ b/plugins/sudoers/testsudoers.c @@ -138,9 +138,7 @@ main(int argc, char *argv[]) sudoersdebug = 1; #endif -#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME) - setprogname(argc > 0 ? argv[0] : "testsudoers"); -#endif + initprogname(argc > 0 ? argv[0] : "testsudoers"); sudoers_setlocale(SUDOERS_LOCALE_USER, NULL); bindtextdomain("sudoers", LOCALEDIR); /* XXX - should have own domain */ diff --git a/plugins/sudoers/visudo.c b/plugins/sudoers/visudo.c index f10b0eba3..6cb1c08ae 100644 --- a/plugins/sudoers/visudo.c +++ b/plugins/sudoers/visudo.c @@ -166,10 +166,7 @@ main(int argc, char *argv[]) } #endif -#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME) - setprogname(argc > 0 ? argv[0] : "visudo"); -#endif - + initprogname(argc > 0 ? argv[0] : "visudo"); sudoers_setlocale(SUDOERS_LOCALE_USER, NULL); bindtextdomain("sudoers", LOCALEDIR); /* XXX - should have visudo domain */ textdomain("sudoers"); diff --git a/src/parse_args.c b/src/parse_args.c index 54bc5376d..61d4647d9 100644 --- a/src/parse_args.c +++ b/src/parse_args.c @@ -185,7 +185,7 @@ parse_args(int argc, char **argv, int *nargc, char ***nargv, char ***settingsp, env_add = emalloc2(env_size, sizeof(char *)); - /* Pass progname to plugin so it can call setprogname() */ + /* Pass progname to plugin so it can call initprogname() */ sudo_settings[ARG_PROGNAME].value = getprogname(); /* First, check to see if we were invoked as "sudoedit". */ diff --git a/src/regress/ttyname/check_ttyname.c b/src/regress/ttyname/check_ttyname.c index be381dd4d..f807f4a0b 100644 --- a/src/regress/ttyname/check_ttyname.c +++ b/src/regress/ttyname/check_ttyname.c @@ -52,9 +52,7 @@ main(int argc, char *argv[]) char *tty_libc, *tty_sudo; int rval = 0; -#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME) - setprogname(argc > 0 ? argv[0] : "check_ttyname"); -#endif + initprogname(argc > 0 ? argv[0] : "check_ttyname"); /* Lookup tty name via libc. */ if ((tty_libc = ttyname(STDIN_FILENO)) == NULL && diff --git a/src/sudo.c b/src/sudo.c index ffa1baff8..075b414a3 100644 --- a/src/sudo.c +++ b/src/sudo.c @@ -299,10 +299,7 @@ main(int argc, char *argv[], char *envp[]) int os_init_common(int argc, char *argv[], char *envp[]) { -#if !defined(HAVE_GETPROGNAME) && !defined(HAVE___PROGNAME) - if (argc > 0) - setprogname(argv[0]); -#endif + initprogname(argc > 0 ? argv[0] : "sudo"); #ifdef STATIC_SUDOERS_PLUGIN preload_static_symbols(); #endif