/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2012 Milan Jurik. All rights reserved. */ /* * This code has a lot in common with the original sys-suspend * code. Windowing facilities have been removed, and it has been * updated to use more recent API's. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* For audit */ #include #include #include #include #include #include #include /* STATICUSED */ struct utmpx utmp; #define NMAX (sizeof (utmp.ut_name)) /* * Authorizations used by Power Management */ #define AUTHNAME_SHUTDOWN "solaris.system.shutdown" #define AUTHNAME_SUSPEND_RAM "solaris.system.power.suspend.ram" #define AUTHNAME_SUSPEND_DISK "solaris.system.power.suspend.disk" /* Platform specific definitions */ #ifdef i386 #define AD_CHECK_SUSPEND AD_CHECK_SUSPEND_TO_RAM #define AD_SUSPEND AD_SUSPEND_TO_RAM #define ADT_FCN ADT_UADMIN_FCN_AD_SUSPEND_TO_RAM #define AUTHNAME_SUSPEND AUTHNAME_SUSPEND_RAM #else #define AD_CHECK_SUSPEND AD_CHECK_SUSPEND_TO_DISK #define AD_SUSPEND AD_SUSPEND_TO_DISK #define ADT_FCN ADT_UADMIN_FCN_AD_SUSPEND_TO_DISK #define AUTHNAME_SUSPEND AUTHNAME_SUSPEND_DISK #endif static int flags = 0; static int no_tty = 0; /* * Flag definitions - could go in a header file, but there are just a few */ #define FORCE 0x001 #define NO_WARN 0x002 #define NO_XLOCK 0x004 #define SHUTDOWN 0x008 #define LOWPOWER 0x010 #define TEST 0x800 static sigjmp_buf jmp_stack; static char user[NMAX + 1]; static char **argvl; /* * Forward Declarations. */ static void pm_poweroff(void); static int bringto_lowpower(void); static int is_mou3(void); static void suspend_error(int); static int pm_check_suspend(void); static void pm_suspend(void); static void pm_do_auth(adt_session_data_t *); /* * External Declarations. */ extern int pam_tty_conv(int, struct pam_message **, struct pam_response **, void *); extern char *optarg; /* * Audit related code. I would also think that some of this could be * in external code, as they could be useful of other apps. */ /* * Write audit event. Could be useful in the PM library, so it is * included here. For the most part it is only used by the PAM code. */ static void pm_audit_event(adt_session_data_t *ah, au_event_t event_id, int status) { adt_event_data_t *event; if ((event = adt_alloc_event(ah, event_id)) == NULL) { return; } (void) adt_put_event(event, status == PAM_SUCCESS ? ADT_SUCCESS : ADT_FAILURE, status == PAM_SUCCESS ? ADT_SUCCESS : ADT_FAIL_PAM + status); adt_free_event(event); } #define RETRY_COUNT 15 static int change_audit_file(void) { pid_t pid; if (!adt_audit_state(AUC_AUDITING)) { /* auditd not running, just return */ return (0); } if ((pid = fork()) == 0) { (void) execl("/usr/sbin/audit", "audit", "-n", NULL); (void) fprintf(stderr, gettext("error changing audit files: " "%s\n"), strerror(errno)); _exit(-1); } else if (pid == -1) { (void) fprintf(stderr, gettext("error changing audit files: " "%s\n"), strerror(errno)); return (-1); } else { pid_t rc; int retries = RETRY_COUNT; /* * Wait for audit(1M) -n process to complete * */ do { if ((rc = waitpid(pid, NULL, WNOHANG)) == pid) { return (0); } else if (rc == -1) { return (-1); } else { (void) sleep(1); retries--; } } while (retries != 0); } return (-1); } static void wait_for_auqueue() { au_stat_t au_stat; int retries = 10; while (retries-- && auditon(A_GETSTAT, (caddr_t)&au_stat, NULL) == 0) { if (au_stat.as_enqueue == au_stat.as_written) { break; } (void) sleep(1); } } /* End of Audit-related code */ /* ARGSUSED0 */ static void alarm_handler(int sig) { siglongjmp(jmp_stack, 1); } /* * These are functions that would be candidates for moving to a library. */ /* * pm_poweroff - similar to poweroff(1M) * This should do the same auditing as poweroff(1m) would do when it * becomes a libpower function. Till then we use poweroff(1m). */ static void pm_poweroff(void) { if (chkauthattr(AUTHNAME_SHUTDOWN, user) != 1) { (void) printf(gettext("User %s does not have correct " "authorizations to shutdown this machine.\n"), user); exit(1); } openlog("suspend", 0, LOG_DAEMON); syslog(LOG_NOTICE, "System is being shut down."); closelog(); /* * Call poweroff(1m) to shut down the system. */ (void) execl("/usr/sbin/poweroff", "poweroff", NULL); } /* * pm_check_suspend() - Check to see if suspend is supported/enabled * on this machine. * Ultimately, we would prefer to get the "default" suspend type from * a PM property or some other API, but for now, we know that STR is * only available on x86 and STD is only available on Sparc. It does * make this function quite easy, though. */ static int pm_check_suspend(void) { /* * Use the uadmin(2) "CHECK" command to see if suspend is supported */ return (uadmin(A_FREEZE, AD_CHECK_SUSPEND, 0)); } /* * This entry point _should_ be the common entry to suspend. It is in * it's entirety here, but would be best moved to libpower when that * is available. */ static void pm_suspend(void) { int cprarg = AD_SUSPEND; enum adt_uadmin_fcn fcn_id = ADT_FCN; au_event_t event_id = ADT_uadmin_freeze; adt_event_data_t *event = NULL; /* event to be generated */ adt_session_data_t *ah = NULL; /* audit session handle */ /* * Does the user have permission to use this command? */ if (chkauthattr(AUTHNAME_SUSPEND, user) != 1) { (void) printf(gettext("User %s does not have correct " "authorizations to suspend this machine.\n"), user); exit(1); } if (flags & LOWPOWER) { if (bringto_lowpower() == -1) { (void) printf(gettext("LowPower Failed\n")); exit(1); } } else if (flags & TEST) { /* * Test mode, do checks as if a real suspend, but * don't actually do the suspend. */ /* Check if suspend is supported */ if (pm_check_suspend() == -1) { suspend_error(errno); } (void) printf(gettext("TEST: Suspend would have been" " performed\n")); } else { /* Check if suspend is supported */ if (pm_check_suspend() == -1) { suspend_error(errno); } /* * We are about to suspend this machine, try and * lock the screen. We don't really care if this * succeeds or not, but that we actually tried. We * also know that we have sufficient privileges to * be here, so we lock the screen now, even if * suspend actually fails. * Note that garbage is sometimes displayed, and * we don't really care about it, so we toss all * text response. * it would also be good if there were another option * instead of launcing a file, as the disk might be * spun down if we are suspending due to idle. */ if (!(flags & NO_XLOCK)) { (void) system("/usr/bin/xdg-screensaver lock " " >/dev/null 2>&1"); } /* Time to do the actual deed! */ /* * Before we actually suspend, we need to audit and * "suspend" the audit files. */ /* set up audit session and event */ if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) == 0) { if ((event = adt_alloc_event(ah, event_id)) != NULL) { event->adt_uadmin_freeze.fcn = fcn_id; event->adt_uadmin_freeze.mdep = NULL; if (adt_put_event(event, ADT_SUCCESS, 0) != 0) { (void) fprintf(stderr, gettext( "%s: can't put audit event\n"), argvl[0]); } else { wait_for_auqueue(); } } (void) change_audit_file(); } else { (void) fprintf(stderr, gettext( "%s: can't start audit session\n"), argvl[0]); } if (uadmin(A_FREEZE, cprarg, 0) != 0) { (void) printf(gettext("Suspend Failed\n")); if (flags & FORCE) { /* * Note, that if we actually poweroff, * that the poweroff function will handle * that audit trail, and the resume * trail is effectively done. */ pm_poweroff(); } else { /* suspend_error() will exit. */ suspend_error(errno); /* * Audit the suspend failure and * reuse the event, but don't create one * if we don't already have one. */ if (event != NULL) { (void) adt_put_event(event, ADT_FAILURE, 0); } } } /* * Write the thaw event. */ if (ah != NULL) { if ((event == NULL) && ((event = adt_alloc_event(ah, ADT_uadmin_thaw)) == NULL)) { (void) fprintf(stderr, gettext( "%s: can't allocate thaw audit event\n"), argvl[0]); } else { event->adt_uadmin_thaw.fcn = fcn_id; if (adt_put_event(event, ADT_SUCCESS, 0) != 0) { (void) fprintf(stderr, gettext( "%s: can't put thaw audit event\n"), argvl[0]); } (void) adt_free_event(event); } } } if ((no_tty ? 0 : 1) && !(flags & NO_XLOCK)) { pm_do_auth(ah); } (void) adt_end_session(ah); } /* End of "library" functions */ /* * Print an appropriate error message and exit. */ static void suspend_error(int error) { switch (error) { case EBUSY: (void) printf(gettext("suspend: " "Suspend already in progress.\n\n")); exit(1); /*NOTREACHED*/ case ENOMEM: /*FALLTHROUGH*/ case ENOSPC: (void) printf(gettext("suspend: " "Not enough resources to suspend.\n\n")); exit(1); /*NOTREACHED*/ case ENOTSUP: (void) printf(gettext("suspend: " "Suspend is not supported.\n\n")); exit(1); /*NOTREACHED*/ case EPERM: (void) printf(gettext("suspend: " "Not sufficient privileges.\n\n")); exit(1); /*NOTREACHED*/ default: (void) printf(gettext("suspend: " "unknown error.\n\n")); exit(1); } } /* * refresh_dt() - Refresh screen when 'dtgreet' is running. * This is here for compatibility reasons, and could be removed once * dtgreet is no longer part of the system. */ static int refresh_dt() { int status; struct stat stat_buf; /* * If dtgreet exists, HUP it, otherwise just let screenlock * do it's thing. */ if ((stat("/usr/dt/bin/dtgreet", &stat_buf) == 0) && (stat_buf.st_mode & S_IXUSR)) { switch (fork()) { case -1: break; case 0: (void) close(1); (void) execl("/usr/bin/pkill", "pkill", "-HUP", "-u", "0", "-x", "dtgreet", NULL); break; default: (void) wait(&status); } } return (0); } #define DT_TMP "/var/dt/tmp" /* * On enter, the "xauthority" string has the value "XAUTHORITY=". On * return, if a Xauthority file is found, concatenate it to this string, * otherwise, return "xauthority" as it is. */ static char * get_xauthority(char *xauthority) { pid_t uid; char *home_dir; struct passwd *pwd; char filepath[MAXPATHLEN]; struct stat stat_buf; DIR *dirp; struct dirent *dp; char xauth[MAXPATHLEN] = ""; time_t latest = 0; uid = getuid(); /* * Determine home directory of the user. */ if ((home_dir = getenv("HOME")) == NULL) { if ((pwd = getpwuid(uid)) == NULL) { (void) printf(gettext("Error: unable to get passwd " "entry for user.\n")); exit(1); } home_dir = pwd->pw_dir; } if ((strlen(home_dir) + sizeof ("/.Xauthority")) >= MAXPATHLEN) { (void) printf(gettext("Error: path to home directory is too " "long.\n")); exit(1); } /* * If there is a .Xauthority file in home directory, reference it. */ /*LINTED*/ (void) sprintf(filepath, "%s/.Xauthority", home_dir); if (stat(filepath, &stat_buf) == 0) return (strcat(xauthority, filepath)); /* * If Xsession can not access user's home directory, it creates the * Xauthority file in "/var/dt/tmp" directory. Since the exact * name of the Xauthority is not known, search the directory and * find the last changed file that starts with ".Xauth" and owned * by the user. Hopefully, that is the valid Xauthority file for * the current X session. */ if ((dirp = opendir(DT_TMP)) == NULL) return (xauthority); while ((dp = readdir(dirp)) != NULL) { if (strstr(dp->d_name, ".Xauth") != NULL) { /*LINTED*/ (void) sprintf(filepath, "%s/%s", DT_TMP, dp->d_name); if (stat(filepath, &stat_buf) == -1) continue; if (stat_buf.st_uid != uid) continue; if (stat_buf.st_ctime > latest) { (void) strcpy(xauth, filepath); latest = stat_buf.st_ctime; } } } (void) closedir(dirp); return (strcat(xauthority, xauth)); } /* * suspend can be called in following ways: * 1. from daemon (powerd) for auto-shutdown. * a. there might be a OW/CDE environment * b. there might not be any windowing environment * 2. by a user entered command. * a. the command can be entered from a cmdtool type OW/CDE tool * b. the command can be entered by a user logged in on a dumb * terminal. * i) there might be a OW/CDE running on console * and we have permission to talk to it. * ii) there is no OW/CDE running on console or we * don't have permission to talk to it or console * itself is the dumb terminal we have logged into. * * In main(), we decide on the correct case and call appropriate functions. */ int main(int argc, char **argv) { int c; char display_name[MAXNAMELEN + 9] = "DISPLAY="; char xauthority[MAXPATHLEN + 12] = "XAUTHORITY="; struct passwd *pw; (void *) signal(SIGHUP, SIG_IGN); (void *) signal(SIGINT, SIG_IGN); (void *) signal(SIGQUIT, SIG_IGN); (void *) signal(SIGTSTP, SIG_IGN); (void *) signal(SIGTTIN, SIG_IGN); (void *) signal(SIGTTOU, SIG_IGN); /* * If suspend is invoked from a daemon (case 1 above), it * will not have a working stdin, stdout and stderr. We need * these to print proper error messages and possibly get user * input. We attach them to console and hope that attachment * works. */ if (ttyname(0) == NULL) { no_tty = 1; (void) dup2(open("/dev/console", O_RDONLY), 0); (void) dup2(open("/dev/console", O_WRONLY), 1); (void) dup2(open("/dev/console", O_WRONLY), 2); } while ((c = getopt(argc, argv, "fnxhtd:")) != EOF) { switch (c) { case 'f': /* * Force machine to poweroff if * suspend fails */ flags |= FORCE; break; case 'n': /* No warning popups - Obsolete */ flags |= NO_WARN; break; case 'x': /* Don't try to screenlock */ flags |= NO_XLOCK; break; case 'h': /* Do a shutdown instead of suspend */ flags |= SHUTDOWN; break; case 'd': /* Needswork */ /* Set the DISPLAY value in the environment */ if (strlen(optarg) >= MAXNAMELEN) { (void) printf(gettext("Error: " "display name is too long.\n")); return (1); } (void) strcat(display_name, optarg); if (putenv(display_name) != 0) { (void) printf(gettext("Error: " "unable to set DISPLAY " "environment variable.\n")); return (1); } break; case 't': /* Test, don't actually do any operation */ flags |= TEST; break; default: (void) printf(gettext("USAGE: suspend " "[-fnxh] [-d ]\n")); return (1); } } /* * The action of pressing power key and power button on a MOU-3 machine * causes suspend being invoked with SYSSUSPENDDODEFAULT * enviromental variable set - indicating the default action is machine * dependent: for MOU-3 type machine, "LowPower" mode is the default, * for all the rest, "Suspend" is the default. Existing suspend * flags works the same. */ if (getenv("SYSSUSPENDDODEFAULT")) if (is_mou3()) flags |= LOWPOWER; if ((flags & FORCE) && (flags & LOWPOWER)) flags &= ~LOWPOWER; /* * Flag "-h" overrides flag "-f". */ if ((flags & SHUTDOWN) && (flags & FORCE)) flags &= ~(FORCE | LOWPOWER); if (flags & FORCE) flags |= NO_WARN; /* * Check initally if the user has the authorizations to * do either a suspend or shutdown. pm_suspend() will also * make this test, so we could defer till then, but if we * do it now, we at least prevent a lot of unneeded setup. */ pw = getpwuid(getuid()); (void) strncpy(user, pw->pw_name, NMAX); if ((flags & (FORCE|SHUTDOWN)) && (chkauthattr(AUTHNAME_SHUTDOWN, pw->pw_name) != 1)) { (void) printf(gettext("User does not have correct " "authorizations to shutdown the machine.\n")); exit(1); } if (!(flags & SHUTDOWN) && (chkauthattr(AUTHNAME_SUSPEND, pw->pw_name) != 1)) { (void) printf(gettext("User does not have correct " "authorizations to suspend.\n")); exit(1); } /* * If we are only shutting down, there isn't much to do, just * call pm_poweroff(), and let it do all the work. */ if (flags & SHUTDOWN) { /* * pm_poweroff either powers off or exits, * so there is no return. */ if (flags & TEST) { (void) printf("TEST: This machine would have " "powered off\n"); exit(1); } else { pm_poweroff(); } /* NOTREACHED */ } /* * If XAUTHORITY environment variable is not set, try to set * one up. */ if (getenv("XAUTHORITY") == NULL) (void) putenv(get_xauthority(xauthority)); /* * In case of "suspend" being called from daemon "powerd", * signal SIGALRM is blocked so use "sigset()" instead of "signal()". */ (void *) sigset(SIGALRM, alarm_handler); /* Call the "suspend" function to do the last of the work */ pm_suspend(); if (refresh_dt() == -1) { (void) printf("%s: Failed to refresh screen.\n", argv[0]); return (1); } return (0); } #include /* * Note that some of these functions are more relevant to Sparc platforms, * but they do function properly on other platforms, they just don't do * as much. */ /* * bringto_lowpower() * This tells the PM framework to put the devices it controls in an idle * state. The framework only complains if a device that *must* be idle * doesn't succeed in getting there. */ static int bringto_lowpower() { int fd; if ((fd = open("/dev/pm", O_RDWR)) < 0) { (void) printf(gettext("Can't open /dev/pm\n")); return (-1); } if (ioctl(fd, PM_IDLE_DOWN, NULL) < 0) { (void) printf(gettext("Failed to bring system " "to low power mode.\n")); (void) close(fd); return (-1); } (void) close(fd); return (0); } #include /* * Though this test is predominantly used on Sparc, it will run on other * platforms, and might be usefull one day on those. */ static int is_mou3() { struct cprconfig cf; int fd; int found = 0; if ((fd = open(CPR_CONFIG, O_RDONLY)) < 0) { (void) printf(gettext("Can't open /etc/.cpr_config file.")); return (found); } if (read(fd, (void *) &cf, sizeof (cf)) != sizeof (cf)) { (void) printf(gettext("Can't read /etc/.cpr_config file.")); } else { found = cf.is_autopm_default; } (void) close(fd); return (found); } /* * Reauthenticate the user on return from suspend. * This is here and not in the PAM-specific file, as there are * items specific to sys-suspend, and not generic to PAM. This may * become part of a future PM library. The audit handle is passed, * as the pm_suspend code actually starts an audit session, so it * makes sense to just continue to use it. If it were separated * from the pm_suspend code, it will need to open a new session. */ #define DEF_ATTEMPTS 3 static void pm_do_auth(adt_session_data_t *ah) { pam_handle_t *pm_pamh; int err; int pam_flag = 0; int chpasswd_tries; struct pam_conv pam_conv = {pam_tty_conv, NULL}; if (user[0] == '\0') return; if ((err = pam_start("sys-suspend", user, &pam_conv, &pm_pamh)) != PAM_SUCCESS) return; pam_flag = PAM_DISALLOW_NULL_AUTHTOK; do { err = pam_authenticate(pm_pamh, pam_flag); if (err == PAM_SUCCESS) { err = pam_acct_mgmt(pm_pamh, pam_flag); if (err == PAM_NEW_AUTHTOK_REQD) { chpasswd_tries = 0; do { err = pam_chauthtok(pm_pamh, PAM_CHANGE_EXPIRED_AUTHTOK); chpasswd_tries++; } while ((err == PAM_AUTHTOK_ERR || err == PAM_TRY_AGAIN) && chpasswd_tries < DEF_ATTEMPTS); pm_audit_event(ah, ADT_passwd, err); } err = pam_setcred(pm_pamh, PAM_REFRESH_CRED); } if (err != PAM_SUCCESS) { (void) fprintf(stdout, "%s\n", pam_strerror(pm_pamh, err)); pm_audit_event(ah, ADT_screenunlock, err); } } while (err != PAM_SUCCESS); pm_audit_event(ah, ADT_passwd, 0); (void) pam_end(pm_pamh, err); }