"Fossies" - the Fresh Open Source Software Archive 
As a special service "Fossies" has tried to format the requested text file into HTML format (style:
standard) with prefixed line numbers.
Alternatively you can here
view or
download the uninterpreted source code file.
1 #! @PERL@
2 # PROGRAM : passwd_exp @VERSION@
3 # PURPOSE : warns of account expiration via email
4 # AUTHOR : Samuel Behan <samkob(at)gmail(dot)com> (c) 2000-2009
5 # HOMEPAGE : http://devel.dob.sk/passwd_exp
6 # LICENSE : GNU GPL v2, NO WARRANTY VOID (see file LICENSE)
7 ################################################################################
8
9 #requirements
10 use Getopt::Long qw(:config no_ignore_case);
11 use POSIX qw(uname strftime);
12 use Text::Tokenizer qw(:all);
13 @HAVE_LOCALE{use Locale::gettext;}@
14
15 #pragmas
16 use strict;
17 use integer;
18 use vars qw($VERSION $AUTHOR $AUTHOR_EMAIL $AGENT $SCRIPT $BANNER);
19
20 #script info
21 $AUTHOR = 'Samuel Behan';
22 $AUTHOR_EMAIL = 'samkob(at)gmail.com';
23 $VERSION = '@VERSION@';
24 $SCRIPT = 'passwd_exp';
25 $AGENT = 'Password expiration agent';
26 $BANNER = "This message was produced by passwd_exp version $VERSION";
27
28 ##
29 $| = 1;
30 my (%CONFIG, %UENV, $TIME);
31 $TIME = time();
32
33 #gettext shortcut
34 sub _($) { return @HAVE_LOCALE{gettext}@($_[0]); }
35
36 ##
37 # BASIC ENVIROMENT
38 my($prefix, $exec_prefix, $datarootdir);
39 $prefix = "@prefix@";
40 $exec_prefix = "@exec_prefix@";
41 $datarootdir = "@datarootdir@";
42 $CONFIG{-cfg_dir} = "@sysconfdir@";
43 $CONFIG{-mod_dir} = "@pw_moddir@";
44 $CONFIG{-data_dir} = "@datadir@";
45 $CONFIG{-mail_dirs} = ("$CONFIG{-cfg_dir}/mail", "$CONFIG{-data_dir}/mail");
46 $CONFIG{-conf_file} = $CONFIG{-cfg_dir}.'/passwd_exp.conf';
47 $CONFIG{-lock_file} = "@libdir@/passwd_exp";
48 $CONFIG{-lock_time} = 22 * 3600; #22 hours only lock time
49 $CONFIG{-module} = '@DB_MODULE@';
50 $CONFIG{-module_opt} = ();
51 $CONFIG{-force} = 0;
52 $CONFIG{-const} = ();
53 $CONFIG{-conf_set} = ();
54 $CONFIG{-mode} = 0;
55 $CONFIG{-test_user} = undef;
56 $CONFIG{-test_mode} = 0;
57 $CONFIG{-check_mode} = 0;
58 $CONFIG{-verbose} = 0;
59 $CONFIG{-domain} = (uname())[1]; $CONFIG{-domain} =~ s/^[^\.]+\.?//o;
60 $ENV{'PATH'} = $CONFIG{-mod_dir}.':/sbin:/usr/sbin:/usr/local/sbin:/usr/bin:/bin:/usr/local/bin';
61 #config file defauls
62 $CONFIG{'locale'} = $ENV{'LANG'} || 'C';
63 $CONFIG{'banner'} = 1;
64 $CONFIG{'expired_warn'} = 1;
65 $CONFIG{'msg_w_in'} = _('\'%user%\' [%ustate%] will expire in %expire_days% days on %expire_date%');
66 $CONFIG{'msg_w_done'} = _('\'%user%\' [%ustate%] has expired on %expire_date% (%expire_days% days ago)');
67 $CONFIG{'msg_e_in'} = _('\'%user%\' [%ustate%] will become inactive in %inactive_days% days on %inactive_date%');
68 $CONFIG{'msg_e_done'} = _('\'%user%\' [%ustate%] has been inactived on %inactive_date% (%inactive_days% days ago)');
69 $CONFIG{'msg_d_in'} = _('\'%user%\' [%ustate%] will be disabled in %account_days% days on %account_date%');
70 $CONFIG{'msg_d_done'} = _('\'%user%\' [%ustate%] has been disabled on %account_date% (%account_days% days ago)');
71
72 ##
73 # USER ENVIROMENT
74 $UENV{'locale'} = \$CONFIG{'locale'};
75 $UENV{'recipient'} = \$UENV{'user'};
76 $UENV{'user_name'} = $UENV{'username'} = \$UENV{'fullname'};
77 $UENV{'mail_addr'} = $UENV{'email_addr'} = \$UENV{'email'};
78 $UENV{'expire_in'} = $UENV{'expire_days'} = \$UENV{'edays'};
79 $UENV{'expire_date'} = \$UENV{'edate'};
80 $UENV{'inactive_in'} = $UENV{'inactive_days'} = \$UENV{'idays'};
81 $UENV{'inactive_date'} = \$UENV{'idate'};
82 $UENV{'account_days'} = \$UENV{'adays'};
83 $UENV{'account_date'} = \$UENV{'adate'};
84 $UENV{'today'} = $UENV{'date'} = \(strftime("%A %e %B %Y",localtime($TIME)));
85 $UENV{'ltoday'} = $UENV{'locale_date'} = $UENV{'ldate'}
86 = \(strftime("%x",localtime($TIME)));
87 $UENV{'now'} = $UENV{'time'} = \(strftime("%H:%M:%S",localtime($TIME)));
88 $UENV{'lnow'} = $UENV{'locale_time'} = $UENV{'ltime'}
89 = \(strftime("%X",localtime($TIME)));
90 $UENV{'unix_time'} = $UENV{'utime'} = \(strftime("%s",localtime($TIME)));
91 $UENV{'host_name'} = $UENV{'hostname'} = $UENV{'host'} = \(uname())[1];
92 $UENV{'host_domain'} = \$UENV{'domain'}; $UENV{'domain'} = $CONFIG{-domain};
93 $UENV{'host_os'} = $UENV{'os'} = \(uname())[0];
94 $UENV{'host_osver'} = $UENV{'osver'} = \(uname())[2];
95 $UENV{'host_machine'} = $UENV{'machine'} =
96 $UENV{'arch'} = $UENV{'host_arch'} = \(uname())[4];
97 #special vars (not aliasesed)
98 $UENV{'agent'} = $UENV{'sender'} = $AGENT;
99 $UENV{'version'} = $UENV{'ver'} = $VERSION;
100
101 ##
102 # Constants
103 sub C_NAME () { 0; };
104 sub C_FULLNAME () { 1; };
105 sub C_EMAIL () { 2; };
106 sub C_EDATE () { 3; };
107 sub C_ADATE () { 4; };
108 sub C_WDAYS () { 5; };
109 sub C_IDAYS () { 6; };
110 sub C_NOSEND () { 7; };
111
112 ##
113 # Configuration map
114 my %t_cfg_map = (
115 qr/^var(iable)?\[([\w-]+)\](\[([\w\*\?]+)\])?$/ =>
116 q/my $l = _localize($4, $opt_val);
117 set_var($2, $l) if(defined($l));/,
118 qr/^locale$/ => q/$CONFIG{"locale"} = $opt_val if(defined($opt_val) && ($opt_val ne "auto"))/,
119 qr/^direct(\s*|_)mta$/ => q/$CONFIG{"direct_mta"} = _isbool($opt_val);/,
120 qr/^(mail((er)|(\s*|_)sender)?)$/ =>
121 q/ return (-1, "mailer_recp_miss") if($opt_val !~ \/\%(recp|recipient)\%\/o);
122 $CONFIG{"mailer"} = $opt_val;/,
123 qr/^module$/ => q/$CONFIG{"module"} = $opt_val;/,
124 qr/^mod(ule)?(\s*|_)?opt(s|ion)?(\[(\w*)\])$/ =>
125 q/${ $CONFIG{-module_opt} }{$5} = $opt_val if(!exists(${ $CONFIG{-module_opt} }{$5}));/,
126 qr/^mta|mail(\s*|_)agent$/ =>
127 q/$CONFIG{"mta"} = $opt_val;/,
128 qr/^reply(\s*|_|-)to$/ => q/$CONFIG{-mailh}{"Reply-To"} = $opt_val;/,
129 qr/^send(\s*|_|-)from$/ => q/$CONFIG{-mailh}{"From"} = $opt_val;/,
130 qr/^mail((\s*|_|-)header)?\[([\w-]+)\]?$/ =>
131 q/$CONFIG{-mailh}{$3} = $opt_val if(defined($3));/,
132 qr/^(print(\s*|_))?banner$/ => q/$CONFIG{"banner"} = _isbool($opt_val);/,
133 qr/^(meg|mexpiring)(\[([\w\*\?]+)\])?$/=> q/$CONFIG{"msg_w_in"} = _localize($3, $opt_val) || $CONFIG{"msg_w_in"};/,
134 qr/^(med|mexpired)(\[([\w\*\?]+)\])?$/ => q/$CONFIG{"msg_w_done"} = _localize($3, $opt_val) || $CONFIG{"msg_w_done"};/,
135 qr/^(mig|minactiving)(\[([\w\*\?]+)\])?$/ => q/$CONFIG{"msg_e_in"} = _localize($3, $opt_val) || $CONFIG{"msg_e_in"};/,
136 qr/^(mid|minactived)(\[([\w\*\?]+)\])?$/ => q/$CONFIG{"msg_e_done"} = _localize($3, $opt_val) || $CONFIG{"msg_e_done"};/,
137 qr/^(mdig|mdinactiving)(\[([\w\*\?]+)\])?$/ => q/$CONFIG{"msg_d_in"} = _localize($3, $opt_val) || $CONFIG{"msg_d_in"};/,
138 qr/^(mdid|mdinactived)(\[([\w\*\?]+)\])?$/ => q/$CONFIG{"msg_d_done"} = _localize($3, $opt_val) || $CONFIG{"msg_d_done"};/,
139 qr/^(no(\s*|_)warnings?|ignore(\s*|_)users)(\s*(\.|\+))?$/ =>
140 q/return (-1, "obsoleted");/,
141 qr/^(no(\s*|_)check((\s*|_)file)?|ignore(\s*|_)file)$/ =>
142 q/return (-1, "obsoleted");/,
143 qr/^(warn(\s*|_)days)$/ =>
144 q/if($opt_val =~ \/^\d+$\/o) { $CONFIG{"warn_days"} = $opt_val; }
145 else { return (-2, 'digit'); }/,
146 qr/^(warn(\s*|_)days(\s*|_)step)$/ =>
147 q/if($opt_val =~ \/^\d+$\/o) { $CONFIG{"warn_days_step"} = $opt_val; }
148 else { return (-2, 'digit'); }/,
149 qr/^(mail(\s*|_)days(\s*|_)only)$/ =>
150 q/$CONFIG{"w_days"} = $CONFIG{"e_days"} = $CONFIG{"d_days"} = $opt_val;/,
151 qr/^(wo|warn(ing)?(\s*|_)days(\s*|_)only)$/ =>
152 q/$CONFIG{"w_days"} = $opt_val;/,
153 qr/^(ws|warn(ing)?(\s*|_)subject)(\[([\w\*\?]+)\])?$/ =>
154 q/$CONFIG{"w_subject"} = _localize($5, $opt_val) || $CONFIG{"w_subject"};
155 return (-3, 'mail subject') if(length($opt_val) > 50);/,
156 qr/^(wb|warn(ing)?(\s*|_)body)(\[([\w\*\?]+)\])?$/ =>
157 q/$CONFIG{"w_body"} = _localize($5, $opt_val) || $CONFIG{"w_body"};/,
158 qr/^(wf|warn(ing)?(\s*|_)file)(\[([\w\*\?]+)\])?$/ =>
159 q/$CONFIG{"w_file"} = _localize($5, $opt_val) || $CONFIG{"w_file"};/,
160 qr/^warn(\s*|_)expired$/ => q/$CONFIG{"expired_warn"} = _isbool($opt_val);/,
161 qr/^(eo|expired(\s*|_)days(\s*|_)only)$/ =>
162 q/$CONFIG{"e_days"} = $opt_val;/,
163 qr/^(es|expired(\s*|_)subject)(\[([\w\*\?]+)\])?$/ =>
164 q/$CONFIG{"e_subject"} = _localize($4, $opt_val) || $CONFIG{"e_subject"};
165 return (-3, 'expired mail subject') if(length($opt_val) > 50);/,
166 qr/^(eb|expired(\s*|_)body)(\[([\w\*\?]+)\])?$/ =>
167 q/$CONFIG{"e_body"} = _localize($4, $opt_val) || $CONFIG{"e_body"};/,
168 qr/^(ef|expired(\s*|_)file)(\[([\w\*\?]+)\])?$/ =>
169 q/$CONFIG{"e_file"} = _localize($4, $opt_val) || $CONFIG{"e_file"};/,
170 qr/^(warn(\s*|_))?date(\s*|_)(expired)?$/ =>
171 q/$CONFIG{"date_warn"} = _isbool($opt_val);/,
172 qr/^(ao|account(\s*|_)days(\s*|_)only)$/ =>
173 q/$CONFIG{"d_days"} = $opt_val;/,
174 qr/^(as|account(\s*|_)?subject)(\[([\w\*\?]+)\])?$/ =>
175 q/$CONFIG{"d_subject"} = _localize($4, $opt_val) || $CONFIG{"d_subject"};
176 return (-3, 'date expired mail subject') if(length($opt_val) > 50);/,
177 qr/^(ab|account?(\s*|_)?body)(\[([\w\*\?]+)\])?$/ =>
178 q/$CONFIG{"d_body"} = _localize($4, $opt_val) || $CONFIG{"d_body"};/,
179 qr/^(af|account?(\s*|_)?file)(\[([\w\*\?]+)\])?$/ =>
180 q/$CONFIG{"d_file"} = _localize($4, $opt_val) || $CONFIG{"d_file"};/,
181 );
182 ##
183 # Configuration error map
184 my %t_cfg_error = (
185 "mailer_recp_miss" => _("destination user recipient missing (see mailer config reference)"),
186 "obsoleted" => _("directive was obsoleted, remove it"),
187 );
188
189 ###
190 # print error msg
191 sub _error($;$)
192 {
193 print STDERR $_[0]."\n";
194 return (defined($_[1]) ? $_[1] : -1);
195 }
196
197 ##
198 # Print version info
199 sub cmd_print_version()
200 {
201 print STDOUT _("passwd_exp version")." ".$VERSION." $AUTHOR (c) 2000-2009\n";
202 return 0;
203 }
204
205 ##
206 # Print usage help
207 sub cmd_print_help(;$)
208 {
209 my $help_f = *STDOUT;
210 my $err = shift;
211 my $err_str = "";
212 my ($k);
213 my %t_help_opts = (
214 "-m MODULE" => _('use module to gather account data (try \'list\')'),
215 "-mi" => "\t"._('print active module informations'),
216 "-mo OPT=VALUE" => ""._('set module options'),
217 "-s DIR=VALUE" => ""._('set config file directive'),
218 "-l" => "\t"._('list expired/inactivated acounts'),
219 "-i" => "\t"._('ignore nocheck file'),
220 "-f" => "\t"._('force, ignore \'run once per day\' limit'),
221 "-t" => "\t"._('test mode, print e-mail(s) to stdout (use with -u)'),
222 "-T" => "\t"._('test mode, check config file only'),
223 "-w DAYS" => "\t"._('minimum warn days for checks'),
224 "-ws DAYS" => _('minimum warn days step for checks (increment warn days)'),
225 "-u USER" => "\t"._('check only this user'),
226 "-c FILE" => "\t"._('path to config file'),
227 "-v" => "\t"._('verbose mode (repeat for more verbosity)'),
228 "-V" => "\t"._('print version information'),
229 "-h" => "\t"._('print this help'),
230 "-d VAR=VALUE" => _('define variable'),
231 );
232
233 if(defined($err))
234 { $help_f = *STDERR;
235 $err_str = _("Error").": ".$err;
236 chomp($err_str);$err_str .= "\n"; }
237
238 #print usage
239 print $help_f _("usage").": $SCRIPT [options] [ USER ]\n";
240 print $help_f " -- passwd_exp $VERSION by $AUTHOR\n";
241
242 #print error
243 print $help_f $err_str;
244
245 #print options
246 print $help_f _("[options]")."\n";
247 foreach $k (sort(keys(%t_help_opts)))
248 { print $help_f "\t".$k."\t".$t_help_opts{$k}."\n"; }
249
250 exit(!defined($err));
251 }
252
253 ##
254 # Print verbose messages
255 sub verbose($$)
256 {
257 syswrite(STDERR, $_[1]."\n")
258 if(defined($CONFIG{-verbose}) && $_[0] <= $CONFIG{-verbose});
259 return 1;
260 }
261
262 ##
263 # Set var
264 sub set_var($$)
265 {
266 my $var = shift;
267 my $val = shift;
268 my $refv;
269
270 return if(!defined($var) && $var ne '');
271 #check for reference
272 if(ref($CONFIG{-const}{$var}) eq 'SCALAR')
273 { $refv = $CONFIG{-const}{$var}; }
274 else { $refv = \$CONFIG{-const}{$var}; }
275 ${ $refv } = $val;
276 return 1;
277 }
278
279 ##
280 # Load configuration from file
281 # ERRORS: -1 error message
282 # -2 bad format
283 # -3 too long
284 #
285 my $EXEC_CODE;
286 sub cmd_load_cfg($)
287 {
288 my $file = shift;
289
290 sub _isbool($)
291 { return 0 if(!defined($_[0]));
292 return 1 if($_[0] =~ /^yes|ok|allow|enable|1|true|oui|siano|jo|hej|da|ja|si$/oi);
293 return 0 if($_[0] =~ /^non?|deny|disable|0|false|ne|nicht|never$/oi);
294 return undef; }
295 sub _localize($$)
296 { defined($_[0]) || return $_[1];
297 my $q = $_[0];
298 $q =~ s/(\*)|(\?)|(\W)/${
299 defined($1) ? \('.*') : (
300 defined($2) ? \('.') : \("\\$3"))
301 }/og;
302 return $_[1] if(defined($CONFIG{'locale'}) &&
303 $CONFIG{'locale'} =~ /^$q$/);
304 return undef; }
305 sub _configure($$$$)
306 {
307 my $opt_key = shift;
308 my $opt_val = shift;
309 my $file = shift;
310 my $line = shift;
311 my($k, @do_ret);
312
313 #find config definition
314 foreach $k (keys(%t_cfg_map))
315 { next if($opt_key !~ /$k/);
316 $EXEC_CODE = $t_cfg_map{$k};
317
318 #do configure
319 @do_ret = eval($EXEC_CODE);
320 #check for eval error
321 if($@ ne '')
322 { print STDERR "$0: [programming error]\n\tCODE: $EXEC_CODE\n--- DIE ---\n$@-----------\n";
323 exit(1); }
324 #report config error
325 if(defined($do_ret[0])
326 && substr($do_ret[0], 0, 1) eq '-'
327 && $do_ret[0] < 0)
328 {
329 if($do_ret[0] == -1)
330 { return _error("[$file:$line] <$opt_key> ".$t_cfg_error{$do_ret[1]}, 0); }
331 elsif($do_ret[0] == -2)
332 { return _error("[$file:$line] <$opt_key> "._("bad input value format, expecting")." '$do_ret[1]'"); }
333 elsif($do_ret[0] == -3)
334 { return _error("[$file:$line] <$opt_key> "._("input value too long")." ".$do_ret[1]); }
335 else
336 { print STDERR "$0: [programming error]\n\tCODE: $EXEC_CODE\n--- DIE ---\n Unhandled error code '$do_ret[0]' -----------\n";
337 exit(1); }
338 }
339 return 1;
340 }
341 _error("[$file:$line] ".
342 _("unknown configuration directive")." '$opt_key'");
343 return undef;
344 }
345
346 no strict 'subs';
347
348 #open cfg file
349 return _error("$SCRIPT: "._("failed opening config file")." `$file' ($!)", 0)
350 if(!open(F_CFG, $file));
351
352 #setup tokenizer
353 my ($tokid);
354 $tokid = tokenizer_new(F_CFG);
355 tokenizer_options(TOK_OPT_UNESCAPE_LINES);
356 use strict;
357
358 #read conf
359 my (@opt_str, $opt_data, $opt_ign, $opt_key);
360 my ($tok_str, $tok_type, $tok_line, $tok_err, $tok_errline);
361 while(1)
362 {
363 SCAN:
364 ($tok_str, $tok_type, $tok_line, $tok_err, $tok_errline) =
365 tokenizer_scan();
366 last if($tok_type == TOK_EOF || $tok_type == TOK_ERROR ||
367 $tok_type == TOK_UNDEF);
368
369 #do conf
370 if($tok_type == TOK_EOL)
371 { goto RESET if($#opt_str == -1 && !defined($opt_data));
372 push(@opt_str, $opt_data);
373 goto CONFIG; }
374
375 #divide by =
376 if($tok_type == TOK_TEXT && $tok_str =~ /^([^=]*)\s*=\s*(.*)$/o)
377 { $opt_data .= $1;
378 push(@opt_str, $opt_data);
379 $opt_data = $2;
380 goto SCAN; }
381
382 #XXX: possible bug here, i don't check token type of adding
383 # string so we can add something bad in
384
385 #load data
386 $opt_data .= $tok_str;
387 goto SCAN;
388 CONFIG:
389 my ($opt_val);
390
391 #fix file pos
392 $tok_line--;
393
394 #get option value
395 $opt_val = pop(@opt_str) if($#opt_str != 0);
396 $opt_val = $1 if(defined($opt_val) &&
397 $opt_val =~ /^\s*(.*?)\s*$/o);
398 $opt_val = '' if(!defined($opt_val));
399 foreach $opt_key (@opt_str)
400 {
401 #trim white spaces
402 $opt_key = $1 if($opt_key =~ /^\s*(.*?)\s*$/o);
403 #ignore blank key
404 next if(defined($opt_key) && $opt_key eq '');
405 #preform configure
406 _configure($opt_key, $opt_val, $file, $tok_line) ||
407 return 0;
408
409 #clean
410 $EXEC_CODE = undef;
411 }
412 RESET:
413 undef @opt_str;
414 $opt_data = undef;
415 }
416 close(F_CFG);
417 undef $EXEC_CODE;
418 return 1;
419 }
420
421 ##
422 # Check lock file
423 sub cmd_check_lock($)
424 {
425 my $file = shift;
426 my @finfo;
427
428 #stat lock file
429 @finfo = stat($file);
430 if($#finfo != -1) { #lock file not found, create new one
431 #check time
432 return _error("$SCRIPT: "._("trying to perform expiration check too soon (use -f to override). Check should be perfomed only once a day !!!"), 0)
433 if($finfo[9] > ($TIME - $CONFIG{-lock_time})); }
434 #update lock file
435 open(LOCK, ">".$CONFIG{-lock_file}) ||
436 return _error("$SCRIPT: "._("failed to update lock file")." '$file' ($!)", -1);
437 my $data;
438
439 $data .= strftime("%s\n",localtime($TIME));
440 $data .= strftime("%a %b %e %H:%M:%S %Y",localtime($TIME));
441 syswrite(LOCK, $data);
442 close(LOCK);
443 return 1;
444 }
445
446
447 # special vars
448 my (@udata, @usdata, %days);
449 #$expire_days == w, $inactive_days == e , $datexp_days == d);
450
451 ##
452 # Apply message enviroment
453 # XXX: this is what i call POWERFULL REGEXP or EXTREME HACK
454 sub eval_vars($\%\%)
455 {
456 my $str = shift;
457 my $evar_h = shift;
458 my %evar = %{ $evar_h };
459 my $env_h = shift;
460 my %env = %{ $env_h };
461
462 # replace user vars
463 $str =~ s/(%([\w-]+)(\[(\w+)\])?%)/${
464 (!exists($evar{$2})) ? \$1 :
465 # !defined($evar{$2}) ? \('UNDEF') :
466 (ref($evar{$2}) eq '') ? \$evar{$2} :
467 (ref($evar{$2}) eq 'SCALAR') ?
468 # ((defined(${ $evar{$2} })) ?
469 # $evar{$2} : \('UNDEF')) :
470 $evar{$2} :
471 (ref($evar{$2}) eq 'ARRAY') ?
472 ((!defined($4) || !exists(${ $evar{$2} }[$4])) ?
473 \$1 : \${ $evar{$2} }[$4]) :
474 die('[programming error]')
475 }/og;
476
477 # evalute special vars
478 $str =~ s/%((w|warn(ing)?)|(e|expire)|(a|account)|(c|curr?(ent)?))(_|\.|-|->|=>)%?
479 ([aAbBcCdDeEgGhHIjkmMOpPrsStTuUVwWxXyYzZ\+])%/${
480 (defined($2)) ?
481 \strftime("%$9", localtime($TIME + $days{'w'} * 86400))
482 :
483 (defined($4)) ?
484 \strftime("%$9", localtime($TIME + $days{'e'} * 86400))
485 :
486 (defined($5)) ?
487 \strftime("%$9", localtime($TIME + $days{'d'} * 86400))
488 :
489 \strftime("%$9", localtime($TIME))
490 }/oxg;
491
492 # evalute enviroment vars
493 $str =~ s/(\$((\w+)|\{(\w+)\}))/${
494 exists($env{$3 || $4}) ? \$env{$3 || $4} : \$1
495 }/og;
496 #evalute special chars
497 $str =~ s/\\([tnrfae]|x[[:xdigit:]]{1,4}|x\{[[:xdigit:]]{1,4}\})/${
498 eval('return \("\\'.$1.'");') }/og;
499 return $str;
500 }
501
502 ##
503 # Setup variables
504 sub setup_vars()
505 {
506 $UENV{'user'} = $udata[C_NAME];
507 $UENV{'fullname'} = $udata[C_FULLNAME] || $udata[C_NAME];
508 $UENV{'email'} = $udata[C_EMAIL];
509 $UENV{'edays'} = abs($days{'w'});
510 $UENV{'edate'} = strftime("%a %e %B %Y",localtime($TIME + $days{'w'} * 86400));
511 $UENV{'idays'} = abs($days{'e'});
512 $UENV{'idate'} = strftime("%a %e %B %Y",localtime($TIME + $days{'e'} * 86400));
513 $UENV{'adays'} = abs($days{'d'});
514 $UENV{'adate'} = strftime("%a %e %B %Y",localtime($TIME + $days{'d'} * 86400));
515
516 #eval user constants
517 my $k;
518 foreach $k (keys(%{ $CONFIG{-const} }))
519 { next if(!defined(${ $CONFIG{-const} }{$k}));
520 #some calls might get saved here
521 $UENV{$k} = eval_vars(${ $CONFIG{-const} }{$k}, %UENV, %ENV); }
522 return 1;
523 }
524
525 ##
526 # Return mail data
527 sub get_mail_data($)
528 {
529 my $mode = shift;
530 my $file = $CONFIG{$mode.'_file'};
531
532 #mail file not defined
533 return ($CONFIG{$mode.'_subject'}, $CONFIG{$mode.'_body'})
534 if(!defined($file));
535
536 #eval file
537 my $found;
538 $file = eval_vars($file, %UENV, %ENV);
539 #test path absolutness
540 if(!substr($file, 0, 1) eq '/')
541 { my $dir;
542 #check if file exists
543 foreach $dir ($CONFIG{-mail_dirs})
544 { if( -e "$dir/$file" )
545 { $file = "$dir/$file";
546 last; } } }
547 #final existance
548 if(! -e $file)
549 { _error("$SCRIPT: ".sprintf(_("Mail file '%s' not found, skipping..."), $file));
550 #we die if mail file not found
551 return (undef, 1) if($file eq $CONFIG{$mode.'_file'});
552 return (undef, 0); }
553
554 #parse file
555 if(!open(MAILF, $file))
556 { _error("$SCRIPT: ".sprintf(_("Failed to open mail file, skipping..."), $file));
557 #we die if mail file not found
558 return (undef, 1) if($file eq $CONFIG{$mode.'_file'});
559 return (undef, 0); }
560
561 my ($subj, $body);
562
563 #read subj (first line of file)
564 $subj = <MAILF>;
565 chomp($subj);
566
567 #read body (rest of file, or to the dot line)
568 my $line;
569 while(defined($line = <MAILF>) && $line !~ /^\.$/o)
570 { $body .= $line; }
571
572 close(MAILF);
573 return ($subj, $body);
574 }
575
576 ##
577 # Send email
578 # sub cmd_sendmail($recp, $subj, $messg)
579 sub cmd_sendmail($$$)
580 {
581 my $mail_recp = shift;
582 my $mail_subj = shift;
583 my $mail_msg = shift;
584 my (@mail_head, $mail, $k);
585
586 #personalize messages
587 $mail_recp = eval_vars($mail_recp, %UENV, %ENV);
588 $mail_subj = eval_vars($mail_subj, %UENV, %ENV);
589 $mail_msg = eval_vars($mail_msg, %UENV, %ENV);
590
591 #check input
592 exit(_error("$SCRIPT: Mail body not defined, exiting..", 1))
593 if(!defined($mail_msg) || $mail_msg eq '');
594 $mail_subj = $AGENT." email"
595 if(!defined($mail_subj) || $mail_subj eq '');
596
597 #create mail head
598 push(@mail_head, "From: ".(exists($CONFIG{-mailh}{'From'}) ? $CONFIG{-mailh}{'From'} : "$AGENT <root\@localhost.localdomain>" ));
599 push(@mail_head, "Reply-To: ".$CONFIG{-mailh}{'Reply-To'}) if(exists($CONFIG{-mailh}{'Reply-To'}));
600 #FIXME: delete mail headers
601 push(@mail_head, "To: ".$mail_recp);
602 push(@mail_head, "Subject: ".$mail_subj);
603 push(@mail_head, "Full-Name: ".$UENV{'fullname'});
604 push(@mail_head, "X-Mailer: $SCRIPT $VERSION");
605 #add user headers
606 foreach $k (keys(%{ $CONFIG{-mailh} }))
607 { #ignore already set headers
608 next if(grep (/^$k$/, ("From", "Reply-To", "To", "Subject", "Full-Name", "X-Mailer")));
609 push(@mail_head, "$k: ".eval_vars($CONFIG{-mailh}{$k}, %UENV, %ENV)); }
610 #assemble mail
611 foreach $k (@mail_head)
612 { $mail .= "$k\n"; }
613
614 #open destination + print headers (if needed)
615 if($CONFIG{-test_mode} == 1)
616 { open(MAIL, ">&STDOUT");
617 syswrite(MAIL, "--- EMAIL($mail_recp) --------\n");
618 syswrite(MAIL, $mail); }
619 elsif(defined($CONFIG{"direct_mta"}) && $CONFIG{"direct_mta"} == 1)
620 { open(MAIL, "| $CONFIG{mta} -i '$mail_recp'") ||
621 return _error("$SCRIPT: "._("(mta) mail transport agent was unable send e-mail using")." '$CONFIG{mta}' ($!)", 0);
622 syswrite(MAIL, $mail); }
623 elsif(defined($CONFIG{"mailer"}))
624 { my $cmd;
625
626 #assemble command
627 $cmd = eval_vars($CONFIG{"mailer"},
628 %{ { "recipient"=> $mail_recp,
629 "user" => $UENV{'user'},
630 "subject" => $mail_subj,
631 "recp" => \$mail_recp,
632 "subj" => \$mail_subj } },
633 %ENV);
634 open(MAIL, "| $cmd") ||
635 return _error("$SCRIPT: "._("(mailer) unable to send e-mail via '$cmd'")." ($!)", 0); }
636 else
637 { exit(_error("$SCRIPT: No sending agent defined, exiting...", 1)); }
638
639 #now print message to email
640 syswrite(MAIL, $mail_msg."\n");
641 syswrite(MAIL, "\n---\n".$BANNER."\n")
642 if(defined($CONFIG{"banner"}) && $CONFIG{"banner"} == 1);
643 close(MAIL) ||
644 exit(_error("$SCRIPT: "._("Sending agent returned error retval")." ($!)",1));
645 return 1;
646 }
647
648 ##
649 # Match day to pattern
650 # sub match_day($pattern, $value)
651 sub match_day($$)
652 {
653 my @ar = split(' ', shift);
654 my $val = shift;
655
656 my $patt;
657 foreach $patt (@ar)
658 { #easy test for *
659 return 1 if($patt eq '*');
660 #test for digit
661 return 1 if($patt == $val);
662 #test for range
663 return 1 if(($patt =~ /^(\d+)-(\d+)?$/o)
664 && ($val >= $1)
665 && (!defined($2) || ($val <= $2)));
666 #test for repeat pattern
667 return 1 if($patt =~ /^\*\/(\d+)$/o
668 && ($1 != 0) && ($val % $1 == 0));
669 }
670 return 0;
671 }
672
673 ################
674 # MAIN PROGRAM #
675 ################
676 my ($cmd);
677
678 #parse command line
679 $SIG{__WARN__} = sub { exit(cmd_print_help($_[0])); };
680 $cmd = Getopt::Long::GetOptions(
681 "c|config=s" => \$CONFIG{-conf_file},
682 "u|user=s" => \$CONFIG{-test_user},
683 "l|list" => sub { $CONFIG{-mode} = 1; },
684 "f|force!" => \$CONFIG{-force},
685 "t|test" => \$CONFIG{-test_mode},
686 "T|configcheck" => \$CONFIG{-check_mode},
687 "v|verbose+" => \$CONFIG{-verbose},
688 "V|Version" => sub { exit(cmd_print_version()); },
689 "h|help" => sub { exit(cmd_print_help()); },
690 "w|warn-days=i" => \$CONFIG{-warn_days},
691 "ws|warn-days-step=i" => \$CONFIG{-warn_days_step},
692 "d|define=s%" => \$CONFIG{-const},
693 "m|module=s" => \$CONFIG{-module},
694 "mi|module-info" => \$CONFIG{-module_info},
695 "mo|module-opt=s%" => \$CONFIG{-module_opt},
696 "s|set=s%" => \$CONFIG{-conf_set},
697 "<>" => sub { $CONFIG{-test_user} = $_[0]; },
698 );
699 exit(1) if(!$cmd); #Bad imput
700 $SIG{__WARN__} = 'DEFAULT';
701
702 #list avaible modules
703 if(defined($CONFIG{-module}) && $CONFIG{-module} eq 'list')
704 {
705 opendir(DIR, $CONFIG{-mod_dir})
706 || exit(_error("$SCRIPT: "._("can not list modules")." ($!)",1));
707 print _("Modules").":\n";
708 while(defined(($cmd = readdir(DIR))))
709 { next if(substr($cmd, 0, 1) eq '.' ||
710 ! -x $CONFIG{-mod_dir}.'/'.$cmd);
711 next if(substr($cmd, -5) eq '.info'); #ignore info files
712 next if(substr($cmd, 0, 1) eq '-'); #ignore helper mods
713 printf("\t%-20s", $cmd);
714 if(-r $CONFIG{-mod_dir}.'/'.$cmd.'.info')
715 { open(F_INFO, $CONFIG{-mod_dir}.'/'.$cmd.'.info');
716 $cmd = <F_INFO>;
717 chomp($cmd);
718 print substr($cmd, 0, 52);
719 close(F_INFO); }
720 print "\n";
721 }
722 closedir(DIR);
723 exit(0); }
724
725 #load config file
726 verbose(1, "Loading config file `$CONFIG{-conf_file}'...");
727 cmd_load_cfg($CONFIG{-conf_file}) || exit(1);
728
729 #config check mode
730 if($CONFIG{-check_mode} == 1)
731 { print _("Syntax OK")."\n";
732 exit(0); }
733
734 # fix config
735 #remove bad mail headers
736 foreach $cmd (qw(To Subject Full-Name X-Mailer))
737 { delete $CONFIG{-mailh}{$cmd}; }
738
739 #setup command line config
740 foreach $cmd (sort(keys(%{ $CONFIG{-conf_set} })))
741 { _configure($cmd, ${ $CONFIG{-conf_set} }{$cmd}, "<-s>", 0) || exit(1); }
742
743 #check lock file
744 verbose(1, "Checking lock file `$CONFIG{-lock_file}'...");
745 exit(1) if((exists($CONFIG{-test_mode}) && $CONFIG{-test_mode} != 1)
746 && (exists($CONFIG{-force}) && $CONFIG{-force} != 1)
747 && (!defined($CONFIG{-test_user}))
748 && ($CONFIG{-mode} != 1)
749 && (exists($CONFIG{-module_info}) && $CONFIG{-module_info} != 1)
750 && cmd_check_lock($CONFIG{-lock_file}) != 1);
751
752 #get user database (via submodule)
753 my ($line, $linenum, $mode, $days);
754 $days = int($TIME / 86400);
755 $UENV{'data'} = \@udata;
756 $UENV{'userdata'} = $UENV{'udata'} = \@usdata;
757
758 $CONFIG{-module} = $CONFIG{"module"};
759 exit(_error("$SCRIPT: No data module defined (see -m argument)", 1))
760 if(!defined($CONFIG{-module}) || $CONFIG{-module} eq '');
761 $CONFIG{-module} .= ' info'
762 if(defined($CONFIG{-module_info}) && $CONFIG{-module_info} == 1);
763 foreach $cmd (sort(keys(%{ $CONFIG{-module_opt}})))
764 { $CONFIG{-module} .= " '$cmd=${ $CONFIG{-module_opt} }{$cmd}'"; }
765
766 verbose(1, "Executing data module `$CONFIG{-module}'...");
767 open(USERS, "-|", $CONFIG{-module})
768 || exit(_error("$SCRIPT: "._("failed to retreive user list data"). " ($!)", 1));
769 while(defined(($line = <USERS>)))
770 {
771 $linenum++;
772 #ignore commented lines + remove eol
773 next if(substr($line, 0, 1) eq '#');
774 chomp($line);
775
776 #split data & check them
777 verbose(2, "[line $linenum] Parsing input data");
778 @udata = split(/(?<!\\):/o, $line);
779 #find out our sys/special separator
780 @usdata = @udata;
781 foreach $_ (@udata)
782 { shift(@usdata); last if($_ eq '*'); }
783
784 #check only specific user
785 next if(defined($CONFIG{-test_user}) &&
786 $udata[C_NAME] ne $CONFIG{-test_user});
787
788 #complete data
789 $udata[C_WDAYS] = $CONFIG{-warn_days}
790 if(defined($CONFIG{-warn_days}) && $udata[C_WDAYS] < $CONFIG{'warn_days'});
791 $udata[C_WDAYS] = $CONFIG{'warn_days'}
792 if(defined($CONFIG{'warn_days'}) && $udata[C_WDAYS] < $CONFIG{'warn_days'}
793 && !defined($CONFIG{-warn_days}));
794 #warn days step
795 $udata[C_WDAYS] += $CONFIG{-warn_days_step}
796 if(defined($CONFIG{-warn_days_step}));
797 $udata[C_WDAYS] += $CONFIG{'warn_days_step'}
798 if(defined($CONFIG{'warn_days_step'}) && !defined($CONFIG{-warn_days_step}));
799 $udata[C_WDAYS] = 0 if(!defined($udata[C_WDAYS]) || $udata[C_WDAYS] eq '');
800 $udata[C_IDAYS] = 0 if(!defined($udata[C_IDAYS]) || $udata[C_IDAYS] eq '');
801 $udata[C_ADATE] = 0 if(!defined($udata[C_ADATE]) || $udata[C_ADATE] eq '');
802 $udata[C_EDATE] = 0 if(!defined($udata[C_EDATE]) || $udata[C_EDATE] eq '');
803
804 #check input data format
805 verbose(2, "[line $linenum] [user $udata[0]] Checking input data format");
806 foreach $cmd (@{ [C_WDAYS, C_IDAYS, C_EDATE, C_ADATE] })
807 { exit(_error("[$CONFIG{-module}:$linenum:<".($cmd).">] ".
808 _("element should be an integer value"), 1))
809 if(!($udata[$cmd] =~ /^\d+|$/o)); }
810
811 exit(_error("[$CONFIG{-module}:$linenum:<".(C_NOSEND).">] ".
812 _("element should be an bool value (1/0)"), 1))
813 if(!($udata[C_NOSEND] =~ /^1|0|$/o));
814 exit(_error("[$CONFIG{-module}:$linenum:<".(C_NAME).">] ".
815 _("element should be an word value"), 1))
816 if(!($udata[C_NAME] =~ /^\S+$/o));
817 exit(_error("[$CONFIG{-module}:$linenum:<".(C_EMAIL).">] ".
818 _("element should be an email address"), 1))
819 if(!($udata[C_EMAIL] =~ /^\S+(\@\S+)?$/o));
820
821 #create enviroment
822 verbose(2, "[line $linenum] [user $udata[0]] Computing values");
823 $days{'w'} = $udata[C_EDATE] - $days;
824 $days{'e'} = ($udata[C_EDATE] + $udata[C_IDAYS]) - $days;
825 $days{'d'} = $udata[C_ADATE] - $days;
826 verbose(3, "[line $linenum] [user $udata[0]] w:$days{w} e:$days{e} d:$days{d}");
827
828 #make %ustate%
829 $UENV{'ustate'} = undef;
830 $UENV{'ustate'} .= 'C'
831 if(defined($udata[C_NOSEND]) && $udata[C_NOSEND] == 1);
832
833 #check if any action required
834 verbose(2, "[line $linenum] [user $udata[0]] Determining warning mode");
835 if($udata[C_WDAYS] != 0 && abs($days{'d'}) <= $udata[C_WDAYS])
836 { $mode = 'd';
837 $UENV{'ustate'} .= 'D'; } #date expiration
838 elsif((($udata[C_IDAYS] != 0 && abs($days{'e'}) <= $udata[C_IDAYS])
839 || (abs($days{'e'}) < abs($days{'w'}))) #inactivation takes precedence
840 && (($CONFIG{-mode} == 1) || ($CONFIG{"expired_warn"})))
841 { $mode = 'e'; } #expired (inactivation)
842 elsif($udata[C_WDAYS] != 0 && (abs($days{'w'}) <= $udata[C_WDAYS]
843 || ($CONFIG{-mode} == 1) && $days{'w'} < 0))
844 { $mode = 'w'; } #expiration
845 else
846 { $mode = undef; } #no mode
847
848 #not in any state
849 next if(!defined($mode));
850 next if($CONFIG{-mode} == 0 && $days{$mode} < 0);
851
852 #make %ustate%
853 $UENV{'ustate'} .= 'N' if(!defined($UENV{'ustate'}));
854
855 #inform
856 verbose(1, "[line $linenum] [user $udata[0]] `".
857 ${{'w'=>'Expiration',
858 'e'=>'Inactivation',
859 'd'=>'Date Expiration'}}{$mode}."' mode, taking actions...");
860
861 #test if we realy should send email
862 my $send_pattern = $CONFIG{$mode."_days"};
863 if($CONFIG{-mode} == 0
864 && defined($days{$mode}) && defined($send_pattern) && !($send_pattern eq '*')
865 && !match_day($send_pattern,$days{$mode}))
866 { verbose(1, "[line $linenum] [user $udata[0]] mail will be not sent today");
867 next; }
868
869 #get domain name from email address
870 if($udata[C_EMAIL] =~ /^\S+\@(\S+)$/o)
871 { $UENV{"domain"} = $1; }
872 else { $UENV{"domain"} = $CONFIG{-domain}; }
873
874 #do it
875 setup_vars();
876 if($CONFIG{-mode} == 0) #mail send
877 { next if(defined($udata[C_NOSEND]) && $udata[C_NOSEND] == 1);
878
879 my ($subj, $body) = get_mail_data($mode);
880
881 #test mail data errors
882 if(!defined($subj))
883 { next if($body = 0);
884 exit(1) if($body = 1); }
885 #error something failed
886 exit(1) if(!cmd_sendmail($udata[C_EMAIL], $subj, $body));
887 }
888 elsif($CONFIG{-mode} == 1) #list users
889 { my $msg = $CONFIG{'msg_'.$mode.'_'.(($days{$mode} >= 0) ? 'in' : 'done')};
890
891 $msg = eval_vars($msg, %UENV, %ENV);
892 syswrite(STDOUT, $msg."\n"); }
893 else #unknown mode
894 { die("[programming error]"); }
895
896 #clear variables
897 undef $mode;
898 undef @udata;
899 }
900 my $x=<USERS>;
901 close(USERS) ||
902 exit(_error("$SCRIPT: "._("data module returned error retval")." ($?)", 1));
903 verbose(1, "Finished.");
904
905 __END__
906
907 =head1 NAME
908
909 passwd_exp - password/account expiration checking tool
910
911 =head1 SYNOPSIS
912
913 passwd_exp [options] [USERNAME]
914
915
916 =head1 DESCRIPTION
917
918 B<passwd_exp> warns users of password/account expiration via email. It extends similar function of login process,
919 that prints such a messages at login time, but many users do not login for a long (long) time and only
920 download/forward their email, so they have absolutely no chance to find out what's happening with their account.
921
922 This script will warn them (via email), and save you from request to re-enable users accounts that has
923 been 'magicaly' disabled by that BAD BAD man called Linux or whatever :) (And be sure there will be some if you have
924 system with many users forcing them to change their passwords to get just a little more security).
925
926 Extra feature of this script is listing of expired user accounts so you will have some more info about your system.
927
928
929 =head1 OPTIONS
930
931 =over 4
932
933 =item -c FILE config file
934
935 =item -u USERNAME username to check
936
937 =item -l list users, do not send mails
938
939 =item -f override `run once per day' restriction
940
941 =item -t test mode, print generated emails instead of sending them
942
943 =item -T test configuration file validity
944
945 =item -v verbose mode, more times for more verbosity
946
947 =item -d var=value define variable for message enviroment
948
949 =item -m MODULE module to use (can be module name or program path)
950
951 =item -mi print module informations
952
953 =item -mo option=value set module option (argument)
954
955 =item -s option=value override config file option
956
957 =back
958
959 =head1 FILES
960
961 =over 4
962
963 =item /etc/passwd_exp/passwd_exp.conf - config file
964
965 =item /etc/passwd_exp/mail - mail files search dir
966
967 =item /usr/share/passwd_exp/mail - mail files search dir
968
969 =item /usr/share/passwd_exp/mod - input modules dir
970
971 =item /etc/cron.daily/passwd_exp.cron - daily cron check script
972
973 =item /etc/cron.weekly/passwd_exp-admin.cron - admin weekly cron check script
974
975 =head1 BUGS
976
977 None found (yet).
978
979 =head1 SEE ALSO
980
981 =over 4
982
983 =item B<Text-Tokenizer>
984
985 perl module for fast text analyzation at B<http://devel.dob.sk/Text-Tokenizer>
986
987 =item B<pfadmin>
988
989 postfix virtual email accounts managment tools (for vmail.pfadmin data module)
990 B<http://devel.dob.sk/pfadmin>
991
992 =back
993
994 =head1 AUTHOR
995
996 Samuel Behan, E<lt>samkob(at)gmail(dot)comE<gt>
997
998 =head1 COPYRIGHT AND LICENSE
999
1000 Copyright 2003-2005 by Samuel Behan
1001
1002 This library is free software; you can redistribute it and/or modify
1003 it under the same terms of GNU/GPL v2.
1004
1005 =cut
1006
1007
1008 #EOF (c) by UN*X 1970-$EOD (End of Days) [ EOD (c) by God ]