DOC HOME SITE MAP MAN PAGES GNU INFO SEARCH PRINT BOOK
 
Using trusted facilities

prwarn.c example

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.

prwarn.c (Part 1/13)

  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 *);


line 1
SecureWare must be defined in programs that use the trusted software. If it is not defined on the compile line, the #define SecureWare statement is required. This allows the security-related definitions in the header files to be interpreted correctly.

line 5
This header file is necessary because the code defines functions with variable length argument lists.

lines 7-9
These lines include the three header files that define the trusted facilities.

line 10
pwd.h defines the passwd structure. Trusted software uses this information to identify the user.

lines 11-12
These header files define time conventions used to calculate the time between warnings and the time until the password expires.

lines 14-15
defopen and defread are used to read default program options. See defopen(S) for more information.

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 }


lines 16-20
These global #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.

lines 16-17
The literals TRUE and FALSE are used to improve readability.

line 18
ARBSTRSIZ is used in lines 83 and 298.

lines 19-20
The time resolutions for this program are in seconds. Defining constants for these time spans increases the readability of the code.

line 21
This line provides storage for the current date and time. The time_t type is defined in the /usr/include/sys/types.h file.

line 22
This line is TRUE if LUID (logged in user identification) is accounts administrator.

line 23
This line specifies the name of the file used to track the previous warning time for this user.

line 24
This line specifies the name of the file used to store system defaults for the program.

line 25
The default time between reports is six hours. This program uses the second as the time measurement, and determines the time of the report by multiplying the number of hours by the constant defined in line 19. Lines 47-49 override this default value.

line 26
This line defines a global flag.

lines 27-50
These lines parse the <hhmm> string using standard string comparison functions.

lines 45-46
These lines check that the value of hour and mins is valid. If no return, a -1 indicates an error. The program does not exit at this point, although this is a fatal error. This error can only result if the user made a typing error when issuing the command. When this routine is called (line 317), an error return causes it to call the usage routine defined in lines 272-291. usage prints an error message, a reminder of the correct syntax of the command, and then exits.

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 }


lines 53-67
These lines parse the <days> string.

lines 68-75
These lines determine the number of days in a specified Julian year.\*(F

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 }


lines 76-110
These lines calculate the relation between the specified date and today.

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 }


lines 111-122
These lines determine the date and time that the user was last warned that the password is about to expire. The modification time of the $HOME/.prwarn_time file is used as the last time the user was notified.

lines 117-118
The home directory is treated relative to /, even if the listing in the /etc/passwd file is not absolute.

lines 119-120
This code uses the stat(S) system call to return statistics on the file; the st_mtime value returned is the last modification time stored in the file's inode.

lines 123-133
These lines remember that a pending expiration was just reported. This code assumes that the current working directory is the user's home directory as set by the last_reported routine in lines 111-122.

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 }


lines 134-147
This code calculates the date and time after which a password must be changed. ``0'' is interpreted as infinity; in other words, the password has no expiration date.

lines 148-161
This code calculates the time that must elapse between password changes.

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 }


lines 162-175
This code calculates the date or time after which the password cannot be used.

lines 176-183
This code calculates the date or time at which the password was last changed.

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 }



lines 184-199
These lines determine whether the password can be changed now, and if not, why not. CanBeChanged means that the password can be changed now. TooEarly means that the password cannot be changed at this time because not enough time has elapsed since it was last changed. Procrastinated means that the password should have been changed before now; the password is expired but the lifetime is not up. CanBeChanged is referenced in lines 198 and 259; TooEarly is referenced in line 195; Procrastinated is referenced in lines 197 and 262.

lines 192-193
If the password was never set, it is legal to set it.

lines 194-195
If the time since the last change was less than the ``minimum time change'' calculated in lines 148-161, the password cannot be changed.

lines 196-197
If the password has a finite lifetime and was last changed earlier than the date calculated in lines 162-169, the password cannot be changed (and the user cannot log in).

line 198
Otherwise, the password can be changed. This code makes no provision for the possibility that the user has retired or left the company.

lines 200-213
This code checks whether the current user (LUID) is authorized to change this user's password. An unauthorized user has no reason to know when another user's password expires. The user running this program is authorized to know when a password expires if, and only if, the user running this program can change the password. This routine is called in line 231.

lines 204-205
If the LUID has the auth subsystem authorization, the password may be changed.

lines 206-207
If the user has a pswduser, the LUID can change the password if it is the user's pswduser.

lines 208-209
If the system has a default pswduser, the password can be changed if, and only if, the LUID is the system default pswduser. Note that this condition is met only if the user does not have a pswduser.

lines 210-211
Users can change their own passwords.

line 212
If none of the above conditions are met, the LUID running the program is not authorized to change this password. It is possible to prevent a user from ever changing the password.

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 }


lines 214-271
These lines report to the user if the password expires soon.

lines 221-230
The user has an entry in the /etc/passwd file, so without the Protected Password entry, you can audit the problem.

lines 231-237
If the user is not authorized to change the password, print an error message. The authorized_to_change subroutine is defined in lines 200-213.

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 }


lines 238-239
The user sets the frequency at which to be reminded that the password is about to expire. If that time period is greater than the lapse since a warning was issued, do not issue a warning and do not update the time of last warning.

lines 240-271
Otherwise, issue a warning that the password must be reset soon, and update the time of the last warning.

line 255
Lines 77-110 define the when subroutine. The subroutine returns a human-readable string representing a number of day in the future or past.

line 259
Line 185 defines the CanBeChanged case.

line 262
Line 187 defines the Procrastinated case.

line 268
Lines 123-133 defines the just_reported routine. The routine changes the date on the users' .prwarn_time file to record when they were last warned.

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 }


lines 272-291
This subroutine prints the usage message and exits the program if the user issues the command incorrectly. It is called in lines 313, 317, and 321.

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 }


line 292
This line begins the main() routine. Thus far, everything has been subroutines that are called as functions in main().

line 299
set_auth_parameters(S) must be the first function called in main().

lines 300-301
These lines initialize the current date and time, and determine whether or not the logged-in user is an accounts administrator.

lines 302-308
If there are any legal system-wide defaults, use them rather than the program's built-in defaults (lines 25-26 and 51-52).

lines 309-323
If the user supplied explicit values on the command line, check that they are legal and, if so, use these values rather than the program or system defaults.

lines 324-349
If the command line specifies additional users, report on pending expirations for those users but do not update the time-of-last-report. Otherwise, report only on the logged-in user, updating the time-of-last-report as appropriate.

lines 324-336
This code converts the logged-in user's UID to the user's name and reports a pending expiration. If it is missing from the Protected Password database, it audits the fact and prints an error message.

lines 337-349
If the named user is not listed in the /etc/passwd file, then there is no user by that name. In this case, print an error message to the user's terminal.

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 }


line 324
This line checks optind from line 14. optind is the index in argv of the next argument, so if optind >= argc, the code has processed all of the command-line arguments.

lines 325-326
This code tries to get the Protected Password entry for the login user ID stored when set_auth_parameters was called. If it is successful, it calls the report routine (lines 214-271) to print a report about the current user. If it fails, it audits the fact and prints error messages.


Footnotes

A year is a leap year if it is divisible by 4 but not by 100, except that years divisible by 400 are leap years. The ``less three days every four hundred years'' rule is the difference between the old Gregorian and modern Julian calendars. The conversion in September 1752 resulted in losing the 11 extra days that had built up in the four 400-year cycles since the beginning of the Common Era. . . . . .

Next topic: loge.c example
Previous topic: subsys.c example

© 2003 Caldera International, Inc. All rights reserved.
SCO OpenServer Release 5.0.7 -- 11 February 2003