4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 * Copyright 2012 Milan Jurik. All rights reserved.
28 * This code has a lot in common with the original sys-suspend
29 * code. Windowing facilities have been removed, and it has been
30 * updated to use more recent API's.
45 #include <sys/types.h>
46 #include <sys/param.h>
47 #include <sys/utsname.h>
48 #include <sys/uadmin.h>
49 #include <auth_attr.h>
50 #include <auth_list.h>
52 #include <security/pam_appl.h>
57 #include <bsm/adt_event.h>
67 #define NMAX (sizeof (utmp.ut_name))
70 * Authorizations used by Power Management
72 #define AUTHNAME_SHUTDOWN "solaris.system.shutdown"
73 #define AUTHNAME_SUSPEND_RAM "solaris.system.power.suspend.ram"
74 #define AUTHNAME_SUSPEND_DISK "solaris.system.power.suspend.disk"
76 /* Platform specific definitions */
78 #define AD_CHECK_SUSPEND AD_CHECK_SUSPEND_TO_RAM
79 #define AD_SUSPEND AD_SUSPEND_TO_RAM
80 #define ADT_FCN ADT_UADMIN_FCN_AD_SUSPEND_TO_RAM
81 #define AUTHNAME_SUSPEND AUTHNAME_SUSPEND_RAM
83 #define AD_CHECK_SUSPEND AD_CHECK_SUSPEND_TO_DISK
84 #define AD_SUSPEND AD_SUSPEND_TO_DISK
85 #define ADT_FCN ADT_UADMIN_FCN_AD_SUSPEND_TO_DISK
86 #define AUTHNAME_SUSPEND AUTHNAME_SUSPEND_DISK
90 static int no_tty
= 0;
92 * Flag definitions - could go in a header file, but there are just a few
96 #define NO_XLOCK 0x004
97 #define SHUTDOWN 0x008
98 #define LOWPOWER 0x010
101 static sigjmp_buf jmp_stack
;
102 static char user
[NMAX
+ 1];
108 * Forward Declarations.
110 static void pm_poweroff(void);
111 static int bringto_lowpower(void);
112 static int is_mou3(void);
113 static void suspend_error(int);
114 static int pm_check_suspend(void);
115 static void pm_suspend(void);
116 static void pm_do_auth(adt_session_data_t
*);
119 * External Declarations.
121 extern int pam_tty_conv(int, struct pam_message
**,
122 struct pam_response
**, void *);
126 * Audit related code. I would also think that some of this could be
127 * in external code, as they could be useful of other apps.
130 * Write audit event. Could be useful in the PM library, so it is
131 * included here. For the most part it is only used by the PAM code.
134 pm_audit_event(adt_session_data_t
*ah
, au_event_t event_id
, int status
)
136 adt_event_data_t
*event
;
139 if ((event
= adt_alloc_event(ah
, event_id
)) == NULL
) {
143 (void) adt_put_event(event
,
144 status
== PAM_SUCCESS
? ADT_SUCCESS
: ADT_FAILURE
,
145 status
== PAM_SUCCESS
? ADT_SUCCESS
: ADT_FAIL_PAM
+ status
);
147 adt_free_event(event
);
150 #define RETRY_COUNT 15
152 change_audit_file(void)
156 if (!adt_audit_state(AUC_AUDITING
)) {
157 /* auditd not running, just return */
161 if ((pid
= fork()) == 0) {
162 (void) execl("/usr/sbin/audit", "audit", "-n", NULL
);
163 (void) fprintf(stderr
, gettext("error changing audit files: "
164 "%s\n"), strerror(errno
));
166 } else if (pid
== -1) {
167 (void) fprintf(stderr
, gettext("error changing audit files: "
168 "%s\n"), strerror(errno
));
172 int retries
= RETRY_COUNT
;
175 * Wait for audit(1M) -n process to complete
179 if ((rc
= waitpid(pid
, NULL
, WNOHANG
)) == pid
) {
181 } else if (rc
== -1) {
188 } while (retries
!= 0);
199 while (retries
-- && auditon(A_GETSTAT
, (caddr_t
)&au_stat
, 0) == 0) {
200 if (au_stat
.as_enqueue
== au_stat
.as_written
) {
207 /* End of Audit-related code */
211 alarm_handler(int sig
)
213 siglongjmp(jmp_stack
, 1);
217 * These are functions that would be candidates for moving to a library.
221 * pm_poweroff - similar to poweroff(1M)
222 * This should do the same auditing as poweroff(1m) would do when it
223 * becomes a libpower function. Till then we use poweroff(1m).
228 if (chkauthattr(AUTHNAME_SHUTDOWN
, user
) != 1) {
229 (void) printf(gettext("User %s does not have correct "
230 "authorizations to shutdown this machine.\n"), user
);
233 openlog("suspend", 0, LOG_DAEMON
);
234 syslog(LOG_NOTICE
, "System is being shut down.");
238 * Call poweroff(1m) to shut down the system.
240 (void) execl("/usr/sbin/poweroff", "poweroff", NULL
);
245 * pm_check_suspend() - Check to see if suspend is supported/enabled
247 * Ultimately, we would prefer to get the "default" suspend type from
248 * a PM property or some other API, but for now, we know that STR is
249 * only available on x86 and STD is only available on Sparc. It does
250 * make this function quite easy, though.
253 pm_check_suspend(void) {
255 * Use the uadmin(2) "CHECK" command to see if suspend is supported
257 return (uadmin(A_FREEZE
, AD_CHECK_SUSPEND
, 0));
261 * This entry point _should_ be the common entry to suspend. It is in
262 * it's entirety here, but would be best moved to libpower when that
268 int cprarg
= AD_SUSPEND
;
269 enum adt_uadmin_fcn fcn_id
= ADT_FCN
;
270 au_event_t event_id
= ADT_uadmin_freeze
;
271 adt_event_data_t
*event
= NULL
; /* event to be generated */
272 adt_session_data_t
*ah
= NULL
; /* audit session handle */
275 * Does the user have permission to use this command?
277 if (chkauthattr(AUTHNAME_SUSPEND
, user
) != 1) {
278 (void) printf(gettext("User %s does not have correct "
279 "authorizations to suspend this machine.\n"), user
);
283 if (flags
& LOWPOWER
) {
284 if (bringto_lowpower() == -1) {
285 (void) printf(gettext("LowPower Failed\n"));
288 } else if (flags
& TEST
) {
290 * Test mode, do checks as if a real suspend, but
291 * don't actually do the suspend.
293 /* Check if suspend is supported */
294 if (pm_check_suspend() == -1) {
295 suspend_error(errno
);
298 (void) printf(gettext("TEST: Suspend would have been"
302 /* Check if suspend is supported */
303 if (pm_check_suspend() == -1) {
304 suspend_error(errno
);
308 * We are about to suspend this machine, try and
309 * lock the screen. We don't really care if this
310 * succeeds or not, but that we actually tried. We
311 * also know that we have sufficient privileges to
312 * be here, so we lock the screen now, even if
313 * suspend actually fails.
314 * Note that garbage is sometimes displayed, and
315 * we don't really care about it, so we toss all
317 * it would also be good if there were another option
318 * instead of launcing a file, as the disk might be
319 * spun down if we are suspending due to idle.
321 if (!(flags
& NO_XLOCK
)) {
322 (void) system("/usr/bin/xdg-screensaver lock "
326 /* Time to do the actual deed! */
328 * Before we actually suspend, we need to audit and
329 * "suspend" the audit files.
331 /* set up audit session and event */
332 if (adt_start_session(&ah
, NULL
, ADT_USE_PROC_DATA
) == 0) {
333 if ((event
= adt_alloc_event(ah
, event_id
)) != NULL
) {
334 event
->adt_uadmin_freeze
.fcn
= fcn_id
;
335 event
->adt_uadmin_freeze
.mdep
= NULL
;
336 if (adt_put_event(event
, ADT_SUCCESS
, 0) != 0) {
337 (void) fprintf(stderr
, gettext(
338 "%s: can't put audit event\n"),
344 (void) change_audit_file();
346 (void) fprintf(stderr
, gettext(
347 "%s: can't start audit session\n"), argvl
[0]);
350 if (uadmin(A_FREEZE
, cprarg
, 0) != 0) {
351 (void) printf(gettext("Suspend Failed\n"));
354 * Note, that if we actually poweroff,
355 * that the poweroff function will handle
356 * that audit trail, and the resume
357 * trail is effectively done.
361 /* suspend_error() will exit. */
362 suspend_error(errno
);
364 * Audit the suspend failure and
365 * reuse the event, but don't create one
366 * if we don't already have one.
369 (void) adt_put_event(event
,
376 * Write the thaw event.
379 if ((event
== NULL
) &&
380 ((event
= adt_alloc_event(ah
, ADT_uadmin_thaw
))
382 (void) fprintf(stderr
, gettext(
383 "%s: can't allocate thaw audit event\n"),
386 event
->adt_uadmin_thaw
.fcn
= fcn_id
;
387 if (adt_put_event(event
, ADT_SUCCESS
, 0) != 0) {
388 (void) fprintf(stderr
, gettext(
389 "%s: can't put thaw audit event\n"),
392 (void) adt_free_event(event
);
396 if ((no_tty
? 0 : 1) && !(flags
& NO_XLOCK
)) {
400 (void) adt_end_session(ah
);
402 /* End of "library" functions */
405 * Print an appropriate error message and exit.
409 suspend_error(int error
) {
413 (void) printf(gettext("suspend: "
414 "Suspend already in progress.\n\n"));
420 (void) printf(gettext("suspend: "
421 "Not enough resources to suspend.\n\n"));
425 (void) printf(gettext("suspend: "
426 "Suspend is not supported.\n\n"));
430 (void) printf(gettext("suspend: "
431 "Not sufficient privileges.\n\n"));
435 (void) printf(gettext("suspend: "
436 "unknown error.\n\n"));
443 * refresh_dt() - Refresh screen when 'dtgreet' is running.
444 * This is here for compatibility reasons, and could be removed once
445 * dtgreet is no longer part of the system.
451 struct stat stat_buf
;
454 * If dtgreet exists, HUP it, otherwise just let screenlock
457 if ((stat("/usr/dt/bin/dtgreet", &stat_buf
) == 0) &&
458 (stat_buf
.st_mode
& S_IXUSR
)) {
464 (void) execl("/usr/bin/pkill", "pkill",
465 "-HUP", "-u", "0", "-x", "dtgreet", NULL
);
468 (void) wait(&status
);
475 #define DT_TMP "/var/dt/tmp"
478 * On enter, the "xauthority" string has the value "XAUTHORITY=". On
479 * return, if a Xauthority file is found, concatenate it to this string,
480 * otherwise, return "xauthority" as it is.
483 get_xauthority(char *xauthority
)
488 char filepath
[MAXPATHLEN
];
489 struct stat stat_buf
;
492 char xauth
[MAXPATHLEN
] = "";
498 * Determine home directory of the user.
500 if ((home_dir
= getenv("HOME")) == NULL
) {
501 if ((pwd
= getpwuid(uid
)) == NULL
) {
502 (void) printf(gettext("Error: unable to get passwd "
503 "entry for user.\n"));
506 home_dir
= pwd
->pw_dir
;
508 if ((strlen(home_dir
) + sizeof ("/.Xauthority")) >= MAXPATHLEN
) {
509 (void) printf(gettext("Error: path to home directory is too "
515 * If there is a .Xauthority file in home directory, reference it.
518 (void) sprintf(filepath
, "%s/.Xauthority", home_dir
);
519 if (stat(filepath
, &stat_buf
) == 0)
520 return (strcat(xauthority
, filepath
));
523 * If Xsession can not access user's home directory, it creates the
524 * Xauthority file in "/var/dt/tmp" directory. Since the exact
525 * name of the Xauthority is not known, search the directory and
526 * find the last changed file that starts with ".Xauth" and owned
527 * by the user. Hopefully, that is the valid Xauthority file for
528 * the current X session.
530 if ((dirp
= opendir(DT_TMP
)) == NULL
)
533 while ((dp
= readdir(dirp
)) != NULL
) {
534 if (strstr(dp
->d_name
, ".Xauth") != NULL
) {
536 (void) sprintf(filepath
, "%s/%s", DT_TMP
, dp
->d_name
);
537 if (stat(filepath
, &stat_buf
) == -1)
539 if (stat_buf
.st_uid
!= uid
)
541 if (stat_buf
.st_ctime
> latest
) {
542 (void) strcpy(xauth
, filepath
);
543 latest
= stat_buf
.st_ctime
;
547 (void) closedir(dirp
);
549 return (strcat(xauthority
, xauth
));
553 * suspend can be called in following ways:
554 * 1. from daemon (powerd) for auto-shutdown.
555 * a. there might be a OW/CDE environment
556 * b. there might not be any windowing environment
557 * 2. by a user entered command.
558 * a. the command can be entered from a cmdtool type OW/CDE tool
559 * b. the command can be entered by a user logged in on a dumb
561 * i) there might be a OW/CDE running on console
562 * and we have permission to talk to it.
563 * ii) there is no OW/CDE running on console or we
564 * don't have permission to talk to it or console
565 * itself is the dumb terminal we have logged into.
567 * In main(), we decide on the correct case and call appropriate functions.
571 main(int argc
, char **argv
)
574 char display_name
[MAXNAMELEN
+ 9] = "DISPLAY=";
575 char xauthority
[MAXPATHLEN
+ 12] = "XAUTHORITY=";
578 (void) signal(SIGHUP
, SIG_IGN
);
579 (void) signal(SIGINT
, SIG_IGN
);
580 (void) signal(SIGQUIT
, SIG_IGN
);
581 (void) signal(SIGTSTP
, SIG_IGN
);
582 (void) signal(SIGTTIN
, SIG_IGN
);
583 (void) signal(SIGTTOU
, SIG_IGN
);
586 * If suspend is invoked from a daemon (case 1 above), it
587 * will not have a working stdin, stdout and stderr. We need
588 * these to print proper error messages and possibly get user
589 * input. We attach them to console and hope that attachment
592 if (ttyname(0) == NULL
) {
594 (void) dup2(open("/dev/console", O_RDONLY
), 0);
595 (void) dup2(open("/dev/console", O_WRONLY
), 1);
596 (void) dup2(open("/dev/console", O_WRONLY
), 2);
599 while ((c
= getopt(argc
, argv
, "fnxhtd:")) != EOF
) {
603 * Force machine to poweroff if
609 /* No warning popups - Obsolete */
613 /* Don't try to screenlock */
617 /* Do a shutdown instead of suspend */
622 /* Set the DISPLAY value in the environment */
623 if (strlen(optarg
) >= MAXNAMELEN
) {
624 (void) printf(gettext("Error: "
625 "display name is too long.\n"));
628 (void) strcat(display_name
, optarg
);
629 if (putenv(display_name
) != 0) {
630 (void) printf(gettext("Error: "
631 "unable to set DISPLAY "
632 "environment variable.\n"));
637 /* Test, don't actually do any operation */
641 (void) printf(gettext("USAGE: suspend "
642 "[-fnxh] [-d <display>]\n"));
648 * The action of pressing power key and power button on a MOU-3 machine
649 * causes suspend being invoked with SYSSUSPENDDODEFAULT
650 * enviromental variable set - indicating the default action is machine
651 * dependent: for MOU-3 type machine, "LowPower" mode is the default,
652 * for all the rest, "Suspend" is the default. Existing suspend
653 * flags works the same.
655 if (getenv("SYSSUSPENDDODEFAULT"))
659 if ((flags
& FORCE
) && (flags
& LOWPOWER
))
663 * Flag "-h" overrides flag "-f".
665 if ((flags
& SHUTDOWN
) && (flags
& FORCE
))
666 flags
&= ~(FORCE
| LOWPOWER
);
672 * Check initally if the user has the authorizations to
673 * do either a suspend or shutdown. pm_suspend() will also
674 * make this test, so we could defer till then, but if we
675 * do it now, we at least prevent a lot of unneeded setup.
677 pw
= getpwuid(getuid());
678 (void) strncpy(user
, pw
->pw_name
, NMAX
);
680 if ((flags
& (FORCE
|SHUTDOWN
)) &&
681 (chkauthattr(AUTHNAME_SHUTDOWN
, pw
->pw_name
) != 1)) {
682 (void) printf(gettext("User does not have correct "
683 "authorizations to shutdown the machine.\n"));
686 if (!(flags
& SHUTDOWN
) &&
687 (chkauthattr(AUTHNAME_SUSPEND
, pw
->pw_name
) != 1)) {
688 (void) printf(gettext("User does not have correct "
689 "authorizations to suspend.\n"));
694 * If we are only shutting down, there isn't much to do, just
695 * call pm_poweroff(), and let it do all the work.
697 if (flags
& SHUTDOWN
) {
699 * pm_poweroff either powers off or exits,
700 * so there is no return.
703 (void) printf("TEST: This machine would have "
713 * If XAUTHORITY environment variable is not set, try to set
716 if (getenv("XAUTHORITY") == NULL
)
717 (void) putenv(get_xauthority(xauthority
));
720 * In case of "suspend" being called from daemon "powerd",
721 * signal SIGALRM is blocked so use "sigset()" instead of "signal()".
723 (void) sigset(SIGALRM
, alarm_handler
);
725 /* Call the "suspend" function to do the last of the work */
728 if (refresh_dt() == -1) {
729 (void) printf("%s: Failed to refresh screen.\n", argv
[0]);
738 * Note that some of these functions are more relevant to Sparc platforms,
739 * but they do function properly on other platforms, they just don't do
744 * This tells the PM framework to put the devices it controls in an idle
745 * state. The framework only complains if a device that *must* be idle
746 * doesn't succeed in getting there.
753 if ((fd
= open("/dev/pm", O_RDWR
)) < 0) {
754 (void) printf(gettext("Can't open /dev/pm\n"));
758 if (ioctl(fd
, PM_IDLE_DOWN
, NULL
) < 0) {
759 (void) printf(gettext("Failed to bring system "
760 "to low power mode.\n"));
771 * Though this test is predominantly used on Sparc, it will run on other
772 * platforms, and might be usefull one day on those.
781 if ((fd
= open(CPR_CONFIG
, O_RDONLY
)) < 0) {
782 (void) printf(gettext("Can't open /etc/.cpr_config file."));
786 if (read(fd
, (void *) &cf
, sizeof (cf
)) != sizeof (cf
)) {
787 (void) printf(gettext("Can't read /etc/.cpr_config file."));
789 found
= cf
.is_autopm_default
;
797 * Reauthenticate the user on return from suspend.
798 * This is here and not in the PAM-specific file, as there are
799 * items specific to sys-suspend, and not generic to PAM. This may
800 * become part of a future PM library. The audit handle is passed,
801 * as the pm_suspend code actually starts an audit session, so it
802 * makes sense to just continue to use it. If it were separated
803 * from the pm_suspend code, it will need to open a new session.
805 #define DEF_ATTEMPTS 3
807 pm_do_auth(adt_session_data_t
*ah
)
809 pam_handle_t
*pm_pamh
;
813 struct pam_conv pam_conv
= {pam_tty_conv
, NULL
};
818 if ((err
= pam_start("sys-suspend", user
, &pam_conv
,
819 &pm_pamh
)) != PAM_SUCCESS
)
822 pam_flag
= PAM_DISALLOW_NULL_AUTHTOK
;
825 err
= pam_authenticate(pm_pamh
, pam_flag
);
827 if (err
== PAM_SUCCESS
) {
828 err
= pam_acct_mgmt(pm_pamh
, pam_flag
);
830 if (err
== PAM_NEW_AUTHTOK_REQD
) {
834 err
= pam_chauthtok(pm_pamh
,
835 PAM_CHANGE_EXPIRED_AUTHTOK
);
838 } while ((err
== PAM_AUTHTOK_ERR
||
839 err
== PAM_TRY_AGAIN
) &&
840 chpasswd_tries
< DEF_ATTEMPTS
);
841 pm_audit_event(ah
, ADT_passwd
, err
);
843 err
= pam_setcred(pm_pamh
, PAM_REFRESH_CRED
);
845 if (err
!= PAM_SUCCESS
) {
846 (void) fprintf(stdout
, "%s\n",
847 pam_strerror(pm_pamh
, err
));
848 pm_audit_event(ah
, ADT_screenunlock
, err
);
850 } while (err
!= PAM_SUCCESS
);
851 pm_audit_event(ah
, ADT_passwd
, 0);
853 (void) pam_end(pm_pamh
, err
);