| 
 | 
The prwarn.c program warns users when their passwords are about to expire. The prwarn command takes three optional arguments. The arguments specify the number of days before a password expires that a warning is given, the number of hours and minutes between warnings, and the user name(s) who receives the warning. The logged-in user must be authorized to change the passwords of all specified users.
The program is usually run from a user's .profile or .login file, in which case no user names are specified and the warning applies to this user. The time of the warning is saved as the modification time of the $HOME/.prwarn_time file.
This program demonstrates how to:
Any program that queries the Protected Password database
must be executed with the effective group ID
(EUID) of auth.
To install this program, make the binary
setgid auth.  For example, use
the following commands:
chgrp auth prwarn
chmod 2111 prwarn
Compile this program with the following command:
cc prwarn.c -lprot -lx -o prwarn
The -lprot option searches the prot library that contains the trusted routines.
This program is structured so that the subroutines are defined in front of the main routine, which begins in line 291.
1 #define SecureWare /* enable security features */2 #include <sys/types.h> /* system pseudo-types */ 3 #include <stdio.h> /* standard I/O definitions */ 4 #include <string.h> /* string library definitions */ 5 #include <stdarg.h> /* variable-len argument lists */ 6 #include <unistd.h> /* access(S) definitions */ 7 #include <sys/security.h> /* various security definitions */ 8 #include <sys/audit.h> /* audit definitions */ 9 #include <prot.h> /* database definitions */ 10 #include <pwd.h> /* /etc/passwd definitions */ 11 #include <time.h> /* localtime(S) definitions */ 12 #include <utime.h> /* utime(S) definitions */ 13 #include <sys/stat.h> /* stat(S) definitions */
14 extern int defopen(char *); 15 extern char *defread(char *);
#define SecureWare statement is required.
This allows the security-related definitions in the
header files to be interpreted correctly.
prwarn.c (Part 2/13)
16 #define TRUE 1 17 #define FALSE 0 18 #define ARBSTRSIZ 300 /* ARBitrary STRing SIZe*/ 19 #define SECONDS_PER_HOUR (60L * 60L) 20 #define SECONDS_PER_DAY (24L * SECONDS_PER_HOUR) 21 time_t now; 22 int is_administrator; /* TRUE if LUID is Accounts Administrator */23 char prwarn_file[] = ".prwarn_time"; 24 char default_file[] = "/etc/default/prwarn";
25 time_t silence = 6 * SECONDS_PER_HOUR; 26 int always = FALSE; 27 int 28 set_remind_time( 29 char *str 30 ) { 31 int hhmm, hour, mins;
32 if (strcmp(str, "always") == 0) { 33 always = TRUE; 34 return (0); 35 } 36 hhmm = atoi(str); 37 if (strlen(str) < 2) { 38 hour = hhmm; 39 mins = 0; 40 } 41 else { 42 hour = hhmm / 100; 43 mins = hhmm % 100; 44 } 45 if (hour < 0 || mins < 0 || 59 < mins) 46 return (-1); 47 silence = (((time_t)hour * 60L) + (time_t)mins) * 60L; 48 always = FALSE; 49 return (0); 50 }
#define statements are shown here for simplicity,
but in an application with several programs
it might make sense to put these definitions
in an application header file
that would use #include statements
in all programs for the application.
prwarn.c (Part 3/13)
51 time_t forced = 7 * SECONDS_PER_DAY; /* -d (time before expires) */ 52 int nolimit = FALSE; /* TRUE if no -d cutoff time */
53 int 54 set_time_before( 55 char *str 56 ) { 57 int days;
58 if (strncmp(str, "inf", 3) == 0) { 59 nolimit = TRUE; 60 return (0); 61 } 62 if ((days = atoi(str)) < 0) 63 return (-1);
64 forced = (time_t)days * SECONDS_PER_DAY; 65 nolimit = FALSE; 66 return (0); 67 }
68 int 69 days_in_year( 70 register int yr 71 ) { 72 if (((yr % 4) == 0 && (yr % 100) != 0) || (yr % 400) == 0) 73 return (366); 74 return (365); 75 }
prwarn.c (Part 4/13)
 76 char *
 77 when(
 78 	time_t then
 79 ) {
 80 	struct tm today, other;
 81 	long days;
 82 	static char buf[ARBSTRSIZ];
 83 	other = *localtime(&then);
 84 	today = *localtime(&now);
 85 	if (other.tm_year == today.tm_year+1) {		/* next year */
 86 		other.tm_yday += days_in_year(today.tm_year);
 87 		other.tm_year = today.tm_year;
 88 	}
 89 	else if (other.tm_year == today.tm_year-1) {		/* last year */
 90 		today.tm_yday += days_in_year(other.tm_year);
 91 		today.tm_year = other.tm_year;
 92 	}
 93 	if (other.tm_year == today.tm_year) {
 94 		if (other.tm_yday == today.tm_yday-1)
 95 			return ("yesterday");
 96 		if (other.tm_yday == today.tm_yday)
 97 			return ("today");
 98 		if (other.tm_yday == today.tm_yday+1)
 99 			return ("tomorrow");
100 	}
101 	if (then > now) {
102 		days = (then - now) / SECONDS_PER_DAY;
103 		(void) sprintf(buf, "in %ld days", days);
104 	}
105 	else {
106 		days = (now - then) / SECONDS_PER_DAY;
107 		(void) sprintf(buf, "%ld days ago", days);
108 	}
109 	return (buf);
110 }
prwarn.c (Part 5/13)
111 time_t
112 last_reported(
113 	struct passwd *pwd
114 ) {
115 	struct stat stbuf;
116 	time_t last;
117 	if (chdir("/"))
118 		perror("Cannot change current working directory to /");
119 	else if (chdir(pwd->pw_dir) == 0 && stat(prwarn_file, &stbuf) == 0)
120 		return (stbuf.st_mtime);
121 	return (0);		/* The Epoch: Jan 1st 1970 00:00 GMT */
122 }
123 void
124 just_reported(
125 	struct passwd *pwd
126 ) {
127 	struct utimbuf utbuf;
128 	if (eaccess(prwarn_file, F_OK) && close(creat(prwarn_file, 0600)) == 0)
129 		(void) chown(prwarn_file, pwd->pw_uid, pwd->pw_gid);
130 	utbuf.actime = now;
131 	utbuf.modtime = now;
132 	(void) utime(prwarn_file, &utbuf);
133 }
prwarn.c (Part 6/13)
134 time_t 135 expires_at( 136 register struct pr_passwd *pr_pw 137 ) { 138 if (pr_pw->uflg.fg_expire) 139 return (pr_pw->ufld.fd_expire); 140 else if (pr_pw->sflg.fg_expire) 141 return (pr_pw->sfld.fd_expire);
142 audit_security_failure( 143 OT_DFLT_CNTL, -1L, 0L, AUTH_DEFAULT, 144 "No system default password expiration time" 145 ); 146 return (0); 147 }
148 time_t 149 minchange_is( 150 register struct pr_passwd *pr_pw 151 ) { 152 if (pr_pw->uflg.fg_min) 153 return (pr_pw->ufld.fd_min); 154 else if (pr_pw->sflg.fg_min) 155 return (pr_pw->sfld.fd_min);
156 audit_security_failure( 157 OT_DFLT_CNTL, -1L, 0L, AUTH_DEFAULT, 158 "No system default minimum password change time" 159 ); 160 return (0); 161 }
prwarn.c (Part 7/13)
162 time_t
163 lives_until(
164 	register struct pr_passwd *pr_pw
165 ) {
166 	if (pr_pw->uflg.fg_lifetime)
167 		return (pr_pw->ufld.fd_lifetime);
168 	else if (pr_pw->sflg.fg_lifetime)
169 		return (pr_pw->sfld.fd_lifetime);
170 	audit_security_failure(
171 		OT_DFLT_CNTL, -1L, 0L, AUTH_DEFAULT,
172 			"No system default password lifetime"
173 		);
174 	return (0);
175 }
176 time_t
177 last_changed_at(
178 	register struct pr_passwd *pr_pw
179 ) {
180 	if (pr_pw->uflg.fg_schange)
181 		return (pr_pw->ufld.fd_schange);
182 	return (0);
183 }
prwarn.c (Part 8/13)
184 enum password_change {
185 	CanBeChanged,
186 	TooEarly,
187 	Procrastinated
188 } changeable(
189 	register struct pr_passwd *pr_pw
190 ) {
191 	time_t lastchg, life;
192 	if ((lastchg = last_changed_at(pr_pw)) == 0)
193 		return (CanBeChanged);
194 	if (lastchg + minchange_is(pr_pw) > now)
195 		return (TooEarly);
196 	if ((life = lives_until(pr_pw)) != 0 && lastchg + life < now)
197 		return (Procrastinated);
198 	return (CanBeChanged);
199 }
200 int
201 authorized_to_change(
202 	register struct pr_passwd *pr_pw
203 ) {
204 	if (is_administrator)
205 		return (TRUE);
206 	else if (pr_pw->uflg.fg_pswduser)
207 		return (pr_pw->ufld.fd_pswduser == starting_luid());
208 	else if (pr_pw->sflg.fg_pswduser)
209 		return (pr_pw->sfld.fd_pswduser == starting_luid());
210 	else if (pr_pw->uflg.fg_uid)
211 		return (pr_pw->ufld.fd_uid == starting_luid());
212 	return (FALSE);
213 }
prwarn.c (Part 9/13)
214 int
215 report(
216 	struct passwd *pwd,
217 	int do_update
218 ) {
219 	struct pr_passwd *pr_pw;
220 	time_t expire, lastchg, nextchg;
221 	if ((pr_pw = getprpwnam(pwd->pw_name)) == (struct pr_passwd *)0) {
222 		audit_auth_entry(pwd->pw_name, OT_PRPWD,
223 			"User missing from Protected Password database"
224 		);
225 		(void) fprintf(stderr,
226 			"%s: Missing Protected Password entry: %s\n",
227 			command_name, pwd->pw_name
228 		);
229 		return (1);
230 	}
231 	if ( ! authorized_to_change(pr_pw)) {
232 		(void) fprintf(stderr,
233 			"%s: Sorry, you are not authorized to change %s's password.\n",
234 			command_name, pwd->pw_name
235 		);
236 		return (1);
237 	}
prwarn.c (Part 10/13)
238 if ( ! always && now - last_reported(pwd) < silence) 239 return (0);
240 expire = expires_at(pr_pw); 241 lastchg = last_changed_at(pr_pw); 242 nextchg = lastchg + expire; 243 if (lastchg == 0 || nolimit || (expire && nextchg - forced <= now)) { 244 if (do_update) 245 (void) fputs("Your password", stdout); 246 else 247 (void) printf("The password for %s", pwd->pw_name); 248 if (expire == 0) 249 (void) fputs(" never expires", stdout); 250 else if (lastchg == 0) 251 (void) fputs(" has never been set", stdout); 252 else 253 (void) printf(" expire%c %s at %s", 254 (nextchg < now) ? 'd' : 's', 255 when(nextchg), 256 nl_cxtime(&nextchg, "") 257 ); 258 switch (changeable(pr_pw)) { 259 case CanBeChanged: 260 (void) fputs(",\n\tand can be changed", stdout); 261 break; 262 case Procrastinated: 263 (void) fputs(",\n\tand is now dead", stdout); 264 break; 265 } 266 (void) fputs(".\n", stdout); 267 if (do_update) 268 just_reported(pwd); 269 } 270 return (0); 271 }
prwarn.c (Part 11/13)
272 void 273 usage( 274 char *fmt, 275 ... 276 ) { 277 va_list args;
278 if (fmt != (char *)0) { 279 (void) fprintf(stderr, "%s: ", command_name);
280 va_start(args, fmt); 281 (void) vfprintf(stderr, fmt, args); 282 va_end(args);
283 putc('\n', stderr); 284 } 285 (void) fprintf(stderr, 286 "Usage: %s [ -d days ] [ -t hh[mm] ] [ users ]...\n", 287 command_name 288 ); 289 exit(2); 290 /* NOTREACHED */ 291 }
prwarn.c (Part 12/13)
292 main(
293 	int argc,
294 	char *argv[]
295 ) {
296 	int c, nerrs;
297 	char *str, temp[ARBSTRSIZ];
298 	struct passwd *pwd;
299 	set_auth_parameters(argc, argv);
300 	(void) time(&now);
301 	is_administrator = authorized_user("auth");
302 	if (defopen(default_file) == 0) {
303 		if ((str = defread("DAYS_BEFORE=")) != (char *)0)
304 			(void) set_time_before(str);
305 		if ((str = defread("REMIND_TIME=")) != (char *)0)
306 			(void) set_remind_time(str);
307 		(void) defopen((char *)0);
308 	}
309 	while ((c = getopt(argc, argv, "d:t:")) != EOF) {
310 		switch (c) {
311 		case 'd':
312 			if (set_time_before(optarg))
313 				usage("Invalid number of days: %s", optarg);
314 			break;
315 		case 't':
316 			if (set_remind_time(optarg))
317 				usage("Invalid time interval: %s", optarg);
318 			break;
319 		case '?':
320 		default:
321 			usage((char *)0);
322 		}
323 	}
prwarn.c (Part 13/13)
324 if (optind >= argc) {
325 if ((pwd = getpwuid(starting_luid())) != (struct passwd *)0) 326 exit(report(pwd, TRUE)); 327 (void) sprintf(temp, "UID %d", starting_luid()); 328 audit_auth_entry(temp, OT_PWD, 329 "User missing from /etc/passwd" 330 ); 331 (void) fprintf(stderr, 332 "%s: Cannot determine your username!\n", 333 command_name 334 ); 335 exit(1); 336 } 337 else { 338 while (optind < argc) { 339 pwd = getpwnam(argv[optind]); 340 if (pwd != (struct passwd *)0) 341 nerrs += report(pwd, FALSE); 342 else 343 (void) printf("There is no user named %s.\n", 344 argv[optind] 345 ); 346 optind++; 347 } 348 exit(nerrs != 0); 349 } 350 /* NOTREACHED */ 351 }