| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | =head1 NAME | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  | QMail::QueueHandler - Module to manage QMail message queues | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | This is all the code behind the qmHandle command line program. | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | use QMail::QueueHandler; | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | QMail::QueueHandler->new->run; | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | =cut | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | package QMail::QueueHandler; | 
| 18 |  |  |  |  |  |  |  | 
| 19 | 1 |  |  | 1 |  | 73452 | use Moose; | 
|  | 1 |  |  |  |  | 482207 |  | 
|  | 1 |  |  |  |  | 8 |  | 
| 20 |  |  |  |  |  |  |  | 
| 21 | 1 |  |  | 1 |  | 8685 | use Term::ANSIColor; | 
|  | 1 |  |  |  |  | 9693 |  | 
|  | 1 |  |  |  |  | 79 |  | 
| 22 | 1 |  |  | 1 |  | 2019 | use Getopt::Std; | 
|  | 1 |  |  |  |  | 49 |  | 
|  | 1 |  |  |  |  | 58 |  | 
| 23 | 1 |  |  | 1 |  | 8 | use File::Basename; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 6032 |  | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | our $VERSION = '2.0.4'; | 
| 26 |  |  |  |  |  |  | my $me       = basename $0; | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | # Where qmail stores all of its files | 
| 29 |  |  |  |  |  |  | has queue => ( | 
| 30 |  |  |  |  |  |  | is      => 'ro', | 
| 31 |  |  |  |  |  |  | isa     => 'Str', | 
| 32 |  |  |  |  |  |  | default => '/var/qmail/queue/', | 
| 33 |  |  |  |  |  |  | ); | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | # Which todo format do we have? | 
| 36 |  |  |  |  |  |  | has bigtodo => ( | 
| 37 |  |  |  |  |  |  | is      => 'ro', | 
| 38 |  |  |  |  |  |  | isa     => 'Bool', | 
| 39 |  |  |  |  |  |  | lazy    => 1, | 
| 40 |  |  |  |  |  |  | default => sub { -d $_[0]->queue . 'todo/0' }, | 
| 41 |  |  |  |  |  |  | ); | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | # Various commands that we use | 
| 44 |  |  |  |  |  |  | has commands => ( | 
| 45 |  |  |  |  |  |  | is      => 'ro', | 
| 46 |  |  |  |  |  |  | isa     => 'HashRef', | 
| 47 |  |  |  |  |  |  | default => sub { | 
| 48 |  |  |  |  |  |  | { | 
| 49 |  |  |  |  |  |  | start => '/sbin/service qmail start', | 
| 50 |  |  |  |  |  |  | stop  => '/sbin/service qmail stop', | 
| 51 |  |  |  |  |  |  | pid   => '/sbin/pidof qmail-send', | 
| 52 |  |  |  |  |  |  | }; | 
| 53 |  |  |  |  |  |  | }, | 
| 54 |  |  |  |  |  |  | ); | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | # Colours for output. | 
| 57 |  |  |  |  |  |  | # Default is non-coloured. These values can be changed in parse_args. | 
| 58 |  |  |  |  |  |  | has colours => ( | 
| 59 |  |  |  |  |  |  | is      => 'ro', | 
| 60 |  |  |  |  |  |  | isa     => 'HashRef', | 
| 61 |  |  |  |  |  |  | default => sub { | 
| 62 |  |  |  |  |  |  | { | 
| 63 |  |  |  |  |  |  | msg  => '', | 
| 64 |  |  |  |  |  |  | stat => '', | 
| 65 |  |  |  |  |  |  | end  => '', | 
| 66 |  |  |  |  |  |  | }; | 
| 67 |  |  |  |  |  |  | }, | 
| 68 |  |  |  |  |  |  | ); | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | # Are we showing a summary? | 
| 71 |  |  |  |  |  |  | has summary => ( | 
| 72 |  |  |  |  |  |  | is      => 'ro', | 
| 73 |  |  |  |  |  |  | isa     => 'Bool', | 
| 74 |  |  |  |  |  |  | default => 0, | 
| 75 |  |  |  |  |  |  | ); | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | # Are we supposed to be deleting things? | 
| 78 |  |  |  |  |  |  | has deletions => ( | 
| 79 |  |  |  |  |  |  | is  => 'rw', | 
| 80 |  |  |  |  |  |  | isa => 'Bool', | 
| 81 |  |  |  |  |  |  | ); | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | # What actions are we carrying out. | 
| 84 |  |  |  |  |  |  | # Each element in this array is another array. | 
| 85 |  |  |  |  |  |  | # The first element in these second level arrays is a code ref. | 
| 86 |  |  |  |  |  |  | # The other elements are arguments to be passed to the code ref. | 
| 87 |  |  |  |  |  |  | has actions => ( | 
| 88 |  |  |  |  |  |  | is      => 'ro', | 
| 89 |  |  |  |  |  |  | traits  => ['Array'], | 
| 90 |  |  |  |  |  |  | isa     => 'ArrayRef', | 
| 91 |  |  |  |  |  |  | default => sub { [] }, | 
| 92 |  |  |  |  |  |  | handles => { | 
| 93 |  |  |  |  |  |  | add_action  => 'push', | 
| 94 |  |  |  |  |  |  | all_actions => 'elements', | 
| 95 |  |  |  |  |  |  | }, | 
| 96 |  |  |  |  |  |  | ); | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | # Do we need to restart QMail once we have finished? | 
| 99 |  |  |  |  |  |  | has restart => ( | 
| 100 |  |  |  |  |  |  | is      => 'rw', | 
| 101 |  |  |  |  |  |  | isa     => 'Bool', | 
| 102 |  |  |  |  |  |  | default => 0, | 
| 103 |  |  |  |  |  |  | ); | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | # List of messages to delete | 
| 106 |  |  |  |  |  |  | has to_delete => ( | 
| 107 |  |  |  |  |  |  | is      => 'rw', | 
| 108 |  |  |  |  |  |  | traits  => ['Array'], | 
| 109 |  |  |  |  |  |  | isa     => 'ArrayRef', | 
| 110 |  |  |  |  |  |  | default => sub { [] }, | 
| 111 |  |  |  |  |  |  | handles => { | 
| 112 |  |  |  |  |  |  | add_to_delete   => 'push', | 
| 113 |  |  |  |  |  |  | all_to_delete   => 'elements', | 
| 114 |  |  |  |  |  |  | to_delete_count => 'count', | 
| 115 |  |  |  |  |  |  | }, | 
| 116 |  |  |  |  |  |  | ); | 
| 117 |  |  |  |  |  |  |  | 
| 118 |  |  |  |  |  |  | before add_to_delete => sub { | 
| 119 |  |  |  |  |  |  | my $self = shift; | 
| 120 |  |  |  |  |  |  | my ($msg_id) = @_; | 
| 121 |  |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  | warn "Message [$msg_id] queued for deletion.\n"; | 
| 123 |  |  |  |  |  |  | }; | 
| 124 |  |  |  |  |  |  |  | 
| 125 |  |  |  |  |  |  | # List of messages to flag | 
| 126 |  |  |  |  |  |  | has to_flag => ( | 
| 127 |  |  |  |  |  |  | is      => 'rw', | 
| 128 |  |  |  |  |  |  | traits  => ['Array'], | 
| 129 |  |  |  |  |  |  | isa     => 'ArrayRef', | 
| 130 |  |  |  |  |  |  | default => sub { [] }, | 
| 131 |  |  |  |  |  |  | handles => { | 
| 132 |  |  |  |  |  |  | add_to_flag => 'push', | 
| 133 |  |  |  |  |  |  | all_to_flag => 'elements', | 
| 134 |  |  |  |  |  |  | }, | 
| 135 |  |  |  |  |  |  | ); | 
| 136 |  |  |  |  |  |  |  | 
| 137 |  |  |  |  |  |  | # Hash containing details of the messages in the queue | 
| 138 |  |  |  |  |  |  | has msglist => ( | 
| 139 |  |  |  |  |  |  | is         => 'rw', | 
| 140 |  |  |  |  |  |  | isa        => 'HashRef', | 
| 141 |  |  |  |  |  |  | lazy_build => 1, | 
| 142 |  |  |  |  |  |  | ); | 
| 143 |  |  |  |  |  |  |  | 
| 144 |  |  |  |  |  |  | sub BUILD { | 
| 145 | 1 |  |  | 1 | 0 | 2 | my $self = shift; | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  | # Get command line options | 
| 148 | 1 |  |  |  |  | 5 | $self->parse_args; | 
| 149 |  |  |  |  |  |  | } | 
| 150 |  |  |  |  |  |  |  | 
| 151 |  |  |  |  |  |  | =head1 METHODS | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | =head2 run() | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | Main driver method. | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | =cut | 
| 158 |  |  |  |  |  |  |  | 
| 159 |  |  |  |  |  |  | sub run { | 
| 160 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 161 | 0 |  |  |  |  | 0 | my @args = @_; | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | # (Possibly) stop qmail | 
| 164 | 0 |  |  |  |  | 0 | $self->stop_qmail; | 
| 165 |  |  |  |  |  |  |  | 
| 166 |  |  |  |  |  |  | # Execute actions | 
| 167 | 0 |  |  |  |  | 0 | foreach my $action ( $self->all_actions ) { | 
| 168 | 0 |  |  |  |  | 0 | my $sub = shift @$action;    # First element is the sub | 
| 169 | 0 |  |  |  |  | 0 | $self->$sub(@$action);       # Others the arguments, if any | 
| 170 |  |  |  |  |  |  | } | 
| 171 |  |  |  |  |  |  |  | 
| 172 |  |  |  |  |  |  | # If we have planned deletions, then do them. | 
| 173 | 0 | 0 |  |  |  | 0 | if ( $self->to_delete_count ) { | 
| 174 | 0 |  |  |  |  | 0 | $self->trash_msgs; | 
| 175 |  |  |  |  |  |  | } | 
| 176 |  |  |  |  |  |  |  | 
| 177 |  |  |  |  |  |  | # If we stopped qmail, then restart it | 
| 178 | 0 |  |  |  |  | 0 | $self->start_qmail; | 
| 179 |  |  |  |  |  |  | } | 
| 180 |  |  |  |  |  |  |  | 
| 181 |  |  |  |  |  |  | sub _get_todo { | 
| 182 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 183 | 0 |  |  |  |  | 0 | my ($todohash, $msglist) = @_; | 
| 184 |  |  |  |  |  |  |  | 
| 185 | 0 |  |  |  |  | 0 | my $queue = $self->queue; | 
| 186 |  |  |  |  |  |  |  | 
| 187 | 0 |  |  |  |  | 0 | opendir( my $tododir, "${queue}todo" ); | 
| 188 |  |  |  |  |  |  |  | 
| 189 | 0 | 0 |  |  |  | 0 | if ( $self->bigtodo ) { | 
| 190 | 0 |  |  |  |  | 0 | foreach my $todofile ( grep { !/\./ } readdir $tododir ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 191 | 0 |  |  |  |  | 0 | $todohash->{$todofile} = $todofile; | 
| 192 |  |  |  |  |  |  | } | 
| 193 |  |  |  |  |  |  | } | 
| 194 |  |  |  |  |  |  | else { | 
| 195 | 0 |  |  |  |  | 0 | foreach my $tododir ( grep { !/\./ } readdir $tododir ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 196 | 0 |  |  |  |  | 0 | opendir( my $subdir, "${queue}todo/$tododir" ); | 
| 197 | 0 |  |  |  |  | 0 | foreach my $todofile ( | 
| 198 | 0 |  |  |  |  | 0 | grep { !/\./ } | 
| 199 | 0 |  |  |  |  | 0 | map  { "$tododir/$_" } readdir $subdir | 
| 200 |  |  |  |  |  |  | ) { | 
| 201 | 0 |  |  |  |  | 0 | $msglist->{$todofile}{'todo'} = $todofile; | 
| 202 |  |  |  |  |  |  | } | 
| 203 |  |  |  |  |  |  | } | 
| 204 |  |  |  |  |  |  | } | 
| 205 | 0 |  |  |  |  | 0 | closedir $tododir; | 
| 206 |  |  |  |  |  |  | } | 
| 207 |  |  |  |  |  |  |  | 
| 208 |  |  |  |  |  |  | sub _get_info { | 
| 209 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 210 | 0 |  |  |  |  | 0 | my ($dir, $msglist) = @_; | 
| 211 |  |  |  |  |  |  |  | 
| 212 | 0 |  |  |  |  | 0 | my $queue = $self->queue; | 
| 213 |  |  |  |  |  |  |  | 
| 214 | 0 |  |  |  |  | 0 | opendir( my $infosubdir, "${queue}info/$dir" ); | 
| 215 |  |  |  |  |  |  |  | 
| 216 | 0 |  |  |  |  | 0 | foreach my $infofile ( | 
| 217 | 0 |  |  |  |  | 0 | grep { !/\./ } | 
| 218 | 0 |  |  |  |  | 0 | map  { "$dir/$_" } readdir $infosubdir | 
| 219 |  |  |  |  |  |  | ) { | 
| 220 | 0 |  |  |  |  | 0 | $msglist->{$infofile}{sender} = 'S'; | 
| 221 |  |  |  |  |  |  | } | 
| 222 |  |  |  |  |  |  |  | 
| 223 | 0 |  |  |  |  | 0 | close $infosubdir; | 
| 224 |  |  |  |  |  |  | } | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | sub _get_local { | 
| 227 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 228 | 0 |  |  |  |  | 0 | my ($dir, $msglist) = @_; | 
| 229 |  |  |  |  |  |  |  | 
| 230 | 0 |  |  |  |  | 0 | my $queue = $self->queue; | 
| 231 |  |  |  |  |  |  |  | 
| 232 | 0 |  |  |  |  | 0 | opendir( my $localsubdir, "${queue}local/$dir" ); | 
| 233 |  |  |  |  |  |  |  | 
| 234 | 0 |  |  |  |  | 0 | foreach my $localfile ( | 
| 235 | 0 |  |  |  |  | 0 | grep { !/\./ } | 
| 236 | 0 |  |  |  |  | 0 | map  { "$dir/$_" } readdir $localsubdir | 
| 237 |  |  |  |  |  |  | ) { | 
| 238 | 0 |  |  |  |  | 0 | $msglist->{$localfile}{local} = 'L'; | 
| 239 |  |  |  |  |  |  | } | 
| 240 |  |  |  |  |  |  |  | 
| 241 | 0 |  |  |  |  | 0 | close $localsubdir; | 
| 242 |  |  |  |  |  |  | } | 
| 243 |  |  |  |  |  |  |  | 
| 244 |  |  |  |  |  |  | sub _get_remote { | 
| 245 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 246 | 0 |  |  |  |  | 0 | my ($dir, $msglist) = @_; | 
| 247 |  |  |  |  |  |  |  | 
| 248 | 0 |  |  |  |  | 0 | my $queue = $self->queue; | 
| 249 |  |  |  |  |  |  |  | 
| 250 | 0 |  |  |  |  | 0 | opendir( my $remotesubdir, "${queue}remote/$dir" ); | 
| 251 |  |  |  |  |  |  |  | 
| 252 | 0 |  |  |  |  | 0 | foreach my $remotefile ( | 
| 253 | 0 |  |  |  |  | 0 | grep { !/\./ } | 
| 254 | 0 |  |  |  |  | 0 | map  { "$dir/$_" } readdir $remotesubdir | 
| 255 |  |  |  |  |  |  | ) { | 
| 256 | 0 |  |  |  |  | 0 | $msglist->{$remotefile}{remote} = 'R'; | 
| 257 |  |  |  |  |  |  | } | 
| 258 |  |  |  |  |  |  |  | 
| 259 | 0 |  |  |  |  | 0 | close $remotesubdir; | 
| 260 |  |  |  |  |  |  | } | 
| 261 |  |  |  |  |  |  |  | 
| 262 |  |  |  |  |  |  | sub _get_subdir { | 
| 263 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 264 | 0 |  |  |  |  | 0 | my ($dir, $msglist, $todohash, $bouncehash) = @_; | 
| 265 |  |  |  |  |  |  |  | 
| 266 | 0 |  |  |  |  | 0 | my $queue = $self->queue; | 
| 267 |  |  |  |  |  |  |  | 
| 268 | 0 |  |  |  |  | 0 | opendir( my $subdir, "${queue}mess/$dir" ); | 
| 269 |  |  |  |  |  |  |  | 
| 270 | 0 |  |  |  |  | 0 | foreach my $file ( | 
| 271 | 0 |  |  |  |  | 0 | grep { !/\./ } | 
| 272 | 0 |  |  |  |  | 0 | map  { "$dir/$_" } readdir $subdir | 
| 273 |  |  |  |  |  |  | ) { | 
| 274 | 0 |  |  |  |  | 0 | my $msgno = ( split( /\//, $file ) )[1]; | 
| 275 | 0 | 0 |  |  |  | 0 | if ( $bouncehash->{$msgno} ) { | 
| 276 | 0 |  |  |  |  | 0 | $msglist->{$file}{bounce} = 'B'; | 
| 277 |  |  |  |  |  |  | } | 
| 278 | 0 | 0 |  |  |  | 0 | if ( $self->bigtodo ) { | 
| 279 | 0 | 0 |  |  |  | 0 | if ( $todohash->{$msgno} ) { | 
| 280 | 0 |  |  |  |  | 0 | $msglist->{$file}{todo} = $msgno; | 
| 281 |  |  |  |  |  |  | } | 
| 282 |  |  |  |  |  |  | } | 
| 283 |  |  |  |  |  |  | } | 
| 284 |  |  |  |  |  |  |  | 
| 285 | 0 |  |  |  |  | 0 | closedir $subdir; | 
| 286 |  |  |  |  |  |  | } | 
| 287 |  |  |  |  |  |  |  | 
| 288 |  |  |  |  |  |  | sub _build_msglist { | 
| 289 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 290 |  |  |  |  |  |  |  | 
| 291 | 0 |  |  |  |  | 0 | my $queue = $self->queue; | 
| 292 |  |  |  |  |  |  |  | 
| 293 | 0 |  |  |  |  | 0 | my ( $todohash, $bouncehash ); | 
| 294 | 0 |  |  |  |  | 0 | my $msglist = {}; | 
| 295 |  |  |  |  |  |  |  | 
| 296 | 0 |  |  |  |  | 0 | $self->_get_todo($todohash, $msglist); | 
| 297 |  |  |  |  |  |  |  | 
| 298 | 0 |  |  |  |  | 0 | opendir( my $bouncedir, "${queue}bounce" ); | 
| 299 | 0 |  |  |  |  | 0 | foreach my $bouncefile ( grep { !/\./ } readdir $bouncedir ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 300 | 0 |  |  |  |  | 0 | $bouncehash->{$bouncefile} = 'B'; | 
| 301 |  |  |  |  |  |  | } | 
| 302 | 0 |  |  |  |  | 0 | closedir $bouncedir; | 
| 303 |  |  |  |  |  |  |  | 
| 304 | 0 |  |  |  |  | 0 | opendir( my $messdir, "${queue}mess" ); | 
| 305 |  |  |  |  |  |  |  | 
| 306 | 0 |  |  |  |  | 0 | foreach my $dir ( grep { !/\./ } readdir $messdir ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 307 | 0 |  |  |  |  | 0 | $self->_get_info($dir); | 
| 308 | 0 |  |  |  |  | 0 | $self->_get_local($dir); | 
| 309 | 0 |  |  |  |  | 0 | $self->_get_remote($dir); | 
| 310 | 0 |  |  |  |  | 0 | $self->_get_subdir($dir); | 
| 311 |  |  |  |  |  |  | } | 
| 312 | 0 |  |  |  |  | 0 | closedir $messdir; | 
| 313 |  |  |  |  |  |  |  | 
| 314 | 0 |  |  |  |  | 0 | return $msglist; | 
| 315 |  |  |  |  |  |  | } | 
| 316 |  |  |  |  |  |  |  | 
| 317 |  |  |  |  |  |  | =head2 parse_args() | 
| 318 |  |  |  |  |  |  |  | 
| 319 |  |  |  |  |  |  | Parse the command line arguments and set any required attributes. | 
| 320 |  |  |  |  |  |  |  | 
| 321 |  |  |  |  |  |  | =cut | 
| 322 |  |  |  |  |  |  |  | 
| 323 |  |  |  |  |  |  | sub parse_args { | 
| 324 | 1 |  |  | 1 | 1 | 2 | my $self = shift; | 
| 325 |  |  |  |  |  |  |  | 
| 326 | 1 | 50 |  |  |  | 7 | @ARGV or $self->usage; | 
| 327 |  |  |  |  |  |  |  | 
| 328 | 1 |  |  |  |  | 2 | my %opt; | 
| 329 |  |  |  |  |  |  |  | 
| 330 |  |  |  |  |  |  | my %option = ( | 
| 331 |  |  |  |  |  |  | # (Attempt to) send all queued messages | 
| 332 |  |  |  |  |  |  | a => { | 
| 333 |  |  |  |  |  |  | arg  => 0, | 
| 334 |  |  |  |  |  |  | code => sub { | 
| 335 | 0 |  |  | 0 |  | 0 | $self->add_action( [ \&send_msgs ] ); | 
| 336 |  |  |  |  |  |  | }, | 
| 337 |  |  |  |  |  |  | }, | 
| 338 |  |  |  |  |  |  | # List message queues | 
| 339 |  |  |  |  |  |  | l => { | 
| 340 |  |  |  |  |  |  | arg  => 0, | 
| 341 |  |  |  |  |  |  | code => sub { | 
| 342 | 0 |  |  | 0 |  | 0 | $self->add_action( [ \&list_msg, 'A' ] ); | 
| 343 |  |  |  |  |  |  | }, | 
| 344 |  |  |  |  |  |  | }, | 
| 345 |  |  |  |  |  |  | # List local message queue | 
| 346 |  |  |  |  |  |  | L => { | 
| 347 |  |  |  |  |  |  | arg  => 0, | 
| 348 |  |  |  |  |  |  | code => sub { | 
| 349 | 0 |  |  | 0 |  | 0 | $self->add_action( [ \&list_msg, 'L' ] ); | 
| 350 |  |  |  |  |  |  | }, | 
| 351 |  |  |  |  |  |  | }, | 
| 352 |  |  |  |  |  |  | # List remote message queue | 
| 353 |  |  |  |  |  |  | R => { | 
| 354 |  |  |  |  |  |  | arg  => 0, | 
| 355 |  |  |  |  |  |  | code => sub { | 
| 356 | 0 |  |  | 0 |  | 0 | $self->add_action( [ \&list_msg, 'R' ] ); | 
| 357 |  |  |  |  |  |  | }, | 
| 358 |  |  |  |  |  |  | }, | 
| 359 |  |  |  |  |  |  | # List message numbers only | 
| 360 |  |  |  |  |  |  | N => { | 
| 361 |  |  |  |  |  |  | arg  => 0, | 
| 362 |  |  |  |  |  |  | code => sub { | 
| 363 | 0 |  |  | 0 |  | 0 | $self->summary(1); | 
| 364 |  |  |  |  |  |  | }, | 
| 365 |  |  |  |  |  |  | }, | 
| 366 |  |  |  |  |  |  | # Coloured output | 
| 367 |  |  |  |  |  |  | c => { | 
| 368 |  |  |  |  |  |  | arg  => 0, | 
| 369 |  |  |  |  |  |  | code => sub { | 
| 370 | 0 |  |  | 0 |  | 0 | @{ $self->colours }{qw[msg stat end]} = ( | 
|  | 0 |  |  |  |  | 0 |  | 
| 371 |  |  |  |  |  |  | color('bold bright_blue'), | 
| 372 |  |  |  |  |  |  | color('bold bright_red'), | 
| 373 |  |  |  |  |  |  | color('reset'), | 
| 374 |  |  |  |  |  |  | ); | 
| 375 |  |  |  |  |  |  | }, | 
| 376 |  |  |  |  |  |  | }, | 
| 377 |  |  |  |  |  |  | # Show statistics of queues | 
| 378 |  |  |  |  |  |  | s => { | 
| 379 |  |  |  |  |  |  | arg  => 0, | 
| 380 |  |  |  |  |  |  | code => sub { | 
| 381 | 0 |  |  | 0 |  | 0 | $self->add_action( [ \&stats ] ); | 
| 382 |  |  |  |  |  |  | }, | 
| 383 |  |  |  |  |  |  | }, | 
| 384 |  |  |  |  |  |  | # Display message with given number | 
| 385 |  |  |  |  |  |  | m => { | 
| 386 |  |  |  |  |  |  | arg  => 1, | 
| 387 |  |  |  |  |  |  | code => sub { | 
| 388 | 0 |  |  | 0 |  | 0 | $self->add_action( [ \&view_msg, @_ ] ); | 
| 389 |  |  |  |  |  |  | }, | 
| 390 |  |  |  |  |  |  | }, | 
| 391 |  |  |  |  |  |  | # Delete messages from given sender | 
| 392 |  |  |  |  |  |  | f => { | 
| 393 |  |  |  |  |  |  | arg  => 1, | 
| 394 |  |  |  |  |  |  | code => sub { | 
| 395 | 0 |  |  | 0 |  | 0 | $self->add_action( [ \&del_msg_from_sender, @_ ] ); | 
| 396 | 0 |  |  |  |  | 0 | $self->deletions(1); | 
| 397 |  |  |  |  |  |  | }, | 
| 398 |  |  |  |  |  |  | }, | 
| 399 |  |  |  |  |  |  | # Delete messages from given sender (regex match) | 
| 400 |  |  |  |  |  |  | F => { | 
| 401 |  |  |  |  |  |  | arg  => 1, | 
| 402 |  |  |  |  |  |  | code => sub { | 
| 403 | 0 |  |  | 0 |  | 0 | $self->add_action( [ \&del_msg_from_sender_r, @_ ] ); | 
| 404 | 0 |  |  |  |  | 0 | $self->deletions(1); | 
| 405 |  |  |  |  |  |  | }, | 
| 406 |  |  |  |  |  |  | }, | 
| 407 |  |  |  |  |  |  | # Delete message with given number | 
| 408 |  |  |  |  |  |  | d => { | 
| 409 |  |  |  |  |  |  | arg  => 1, | 
| 410 |  |  |  |  |  |  | code => sub { | 
| 411 | 0 |  |  | 0 |  | 0 | $self->add_action( [ \&del_msg, @_ ] ); | 
| 412 | 0 |  |  |  |  | 0 | $self->deletions(1); | 
| 413 |  |  |  |  |  |  | }, | 
| 414 |  |  |  |  |  |  | }, | 
| 415 |  |  |  |  |  |  | # Delete messages with matching subject | 
| 416 |  |  |  |  |  |  | S => { | 
| 417 |  |  |  |  |  |  | arg  => 1, | 
| 418 |  |  |  |  |  |  | code => sub { | 
| 419 | 0 |  |  | 0 |  | 0 | $self->add_action( [ \&del_msg_subj, @_ ] ); | 
| 420 | 0 |  |  |  |  | 0 | $self->deletions(1); | 
| 421 |  |  |  |  |  |  | }, | 
| 422 |  |  |  |  |  |  | }, | 
| 423 |  |  |  |  |  |  | # Delete messages with matching header (case insensitive) | 
| 424 |  |  |  |  |  |  | h => { | 
| 425 |  |  |  |  |  |  | arg  => 1, | 
| 426 |  |  |  |  |  |  | code => sub { | 
| 427 | 0 |  |  | 0 |  | 0 | $self->add_action( [ \&del_msg_header_r, @_, 1 ] ); | 
| 428 | 0 |  |  |  |  | 0 | $self->deletions(1); | 
| 429 |  |  |  |  |  |  | }, | 
| 430 |  |  |  |  |  |  | }, | 
| 431 |  |  |  |  |  |  | # Delete messages with matching body (case insensitive) | 
| 432 |  |  |  |  |  |  | b => { | 
| 433 |  |  |  |  |  |  | arg  => 1, | 
| 434 |  |  |  |  |  |  | code => sub { | 
| 435 | 0 |  |  | 0 |  | 0 | $self->add_action( [ \&del_msg_body_r, @_, 1 ] ); | 
| 436 | 0 |  |  |  |  | 0 | $self->deletions(1); | 
| 437 |  |  |  |  |  |  | }, | 
| 438 |  |  |  |  |  |  | }, | 
| 439 |  |  |  |  |  |  | # Delete messages with matching header (case sensitive) | 
| 440 |  |  |  |  |  |  | H => { | 
| 441 |  |  |  |  |  |  | arg  => 1, | 
| 442 |  |  |  |  |  |  | code => sub { | 
| 443 | 0 |  |  | 0 |  | 0 | $self->add_action( [ \&del_msg_header_r, @_, 0 ] ); | 
| 444 | 0 |  |  |  |  | 0 | $self->deletions(1); | 
| 445 |  |  |  |  |  |  | }, | 
| 446 |  |  |  |  |  |  | }, | 
| 447 |  |  |  |  |  |  | # Delete messages with matching body (case sensitive) | 
| 448 |  |  |  |  |  |  | B => { | 
| 449 |  |  |  |  |  |  | arg  => 1, | 
| 450 |  |  |  |  |  |  | code => sub { | 
| 451 | 0 |  |  | 0 |  | 0 | $self->add_action( [ \&del_msg_body_r, @_, 0 ] ); | 
| 452 | 0 |  |  |  |  | 0 | $self->deletions(1); | 
| 453 |  |  |  |  |  |  | }, | 
| 454 |  |  |  |  |  |  | }, | 
| 455 |  |  |  |  |  |  | # Flag messages with matching recipients | 
| 456 |  |  |  |  |  |  | t => { | 
| 457 |  |  |  |  |  |  | arg  => 1, | 
| 458 |  |  |  |  |  |  | code => sub { | 
| 459 | 0 |  |  | 0 |  | 0 | $self->add_actions( [ \&flag_remote, @_ ] ); | 
| 460 |  |  |  |  |  |  | }, | 
| 461 |  |  |  |  |  |  | }, | 
| 462 |  |  |  |  |  |  | # Delete all messages in queues | 
| 463 |  |  |  |  |  |  | D => { | 
| 464 |  |  |  |  |  |  | arg  => 0, | 
| 465 |  |  |  |  |  |  | code => sub { | 
| 466 | 0 |  |  | 0 |  | 0 | $self->add_action( [ \&del_all ] ); | 
| 467 | 0 |  |  |  |  | 0 | $self->deletions(1); | 
| 468 |  |  |  |  |  |  | }, | 
| 469 |  |  |  |  |  |  | }, | 
| 470 |  |  |  |  |  |  | # Display program version | 
| 471 |  |  |  |  |  |  | V => { | 
| 472 |  |  |  |  |  |  | arg  => 0, | 
| 473 |  |  |  |  |  |  | code => sub { | 
| 474 | 0 |  |  | 0 |  | 0 | $self->add_action( [ \&version ] ); | 
| 475 |  |  |  |  |  |  | }, | 
| 476 |  |  |  |  |  |  | }, | 
| 477 |  |  |  |  |  |  | # Display help | 
| 478 |  |  |  |  |  |  | '?' => { | 
| 479 |  |  |  |  |  |  | arg  => 0, | 
| 480 |  |  |  |  |  |  | code => sub { | 
| 481 | 0 |  |  | 0 |  | 0 | $self->usage; | 
| 482 |  |  |  |  |  |  | }, | 
| 483 |  |  |  |  |  |  | }, | 
| 484 | 1 |  |  |  |  | 87 | ); | 
| 485 |  |  |  |  |  |  |  | 
| 486 | 1 | 100 |  |  |  | 8 | my $optstring = join '', map { $_ . ( $option{$_}{arg} ? ':' : '' ) } | 
|  | 20 |  |  |  |  | 39 |  | 
| 487 |  |  |  |  |  |  | keys %option; | 
| 488 |  |  |  |  |  |  |  | 
| 489 | 1 |  |  |  |  | 9 | getopts( $optstring, \%opt ); | 
| 490 |  |  |  |  |  |  |  | 
| 491 | 1 |  |  |  |  | 37 | foreach my $opt ( keys %opt ) { | 
| 492 | 0 | 0 |  |  |  | 0 | if (! exists $option{$opt}) { | 
| 493 | 0 |  |  |  |  | 0 | warn "$opt is not a valid option\n"; | 
| 494 | 0 |  |  |  |  | 0 | next; | 
| 495 |  |  |  |  |  |  | } | 
| 496 | 0 | 0 | 0 |  |  | 0 | if ( $option{$opt}{arg} and not $opt{$opt} ) { | 
| 497 | 0 |  |  |  |  | 0 | die "Option $opt must have an argument\n"; | 
| 498 |  |  |  |  |  |  | } | 
| 499 |  |  |  |  |  |  |  | 
| 500 | 0 | 0 |  |  |  | 0 | if ($option{$opt}{arg}) { | 
| 501 | 0 |  |  |  |  | 0 | $option{$opt}{code}->($opt{$opt}); | 
| 502 |  |  |  |  |  |  | } else { | 
| 503 | 0 |  |  |  |  | 0 | $option{$opt}{code}->(); | 
| 504 |  |  |  |  |  |  | } | 
| 505 |  |  |  |  |  |  | } | 
| 506 |  |  |  |  |  |  |  | 
| 507 | 1 |  |  |  |  | 78 | return; | 
| 508 |  |  |  |  |  |  | } | 
| 509 |  |  |  |  |  |  |  | 
| 510 |  |  |  |  |  |  | =head2 stop_qmail() | 
| 511 |  |  |  |  |  |  |  | 
| 512 |  |  |  |  |  |  | Optionally stop the qmail daemon. | 
| 513 |  |  |  |  |  |  |  | 
| 514 |  |  |  |  |  |  | =cut | 
| 515 |  |  |  |  |  |  |  | 
| 516 |  |  |  |  |  |  | sub stop_qmail { | 
| 517 | 1 |  |  | 1 | 1 | 3529 | my $self = shift; | 
| 518 |  |  |  |  |  |  |  | 
| 519 |  |  |  |  |  |  | # Don't need to stop qmail if we're not planning to delete stuff | 
| 520 | 1 | 50 |  |  |  | 34 | return unless $self->deletions; | 
| 521 |  |  |  |  |  |  |  | 
| 522 |  |  |  |  |  |  | # If qmail is running, we stop it | 
| 523 | 0 | 0 |  |  |  | 0 | if ( my $qmpid = $self->qmail_pid ) { | 
| 524 |  |  |  |  |  |  |  | 
| 525 |  |  |  |  |  |  | # If there is a system script available, we use it | 
| 526 | 0 | 0 |  |  |  | 0 | if ( $self->commands->{stop} ne '' ) { | 
| 527 |  |  |  |  |  |  |  | 
| 528 | 0 |  |  |  |  | 0 | warn "Calling system script to terminate qmail...\n"; | 
| 529 | 0 | 0 |  |  |  | 0 | if ( system( $self->commands->{stop} ) > 0 ) { | 
| 530 | 0 |  |  |  |  | 0 | die 'Could not stop qmail'; | 
| 531 |  |  |  |  |  |  | } | 
| 532 | 0 |  |  |  |  | 0 | sleep 1 while $self->qmail_pid; | 
| 533 |  |  |  |  |  |  |  | 
| 534 |  |  |  |  |  |  | # Otherwise, we're killers! | 
| 535 |  |  |  |  |  |  | } | 
| 536 |  |  |  |  |  |  | else { | 
| 537 | 0 |  |  |  |  | 0 | warn "Terminating qmail (pid $qmpid)... ", | 
| 538 |  |  |  |  |  |  | "this might take a while if qmail is working.\n"; | 
| 539 | 0 |  |  |  |  | 0 | kill 'TERM', $qmpid; | 
| 540 |  |  |  |  |  |  |  | 
| 541 | 0 |  |  |  |  | 0 | sleep 1 while $self->qmail_pid; | 
| 542 |  |  |  |  |  |  | } | 
| 543 |  |  |  |  |  |  |  | 
| 544 |  |  |  |  |  |  | # If it isn't, we don't. We also return a false value so our caller | 
| 545 |  |  |  |  |  |  | # knows they might not want to restart it later. | 
| 546 |  |  |  |  |  |  | } | 
| 547 |  |  |  |  |  |  | else { | 
| 548 | 0 |  |  |  |  | 0 | warn "Qmail isn't running... no need to stop it.\n"; | 
| 549 | 0 |  |  |  |  | 0 | return; | 
| 550 |  |  |  |  |  |  | } | 
| 551 |  |  |  |  |  |  |  | 
| 552 | 0 |  |  |  |  | 0 | $self->restart(1); | 
| 553 |  |  |  |  |  |  |  | 
| 554 | 0 |  |  |  |  | 0 | return 1; | 
| 555 |  |  |  |  |  |  | } | 
| 556 |  |  |  |  |  |  |  | 
| 557 |  |  |  |  |  |  |  | 
| 558 |  |  |  |  |  |  | =head2 start_qmail() | 
| 559 |  |  |  |  |  |  |  | 
| 560 |  |  |  |  |  |  | Restart the qmail daemon if it was previously stopped. | 
| 561 |  |  |  |  |  |  |  | 
| 562 |  |  |  |  |  |  | =cut | 
| 563 |  |  |  |  |  |  |  | 
| 564 |  |  |  |  |  |  | sub start_qmail { | 
| 565 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 566 |  |  |  |  |  |  |  | 
| 567 | 0 | 0 |  |  |  | 0 | return unless $self->restart; | 
| 568 |  |  |  |  |  |  |  | 
| 569 |  |  |  |  |  |  | # If qmail is running, why restart it? | 
| 570 | 0 | 0 |  |  |  | 0 | if ( my $qmpid = $self->qmail_pid ) { | 
| 571 | 0 |  |  |  |  | 0 | warn "Qmail is already running again, so it won't be restarted.\n"; | 
| 572 | 0 |  |  |  |  | 0 | return 1; | 
| 573 |  |  |  |  |  |  | } | 
| 574 |  |  |  |  |  |  |  | 
| 575 |  |  |  |  |  |  | # In any other case, we restart it | 
| 576 | 0 |  |  |  |  | 0 | warn "Restarting qmail... \n"; | 
| 577 | 0 |  |  |  |  | 0 | system( $self->commands->{start} ); | 
| 578 | 0 |  |  |  |  | 0 | warn "Done (hopefully).\n"; | 
| 579 |  |  |  |  |  |  |  | 
| 580 | 0 |  |  |  |  | 0 | return 1; | 
| 581 |  |  |  |  |  |  | } | 
| 582 |  |  |  |  |  |  |  | 
| 583 |  |  |  |  |  |  | =head2 get_subject($msg_id) | 
| 584 |  |  |  |  |  |  |  | 
| 585 |  |  |  |  |  |  | Given the id of a message, return the subject of that message. | 
| 586 |  |  |  |  |  |  |  | 
| 587 |  |  |  |  |  |  | =cut | 
| 588 |  |  |  |  |  |  |  | 
| 589 |  |  |  |  |  |  | sub get_subject { | 
| 590 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 591 | 0 |  |  |  |  | 0 | my ($msg_id) = @_; | 
| 592 |  |  |  |  |  |  |  | 
| 593 | 0 |  |  |  |  | 0 | my $msgsub; | 
| 594 | 0 |  |  |  |  | 0 | my $queue = $self->queue; | 
| 595 | 0 | 0 |  |  |  | 0 | open( my $msg_fh, '<', "${queue}mess/$msg_id" ) | 
| 596 |  |  |  |  |  |  | or die("cannot open message $msg_id! Is qmail-send running?\n"); | 
| 597 | 0 |  |  |  |  | 0 | while (<$msg_fh>) { | 
| 598 | 0 |  |  |  |  | 0 | chomp; | 
| 599 | 0 | 0 |  |  |  | 0 | last if !/\S/; # End of headers | 
| 600 | 0 | 0 |  |  |  | 0 | if (/^Subject: (.*)/) { | 
| 601 | 0 |  |  |  |  | 0 | $msgsub = $1; | 
| 602 | 0 |  |  |  |  | 0 | last; | 
| 603 |  |  |  |  |  |  | } | 
| 604 |  |  |  |  |  |  | } | 
| 605 | 0 |  |  |  |  | 0 | close($msg_fh); | 
| 606 | 0 |  |  |  |  | 0 | return $msgsub; | 
| 607 |  |  |  |  |  |  | } | 
| 608 |  |  |  |  |  |  |  | 
| 609 |  |  |  |  |  |  | =head2 get_sender($msg_id) | 
| 610 |  |  |  |  |  |  |  | 
| 611 |  |  |  |  |  |  | Given the id of a message, return the sender of the message. | 
| 612 |  |  |  |  |  |  |  | 
| 613 |  |  |  |  |  |  | =cut | 
| 614 |  |  |  |  |  |  |  | 
| 615 |  |  |  |  |  |  | sub get_sender { | 
| 616 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 617 | 0 |  |  |  |  | 0 | my ($msg_id) = @_; | 
| 618 |  |  |  |  |  |  |  | 
| 619 | 0 |  |  |  |  | 0 | my $queue = $self->queue; | 
| 620 |  |  |  |  |  |  |  | 
| 621 | 0 | 0 |  |  |  | 0 | open( my $msg_fh, '<', "${queue}/info/$msg_id" ) | 
| 622 |  |  |  |  |  |  | or die( "cannot open info file ${queue}/info/$msg_id! ", | 
| 623 |  |  |  |  |  |  | "Is qmail-send running?\n" ); | 
| 624 | 0 |  |  |  |  | 0 | my $sender = <$msg_fh>; | 
| 625 | 0 |  |  |  |  | 0 | substr( $sender, 0, 1 ) = ''; | 
| 626 | 0 |  |  |  |  | 0 | chomp $sender; | 
| 627 | 0 |  |  |  |  | 0 | close($msg_fh); | 
| 628 | 0 |  |  |  |  | 0 | return $sender; | 
| 629 |  |  |  |  |  |  | } | 
| 630 |  |  |  |  |  |  |  | 
| 631 |  |  |  |  |  |  | =head2 send_msgs() | 
| 632 |  |  |  |  |  |  |  | 
| 633 |  |  |  |  |  |  | Attempt to send all currently queued messages. | 
| 634 |  |  |  |  |  |  |  | 
| 635 |  |  |  |  |  |  | It does this by sending SIGALRM to the qmail daemon. | 
| 636 |  |  |  |  |  |  |  | 
| 637 |  |  |  |  |  |  | =cut | 
| 638 |  |  |  |  |  |  |  | 
| 639 |  |  |  |  |  |  | sub send_msgs { | 
| 640 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 641 |  |  |  |  |  |  |  | 
| 642 |  |  |  |  |  |  | # If qmail is running, we force sending of messages | 
| 643 | 0 | 0 |  |  |  | 0 | if ( my $qmpid = $self->qmail_pid ) { | 
| 644 |  |  |  |  |  |  |  | 
| 645 | 0 |  |  |  |  | 0 | kill 'ALRM', $qmpid; | 
| 646 |  |  |  |  |  |  |  | 
| 647 |  |  |  |  |  |  | } | 
| 648 |  |  |  |  |  |  | else { | 
| 649 |  |  |  |  |  |  |  | 
| 650 | 0 |  |  |  |  | 0 | warn "Qmail isn't running, can't send messages!\n"; | 
| 651 |  |  |  |  |  |  |  | 
| 652 |  |  |  |  |  |  | } | 
| 653 | 0 |  |  |  |  | 0 | return; | 
| 654 |  |  |  |  |  |  | } | 
| 655 |  |  |  |  |  |  |  | 
| 656 |  |  |  |  |  |  | =head2 show_msg_info($msg_id) | 
| 657 |  |  |  |  |  |  |  | 
| 658 |  |  |  |  |  |  | Given a message id, display the information about that message. | 
| 659 |  |  |  |  |  |  |  | 
| 660 |  |  |  |  |  |  | =cut | 
| 661 |  |  |  |  |  |  |  | 
| 662 |  |  |  |  |  |  | sub show_msg_info { | 
| 663 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 664 | 0 |  |  |  |  | 0 | my ($msg_id) = @_; | 
| 665 |  |  |  |  |  |  |  | 
| 666 | 0 |  |  |  |  | 0 | my %msg; | 
| 667 | 0 |  |  |  |  | 0 | my $queue = $self->queue; | 
| 668 |  |  |  |  |  |  |  | 
| 669 | 0 |  |  |  |  | 0 | open( my $info_fh, '<', "${queue}info/$msg_id" ); | 
| 670 | 0 |  |  |  |  | 0 | $msg{ret} = <$info_fh>; | 
| 671 | 0 |  |  |  |  | 0 | substr( $msg{ret}, 0, 1 ) = ''; | 
| 672 | 0 |  |  |  |  | 0 | chomp $msg{ret}; | 
| 673 | 0 |  |  |  |  | 0 | close($info_fh); | 
| 674 | 0 |  |  |  |  | 0 | my ( $dirno, $rmsg ) = split( /\//, $msg_id ); | 
| 675 | 0 |  |  |  |  | 0 | print "$rmsg ($dirno, $msg_id)\n"; | 
| 676 |  |  |  |  |  |  |  | 
| 677 |  |  |  |  |  |  | # Get message (file) size | 
| 678 | 0 |  |  |  |  | 0 | $msg{fsize} = ( stat("${queue}mess/$msg_id") )[7]; | 
| 679 |  |  |  |  |  |  |  | 
| 680 | 0 |  |  |  |  | 0 | my %header = ( | 
| 681 |  |  |  |  |  |  | Date    => 'date', | 
| 682 |  |  |  |  |  |  | From    => 'from', | 
| 683 |  |  |  |  |  |  | Subject => 'subject', | 
| 684 |  |  |  |  |  |  | To      => 'to', | 
| 685 |  |  |  |  |  |  | Cc      => 'cc', | 
| 686 |  |  |  |  |  |  | ); | 
| 687 |  |  |  |  |  |  |  | 
| 688 |  |  |  |  |  |  | # Read something from message header (sender, receiver, subject, date) | 
| 689 | 0 |  |  |  |  | 0 | open( my $msg_fh, '<', "${queue}mess/$msg_id" ); | 
| 690 | 0 |  |  |  |  | 0 | while (<$msg_fh>) { | 
| 691 | 0 |  |  |  |  | 0 | chomp; | 
| 692 |  |  |  |  |  |  | # Stop processing at the end of the headers | 
| 693 | 0 | 0 |  |  |  | 0 | last unless /\S/; | 
| 694 | 0 |  |  |  |  | 0 | foreach my $h ( keys %header ) { | 
| 695 | 0 | 0 |  |  |  | 0 | if (/^$h: (.*)/) { | 
| 696 | 0 |  |  |  |  | 0 | $msg{ $header{$h} } = $1; | 
| 697 | 0 |  |  |  |  | 0 | last; | 
| 698 |  |  |  |  |  |  | } | 
| 699 |  |  |  |  |  |  | } | 
| 700 |  |  |  |  |  |  | } | 
| 701 | 0 |  |  |  |  | 0 | close($msg_fh); | 
| 702 |  |  |  |  |  |  |  | 
| 703 |  |  |  |  |  |  | # Add "pseudo-headers" for output | 
| 704 | 0 |  |  |  |  | 0 | $header{'Return-path'} = 'ret'; | 
| 705 | 0 |  |  |  |  | 0 | $header{Size} = 'fsize'; | 
| 706 |  |  |  |  |  |  |  | 
| 707 | 0 |  |  |  |  | 0 | my $colours = $self->colours; | 
| 708 | 0 |  |  |  |  | 0 | my ( $cmsg, $cend ) = @{$colours}{qw[msg end]}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 709 |  |  |  |  |  |  |  | 
| 710 | 0 |  |  |  |  | 0 | for (qw[Return-path From To Cc Subject Date Size]) { | 
| 711 | 0 | 0 |  |  |  | 0 | next unless exists $msg{ $header{$_} }; | 
| 712 |  |  |  |  |  |  |  | 
| 713 | 0 |  |  |  |  | 0 | print "  ${cmsg}$_${cend}: $msg{$header{$_}}\n"; | 
| 714 |  |  |  |  |  |  | } | 
| 715 |  |  |  |  |  |  |  | 
| 716 | 0 |  |  |  |  | 0 | return; | 
| 717 |  |  |  |  |  |  | } | 
| 718 |  |  |  |  |  |  |  | 
| 719 |  |  |  |  |  |  | =head2 list_msg($queue) | 
| 720 |  |  |  |  |  |  |  | 
| 721 |  |  |  |  |  |  | Display information for all messages in a given queue. | 
| 722 |  |  |  |  |  |  |  | 
| 723 |  |  |  |  |  |  | The $queue parameter should be 'L' to display only local messages, 'R' | 
| 724 |  |  |  |  |  |  | to display only remote messages or anything else to display all messages. | 
| 725 |  |  |  |  |  |  |  | 
| 726 |  |  |  |  |  |  | =cut | 
| 727 |  |  |  |  |  |  |  | 
| 728 |  |  |  |  |  |  | sub list_msg { | 
| 729 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 730 | 0 |  |  |  |  | 0 | my ($q) = @_; | 
| 731 |  |  |  |  |  |  |  | 
| 732 | 0 |  |  |  |  | 0 | $q = uc $q; | 
| 733 |  |  |  |  |  |  |  | 
| 734 | 0 |  |  |  |  | 0 | my $local =  $q ne 'R'; | 
| 735 | 0 |  |  |  |  | 0 | my $remote = $q ne 'L'; | 
| 736 |  |  |  |  |  |  |  | 
| 737 | 0 |  |  |  |  | 0 | my $msglist = $self->msglist; | 
| 738 | 0 | 0 |  |  |  | 0 | if ( !$self->summary ) { | 
| 739 | 0 |  |  |  |  | 0 | for my $msg ( keys %$msglist ) { | 
| 740 | 0 | 0 | 0 |  |  | 0 | next if $local  and not $msglist->{$msg}{local}; | 
| 741 | 0 | 0 | 0 |  |  | 0 | next if $remote and not $msglist->{$msg}{remote}; | 
| 742 |  |  |  |  |  |  |  | 
| 743 | 0 |  |  |  |  | 0 | $self->show_msg_info($msg); | 
| 744 |  |  |  |  |  |  | } | 
| 745 |  |  |  |  |  |  | } | 
| 746 |  |  |  |  |  |  |  | 
| 747 | 0 |  |  |  |  | 0 | $self->stats; | 
| 748 | 0 |  |  |  |  | 0 | return; | 
| 749 |  |  |  |  |  |  | } | 
| 750 |  |  |  |  |  |  |  | 
| 751 |  |  |  |  |  |  | =head2 view_msg($msg_id) | 
| 752 |  |  |  |  |  |  |  | 
| 753 |  |  |  |  |  |  | View a message in the queue | 
| 754 |  |  |  |  |  |  |  | 
| 755 |  |  |  |  |  |  | =cut | 
| 756 |  |  |  |  |  |  |  | 
| 757 |  |  |  |  |  |  | sub view_msg { | 
| 758 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 759 | 0 |  |  |  |  | 0 | my ($msg_id) = @_; | 
| 760 |  |  |  |  |  |  |  | 
| 761 | 0 | 0 |  |  |  | 0 | if ( $msg_id =~ /\D/ ) { | 
| 762 | 0 |  |  |  |  | 0 | warn "$msg_id is not a valid message number!\n"; | 
| 763 | 0 |  |  |  |  | 0 | return; | 
| 764 |  |  |  |  |  |  | } | 
| 765 |  |  |  |  |  |  |  | 
| 766 |  |  |  |  |  |  | # Search message | 
| 767 | 0 |  |  |  |  | 0 | my $ok    = 0; | 
| 768 | 0 |  |  |  |  | 0 | my $queue = $self->queue; | 
| 769 | 0 |  |  |  |  | 0 | for my $msg ( keys %{ $self->msglist } ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 770 | 0 | 0 |  |  |  | 0 | if ( $msg =~ /\/$msg_id$/ ) { | 
| 771 | 0 |  |  |  |  | 0 | $ok = 1; | 
| 772 | 0 |  |  |  |  | 0 | print "\n --------------\nMESSAGE NUMBER $msg_id \n --------------\n"; | 
| 773 | 0 |  |  |  |  | 0 | open( my $msg_fh, '<', "${queue}mess/$msg" ); | 
| 774 | 0 |  |  |  |  | 0 | print while <$msg_fh>; | 
| 775 | 0 |  |  |  |  | 0 | close($msg_fh); | 
| 776 | 0 |  |  |  |  | 0 | last; | 
| 777 |  |  |  |  |  |  | } | 
| 778 |  |  |  |  |  |  | } | 
| 779 |  |  |  |  |  |  |  | 
| 780 |  |  |  |  |  |  | # If the message isn't found, print a notice | 
| 781 | 0 | 0 |  |  |  | 0 | if ( !$ok ) { | 
| 782 | 0 |  |  |  |  | 0 | warn "Message $msg_id not found in the queue!\n"; | 
| 783 |  |  |  |  |  |  | } | 
| 784 |  |  |  |  |  |  |  | 
| 785 | 0 |  |  |  |  | 0 | return; | 
| 786 |  |  |  |  |  |  | } | 
| 787 |  |  |  |  |  |  |  | 
| 788 |  |  |  |  |  |  | =head2 trash_msgs() | 
| 789 |  |  |  |  |  |  |  | 
| 790 |  |  |  |  |  |  | Delete all of the messages whose ids are in the C<all_to_delete> | 
| 791 |  |  |  |  |  |  | array. | 
| 792 |  |  |  |  |  |  |  | 
| 793 |  |  |  |  |  |  | =cut | 
| 794 |  |  |  |  |  |  |  | 
| 795 |  |  |  |  |  |  | sub trash_msgs { | 
| 796 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 797 |  |  |  |  |  |  |  | 
| 798 | 0 |  |  |  |  | 0 | my $queue    = $self->queue; | 
| 799 | 0 |  |  |  |  | 0 | my $msglist  = $self->msglist; | 
| 800 | 0 |  |  |  |  | 0 | my @todelete = (); | 
| 801 | 0 |  |  |  |  | 0 | my $grouped  = 0; | 
| 802 | 0 |  |  |  |  | 0 | my $deleted  = 0; | 
| 803 | 0 |  |  |  |  | 0 | foreach my $msg ( $self->all_to_delete ) { | 
| 804 | 0 |  |  |  |  | 0 | $grouped++; | 
| 805 | 0 |  |  |  |  | 0 | $deleted++; | 
| 806 | 0 |  |  |  |  | 0 | my $msgno = ( split( /\//, $msg ) )[1]; | 
| 807 | 0 | 0 |  |  |  | 0 | if ( $msglist->{$msg}{bounce} ) { | 
| 808 | 0 |  |  |  |  | 0 | push @todelete, "${queue}bounce/$msgno"; | 
| 809 |  |  |  |  |  |  | } | 
| 810 | 0 |  |  |  |  | 0 | push @todelete, "${queue}mess/$msg", "${queue}info/$msg"; | 
| 811 | 0 | 0 |  |  |  | 0 | if ( $msglist->{$msg}{remote} ) { | 
| 812 | 0 |  |  |  |  | 0 | push @todelete, "${queue}remote/$msg"; | 
| 813 |  |  |  |  |  |  | } | 
| 814 | 0 | 0 |  |  |  | 0 | if ( $msglist->{$msg}{local} ) { | 
| 815 | 0 |  |  |  |  | 0 | push @todelete, "${queue}local/$msg"; | 
| 816 |  |  |  |  |  |  | } | 
| 817 | 0 | 0 |  |  |  | 0 | if ( $msglist->{$msg}{todo} ) { | 
| 818 | 0 |  |  |  |  | 0 | push @todelete, "${queue}todo/$msglist->{$msg}{'todo'}", | 
| 819 |  |  |  |  |  |  | "${queue}intd/$msglist->{$msg}{'todo'}"; | 
| 820 |  |  |  |  |  |  | } | 
| 821 | 0 | 0 |  |  |  | 0 | if ( $grouped == 11 ) { | 
| 822 | 0 |  |  |  |  | 0 | unlink @todelete; | 
| 823 | 0 |  |  |  |  | 0 | @todelete = (); | 
| 824 | 0 |  |  |  |  | 0 | $grouped  = 0; | 
| 825 |  |  |  |  |  |  | } | 
| 826 |  |  |  |  |  |  | } | 
| 827 | 0 | 0 |  |  |  | 0 | if ($grouped) { | 
| 828 | 0 |  |  |  |  | 0 | unlink @todelete; | 
| 829 |  |  |  |  |  |  | } | 
| 830 | 0 | 0 |  |  |  | 0 | my $msg_str = $deleted == 1 ? 'message' : 'messages'; | 
| 831 | 0 |  |  |  |  | 0 | warn "Deleted $deleted $msg_str from queue\n"; | 
| 832 | 0 |  |  |  |  | 0 | return; | 
| 833 |  |  |  |  |  |  | } | 
| 834 |  |  |  |  |  |  |  | 
| 835 |  |  |  |  |  |  | =head2 flag_msgs() | 
| 836 |  |  |  |  |  |  |  | 
| 837 |  |  |  |  |  |  | Flag all messages whose ids are in the C<all_to_flag> array. | 
| 838 |  |  |  |  |  |  |  | 
| 839 |  |  |  |  |  |  | =cut | 
| 840 |  |  |  |  |  |  |  | 
| 841 |  |  |  |  |  |  | sub flag_msgs { | 
| 842 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 843 |  |  |  |  |  |  |  | 
| 844 | 0 |  |  |  |  | 0 | my $queue     = $self->queue; | 
| 845 | 0 |  |  |  |  | 0 | my $now       = time; | 
| 846 | 0 |  |  |  |  | 0 | my @flagqueue = (); | 
| 847 | 0 |  |  |  |  | 0 | my $flagged   = 0; | 
| 848 | 0 |  |  |  |  | 0 | foreach my $msg ( $self->all_to_flag ) { | 
| 849 | 0 |  |  |  |  | 0 | push @flagqueue, "${queue}info/$msg"; | 
| 850 | 0 |  |  |  |  | 0 | $flagged++; | 
| 851 | 0 | 0 |  |  |  | 0 | if ( $flagged == 30 ) { | 
| 852 | 0 |  |  |  |  | 0 | utime $now, $now, @flagqueue; | 
| 853 | 0 |  |  |  |  | 0 | $flagged   = 0; | 
| 854 | 0 |  |  |  |  | 0 | @flagqueue = (); | 
| 855 |  |  |  |  |  |  | } | 
| 856 |  |  |  |  |  |  | } | 
| 857 | 0 | 0 |  |  |  | 0 | if ($flagged) { | 
| 858 | 0 |  |  |  |  | 0 | utime $now, $now, @flagqueue; | 
| 859 |  |  |  |  |  |  | } | 
| 860 | 0 |  |  |  |  | 0 | return; | 
| 861 |  |  |  |  |  |  | } | 
| 862 |  |  |  |  |  |  |  | 
| 863 |  |  |  |  |  |  | =head2 del_msg($msg_id) | 
| 864 |  |  |  |  |  |  |  | 
| 865 |  |  |  |  |  |  | Given a message id, add that message to the list of messages to delete. | 
| 866 |  |  |  |  |  |  |  | 
| 867 |  |  |  |  |  |  | The actual deletion is carried out by C<trash_msgs>. | 
| 868 |  |  |  |  |  |  |  | 
| 869 |  |  |  |  |  |  | =cut | 
| 870 |  |  |  |  |  |  |  | 
| 871 |  |  |  |  |  |  | sub del_msg { | 
| 872 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 873 | 0 |  |  |  |  | 0 | my ($msg_id) = @_; | 
| 874 |  |  |  |  |  |  |  | 
| 875 | 0 | 0 |  |  |  | 0 | if ( $msg_id =~ /\D/ ) { | 
| 876 | 0 |  |  |  |  | 0 | warn "$msg_id is not a valid message number!\n"; | 
| 877 | 0 |  |  |  |  | 0 | return; | 
| 878 |  |  |  |  |  |  | } | 
| 879 |  |  |  |  |  |  |  | 
| 880 |  |  |  |  |  |  | # Search message | 
| 881 | 0 |  |  |  |  | 0 | my $ok = 0; | 
| 882 | 0 |  |  |  |  | 0 | for my $msg ( keys %{ $self->msglist } ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 883 | 0 | 0 |  |  |  | 0 | if ( $msg =~ /\/$msg_id$/ ) { | 
| 884 | 0 |  |  |  |  | 0 | $ok = 1; | 
| 885 | 0 |  |  |  |  | 0 | $self->add_to_delete($msg); | 
| 886 | 0 |  |  |  |  | 0 | last; | 
| 887 |  |  |  |  |  |  | } | 
| 888 |  |  |  |  |  |  | } | 
| 889 |  |  |  |  |  |  |  | 
| 890 |  |  |  |  |  |  | # If the message isn't found, print a notice | 
| 891 | 0 | 0 |  |  |  | 0 | if ( !$ok ) { | 
| 892 | 0 |  |  |  |  | 0 | warn "Message $msg_id not found in the queue!\n"; | 
| 893 |  |  |  |  |  |  | } | 
| 894 |  |  |  |  |  |  |  | 
| 895 | 0 |  |  |  |  | 0 | return; | 
| 896 |  |  |  |  |  |  | } | 
| 897 |  |  |  |  |  |  |  | 
| 898 |  |  |  |  |  |  | =head2 del_msg_from_sender($sender) | 
| 899 |  |  |  |  |  |  |  | 
| 900 |  |  |  |  |  |  | Given a sender's email address, add all messages from that sender to the | 
| 901 |  |  |  |  |  |  | list of messages to delete. | 
| 902 |  |  |  |  |  |  |  | 
| 903 |  |  |  |  |  |  | The actual deletion is carried out by C<trash_msgs>. | 
| 904 |  |  |  |  |  |  |  | 
| 905 |  |  |  |  |  |  | =cut | 
| 906 |  |  |  |  |  |  |  | 
| 907 |  |  |  |  |  |  | sub del_msg_from_sender { | 
| 908 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 909 | 0 |  |  |  |  | 0 | my ($sender) = @_; | 
| 910 |  |  |  |  |  |  |  | 
| 911 | 0 |  |  |  |  | 0 | warn "Looking for messages from $sender\n"; | 
| 912 |  |  |  |  |  |  |  | 
| 913 | 0 |  |  |  |  | 0 | my $ok = 0; | 
| 914 | 0 |  |  |  |  | 0 | for my $msg ( keys %{ $self->msglist } ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 915 | 0 | 0 |  |  |  | 0 | if ( $self->msglist->{$msg}{sender} ) { | 
| 916 | 0 |  |  |  |  | 0 | my $msg_sender = $self->get_sender($msg); | 
| 917 | 0 | 0 |  |  |  | 0 | if ( $msg_sender eq $sender ) { | 
| 918 | 0 |  |  |  |  | 0 | $ok = 1; | 
| 919 | 0 |  |  |  |  | 0 | $self->add_to_delete($msg); | 
| 920 |  |  |  |  |  |  | } | 
| 921 |  |  |  |  |  |  | } | 
| 922 |  |  |  |  |  |  | } | 
| 923 |  |  |  |  |  |  |  | 
| 924 |  |  |  |  |  |  | # If no messages are found, print a notice | 
| 925 | 0 | 0 |  |  |  | 0 | if ( !$ok ) { | 
| 926 | 0 |  |  |  |  | 0 | warn "No messages from $sender found in the queue!\n"; | 
| 927 |  |  |  |  |  |  | } | 
| 928 |  |  |  |  |  |  |  | 
| 929 | 0 |  |  |  |  | 0 | return; | 
| 930 |  |  |  |  |  |  | } | 
| 931 |  |  |  |  |  |  |  | 
| 932 |  |  |  |  |  |  | =head2 del_msg_from_sender_r($sender) | 
| 933 |  |  |  |  |  |  |  | 
| 934 |  |  |  |  |  |  | Given a sender's email address, add all messages from that sender to the | 
| 935 |  |  |  |  |  |  | list of messages to delete. | 
| 936 |  |  |  |  |  |  |  | 
| 937 |  |  |  |  |  |  | This method treats $sender as a regex. | 
| 938 |  |  |  |  |  |  |  | 
| 939 |  |  |  |  |  |  | The actual deletion is carried out by C<trash_msgs>. | 
| 940 |  |  |  |  |  |  |  | 
| 941 |  |  |  |  |  |  | =cut | 
| 942 |  |  |  |  |  |  |  | 
| 943 |  |  |  |  |  |  | sub del_msg_from_sender_r { | 
| 944 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 945 | 0 |  |  |  |  | 0 | my ($sender_re) = @_; | 
| 946 |  |  |  |  |  |  |  | 
| 947 | 0 |  |  |  |  | 0 | warn "Looking for messages from senders matching $sender_re\n"; | 
| 948 |  |  |  |  |  |  |  | 
| 949 | 0 |  |  |  |  | 0 | my $ok = 0; | 
| 950 | 0 |  |  |  |  | 0 | for my $msg ( keys %{ $self->msglist } ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 951 | 0 | 0 |  |  |  | 0 | if ( $self->msglist->{$msg}{sender} ) { | 
| 952 | 0 |  |  |  |  | 0 | my $msg_sender = $self->get_sender($msg); | 
| 953 | 0 | 0 |  |  |  | 0 | if ( $msg_sender =~ /$sender_re/ ) { | 
| 954 | 0 |  |  |  |  | 0 | $ok = 1; | 
| 955 | 0 |  |  |  |  | 0 | $self->add_to_delete($msg); | 
| 956 |  |  |  |  |  |  | } | 
| 957 |  |  |  |  |  |  | } | 
| 958 |  |  |  |  |  |  | } | 
| 959 |  |  |  |  |  |  |  | 
| 960 |  |  |  |  |  |  | # If no messages are found, print a notice | 
| 961 | 0 | 0 |  |  |  | 0 | if ( !$ok ) { | 
| 962 | 0 |  |  |  |  | 0 | warn "No messages from senders matching ", | 
| 963 |  |  |  |  |  |  | "$sender_re found in the queue!\n"; | 
| 964 |  |  |  |  |  |  | } | 
| 965 |  |  |  |  |  |  |  | 
| 966 | 0 |  |  |  |  | 0 | return; | 
| 967 |  |  |  |  |  |  | } | 
| 968 |  |  |  |  |  |  |  | 
| 969 |  |  |  |  |  |  | =head2 del_msg_header($header_re, $is_case_sensitive) | 
| 970 |  |  |  |  |  |  |  | 
| 971 |  |  |  |  |  |  | Given a regex, add all messages with headers that match the regex to the | 
| 972 |  |  |  |  |  |  | list of messages to delete. | 
| 973 |  |  |  |  |  |  |  | 
| 974 |  |  |  |  |  |  | The actual deletion is carried out by C<trash_msgs>. | 
| 975 |  |  |  |  |  |  |  | 
| 976 |  |  |  |  |  |  | =cut | 
| 977 |  |  |  |  |  |  |  | 
| 978 |  |  |  |  |  |  | sub del_msg_header_r { | 
| 979 | 0 |  |  | 0 | 0 | 0 | my $self = shift; | 
| 980 | 0 |  |  |  |  | 0 | my ( $header_re, $is_case_sensitive ) = @_; | 
| 981 |  |  |  |  |  |  |  | 
| 982 | 0 |  |  |  |  | 0 | warn "Looking for messages with headers matching $header_re\n"; | 
| 983 |  |  |  |  |  |  |  | 
| 984 | 0 | 0 |  |  |  | 0 | $header_re = "(?i)$header_re" if $is_case_sensitive; | 
| 985 |  |  |  |  |  |  |  | 
| 986 | 0 |  |  |  |  | 0 | my $queue = $self->queue; | 
| 987 | 0 |  |  |  |  | 0 | my $ok    = 0; | 
| 988 | 0 |  |  |  |  | 0 | for my $msg ( keys %{ $self->msglist } ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 989 | 0 | 0 |  |  |  | 0 | open( my $msg_fh, '<', "${queue}mess/$msg" ) | 
| 990 |  |  |  |  |  |  | or die("cannot open message $msg! Is qmail-send running?\n"); | 
| 991 | 0 |  |  |  |  | 0 | while (<$msg_fh>) { | 
| 992 | 0 |  |  |  |  | 0 | chomp; | 
| 993 | 0 | 0 |  |  |  | 0 | last if ! /\S/; # End of headers | 
| 994 | 0 | 0 |  |  |  | 0 | if (/$header_re/) { | 
| 995 | 0 |  |  |  |  | 0 | $ok = 1; | 
| 996 | 0 |  |  |  |  | 0 | $self->add_to_delete($msg); | 
| 997 | 0 |  |  |  |  | 0 | last; | 
| 998 |  |  |  |  |  |  | } | 
| 999 |  |  |  |  |  |  | } | 
| 1000 | 0 |  |  |  |  | 0 | close($msg_fh); | 
| 1001 |  |  |  |  |  |  |  | 
| 1002 |  |  |  |  |  |  | } | 
| 1003 |  |  |  |  |  |  |  | 
| 1004 |  |  |  |  |  |  | # If no messages are found, print a notice | 
| 1005 | 0 | 0 |  |  |  | 0 | if ( !$ok ) { | 
| 1006 | 0 |  |  |  |  | 0 | warn "No messages with headers matching $header_re ", | 
| 1007 |  |  |  |  |  |  | "found in the queue!\n"; | 
| 1008 |  |  |  |  |  |  | } | 
| 1009 |  |  |  |  |  |  |  | 
| 1010 | 0 |  |  |  |  | 0 | return; | 
| 1011 |  |  |  |  |  |  | } | 
| 1012 |  |  |  |  |  |  |  | 
| 1013 |  |  |  |  |  |  | =head2 del_msg_body_r($body_re, $is_case_sensitive) | 
| 1014 |  |  |  |  |  |  |  | 
| 1015 |  |  |  |  |  |  | Given a regex, add all messages with a body that matches the regex to the | 
| 1016 |  |  |  |  |  |  | list of messages to delete. | 
| 1017 |  |  |  |  |  |  |  | 
| 1018 |  |  |  |  |  |  | The actual deletion is carried out by C<trash_msgs>. | 
| 1019 |  |  |  |  |  |  |  | 
| 1020 |  |  |  |  |  |  | =cut | 
| 1021 |  |  |  |  |  |  |  | 
| 1022 |  |  |  |  |  |  | sub del_msg_body_r { | 
| 1023 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 1024 | 0 |  |  |  |  | 0 | my ( $body_re, $is_case_sensitive ) = @_; | 
| 1025 |  |  |  |  |  |  |  | 
| 1026 | 0 |  |  |  |  | 0 | my $queue = $self->queue; | 
| 1027 |  |  |  |  |  |  |  | 
| 1028 | 0 |  |  |  |  | 0 | warn "Looking for messages with body matching $body_re\n"; | 
| 1029 |  |  |  |  |  |  |  | 
| 1030 | 0 | 0 |  |  |  | 0 | $body_re = "(?i)$body_re" if $is_case_sensitive; | 
| 1031 |  |  |  |  |  |  |  | 
| 1032 | 0 |  |  |  |  | 0 | my $ok = 0; | 
| 1033 | 0 |  |  |  |  | 0 | for my $msg ( keys %{ $self->msglist } ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 1034 | 0 | 0 |  |  |  | 0 | open( my $msg_fh, '<', "${queue}mess/$msg" ) | 
| 1035 |  |  |  |  |  |  | or die("cannot open message $msg! Is qmail-send running?\n"); | 
| 1036 |  |  |  |  |  |  | # Skip headers | 
| 1037 | 0 |  |  |  |  | 0 | while (<$msg_fh>) { | 
| 1038 | 0 |  |  |  |  | 0 | chomp; | 
| 1039 | 0 | 0 |  |  |  | 0 | last if !/\S/; | 
| 1040 |  |  |  |  |  |  | } | 
| 1041 | 0 |  |  |  |  | 0 | while (<$msg_fh>) { | 
| 1042 | 0 | 0 |  |  |  | 0 | if (/$body_re/) { | 
| 1043 | 0 |  |  |  |  | 0 | $ok = 1; | 
| 1044 | 0 |  |  |  |  | 0 | $self->add_to_delete($msg); | 
| 1045 | 0 |  |  |  |  | 0 | last; | 
| 1046 |  |  |  |  |  |  | } | 
| 1047 |  |  |  |  |  |  | } | 
| 1048 | 0 |  |  |  |  | 0 | close($msg_fh); | 
| 1049 |  |  |  |  |  |  | } | 
| 1050 |  |  |  |  |  |  |  | 
| 1051 |  |  |  |  |  |  | # If no messages are found, print a notice | 
| 1052 | 0 | 0 |  |  |  | 0 | if ( !$ok ) { | 
| 1053 | 0 |  |  |  |  | 0 | warn "No messages with body matching $body_re found in the queue!\n"; | 
| 1054 |  |  |  |  |  |  | } | 
| 1055 |  |  |  |  |  |  |  | 
| 1056 | 0 |  |  |  |  | 0 | return; | 
| 1057 |  |  |  |  |  |  | } | 
| 1058 |  |  |  |  |  |  |  | 
| 1059 |  |  |  |  |  |  | =head2 del_msg_subj($subject, $is_case_sensitive) | 
| 1060 |  |  |  |  |  |  |  | 
| 1061 |  |  |  |  |  |  | Given a subject, add all messages with that subject to the list of messages | 
| 1062 |  |  |  |  |  |  | to delete. | 
| 1063 |  |  |  |  |  |  |  | 
| 1064 |  |  |  |  |  |  | The actual deletion is carried out by C<trash_msgs>. | 
| 1065 |  |  |  |  |  |  |  | 
| 1066 |  |  |  |  |  |  | =cut | 
| 1067 |  |  |  |  |  |  |  | 
| 1068 |  |  |  |  |  |  | sub del_msg_subj { | 
| 1069 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 1070 | 0 |  |  |  |  | 0 | my ($subject) = @_; | 
| 1071 |  |  |  |  |  |  |  | 
| 1072 | 0 |  |  |  |  | 0 | warn "Looking for messages with Subject: $subject\n"; | 
| 1073 |  |  |  |  |  |  |  | 
| 1074 |  |  |  |  |  |  | # Search messages | 
| 1075 | 0 |  |  |  |  | 0 | my $ok = 0; | 
| 1076 | 0 |  |  |  |  | 0 | for my $msg ( keys %{ $self->msglist } ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 1077 | 0 |  |  |  |  | 0 | my $msgsub = $self->get_subject($msg); | 
| 1078 |  |  |  |  |  |  |  | 
| 1079 | 0 | 0 | 0 |  |  | 0 | if ( $msgsub and $msgsub =~ /$subject/ ) { | 
| 1080 | 0 |  |  |  |  | 0 | $ok = 1; | 
| 1081 | 0 |  |  |  |  | 0 | $self->add_to_delete($msg); | 
| 1082 |  |  |  |  |  |  | } | 
| 1083 |  |  |  |  |  |  |  | 
| 1084 |  |  |  |  |  |  | } | 
| 1085 |  |  |  |  |  |  |  | 
| 1086 |  |  |  |  |  |  | # If no messages are found, print a notice | 
| 1087 | 0 | 0 |  |  |  | 0 | if ( !$ok ) { | 
| 1088 | 0 |  |  |  |  | 0 | warn "No messages matching Subject \"$subject\" found in the queue!\n"; | 
| 1089 |  |  |  |  |  |  | } | 
| 1090 |  |  |  |  |  |  |  | 
| 1091 | 0 |  |  |  |  | 0 | return; | 
| 1092 |  |  |  |  |  |  | } | 
| 1093 |  |  |  |  |  |  |  | 
| 1094 |  |  |  |  |  |  | =head2 del_all() | 
| 1095 |  |  |  |  |  |  |  | 
| 1096 |  |  |  |  |  |  | Delete all messages in the queue. | 
| 1097 |  |  |  |  |  |  |  | 
| 1098 |  |  |  |  |  |  | The actual deletion is carried out by C<trash_msgs>. | 
| 1099 |  |  |  |  |  |  |  | 
| 1100 |  |  |  |  |  |  | =cut | 
| 1101 |  |  |  |  |  |  |  | 
| 1102 |  |  |  |  |  |  | sub del_all { | 
| 1103 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 1104 |  |  |  |  |  |  |  | 
| 1105 |  |  |  |  |  |  | # Search messages | 
| 1106 | 0 |  |  |  |  | 0 | my $ok = 0; | 
| 1107 | 0 |  |  |  |  | 0 | for my $msg ( keys %{ $self->msglist } ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 1108 | 0 |  |  |  |  | 0 | $ok = 1; | 
| 1109 | 0 |  |  |  |  | 0 | $self->add_to_delete($msg); | 
| 1110 |  |  |  |  |  |  | } | 
| 1111 |  |  |  |  |  |  |  | 
| 1112 |  |  |  |  |  |  | # If no messages are found, print a notice | 
| 1113 | 0 | 0 |  |  |  | 0 | if ( !$ok ) { | 
| 1114 | 0 |  |  |  |  | 0 | warn "No messages found in the queue!\n"; | 
| 1115 |  |  |  |  |  |  | } | 
| 1116 |  |  |  |  |  |  |  | 
| 1117 | 0 |  |  |  |  | 0 | return; | 
| 1118 |  |  |  |  |  |  | } | 
| 1119 |  |  |  |  |  |  |  | 
| 1120 |  |  |  |  |  |  | =head2 flag_remote($recipient_re) | 
| 1121 |  |  |  |  |  |  |  | 
| 1122 |  |  |  |  |  |  | Flag all remote messages whose recipient matches the given regex. | 
| 1123 |  |  |  |  |  |  |  | 
| 1124 |  |  |  |  |  |  | =cut | 
| 1125 |  |  |  |  |  |  |  | 
| 1126 |  |  |  |  |  |  | sub flag_remote { | 
| 1127 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 1128 | 0 |  |  |  |  | 0 | my ($recipient_re) = @_; | 
| 1129 |  |  |  |  |  |  |  | 
| 1130 | 0 |  |  |  |  | 0 | my $queue = $self->queue; | 
| 1131 |  |  |  |  |  |  |  | 
| 1132 | 0 |  |  |  |  | 0 | warn "Looking for messages with recipients in $recipient_re\n"; | 
| 1133 |  |  |  |  |  |  |  | 
| 1134 | 0 |  |  |  |  | 0 | my $ok = 0; | 
| 1135 | 0 |  |  |  |  | 0 | for my $msg ( keys %{ $self->msglist } ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 1136 | 0 | 0 |  |  |  | 0 | if ( $self->msglist->{$msg}{remote} ) { | 
| 1137 | 0 | 0 |  |  |  | 0 | open( my $msg_fh, '<', "${queue}remote/$msg" ) | 
| 1138 |  |  |  |  |  |  | or die( "cannot open remote file for message $msg! ", | 
| 1139 |  |  |  |  |  |  | "Is qmail-send running?\n" ); | 
| 1140 | 0 |  |  |  |  | 0 | my $recipients = <$msg_fh>; | 
| 1141 | 0 |  |  |  |  | 0 | chomp($recipients); | 
| 1142 | 0 |  |  |  |  | 0 | close($msg_fh); | 
| 1143 | 0 | 0 |  |  |  | 0 | if ( $recipients =~ /$recipient_re/ ) { | 
| 1144 | 0 |  |  |  |  | 0 | $ok = 1; | 
| 1145 | 0 |  |  |  |  | 0 | $self->add_to_flag($msg); | 
| 1146 | 0 |  |  |  |  | 0 | warn "Message $msg being tagged for earlier retry ", | 
| 1147 |  |  |  |  |  |  | "(and lengthened stay in queue)!\n"; | 
| 1148 |  |  |  |  |  |  | } | 
| 1149 |  |  |  |  |  |  | } | 
| 1150 |  |  |  |  |  |  | } | 
| 1151 |  |  |  |  |  |  |  | 
| 1152 |  |  |  |  |  |  | # If no messages are found, print a notice | 
| 1153 | 0 | 0 |  |  |  | 0 | if ( !$ok ) { | 
| 1154 | 0 |  |  |  |  | 0 | warn "No messages with recipients in $recipient_re ", | 
| 1155 |  |  |  |  |  |  | "found in the queue!\n"; | 
| 1156 | 0 |  |  |  |  | 0 | return; | 
| 1157 |  |  |  |  |  |  | } | 
| 1158 |  |  |  |  |  |  |  | 
| 1159 | 0 |  |  |  |  | 0 | $self->flag_msgs; | 
| 1160 |  |  |  |  |  |  |  | 
| 1161 | 0 |  |  |  |  | 0 | return; | 
| 1162 |  |  |  |  |  |  | } | 
| 1163 |  |  |  |  |  |  |  | 
| 1164 |  |  |  |  |  |  | =head2 stats() | 
| 1165 |  |  |  |  |  |  |  | 
| 1166 |  |  |  |  |  |  | Display statistics about the queue. | 
| 1167 |  |  |  |  |  |  |  | 
| 1168 |  |  |  |  |  |  | =cut | 
| 1169 |  |  |  |  |  |  |  | 
| 1170 |  |  |  |  |  |  | sub stats { | 
| 1171 | 0 |  |  | 0 | 1 | 0 | my $self = shift; | 
| 1172 |  |  |  |  |  |  |  | 
| 1173 | 0 |  |  |  |  | 0 | my $total = 0; | 
| 1174 | 0 |  |  |  |  | 0 | my $l     = 0; | 
| 1175 | 0 |  |  |  |  | 0 | my $r     = 0; | 
| 1176 | 0 |  |  |  |  | 0 | my $b     = 0; | 
| 1177 | 0 |  |  |  |  | 0 | my $t     = 0; | 
| 1178 |  |  |  |  |  |  |  | 
| 1179 | 0 |  |  |  |  | 0 | foreach my $msg ( keys %{ $self->msglist } ) { | 
|  | 0 |  |  |  |  | 0 |  | 
| 1180 | 0 |  |  |  |  | 0 | $total++; | 
| 1181 | 0 | 0 |  |  |  | 0 | $self->msglist->{$msg}{local}  && $l++; | 
| 1182 | 0 | 0 |  |  |  | 0 | $self->msglist->{$msg}{remote} && $r++; | 
| 1183 | 0 | 0 |  |  |  | 0 | $self->msglist->{$msg}{bounce} && $b++; | 
| 1184 | 0 | 0 |  |  |  | 0 | $self->msglist->{$msg}{todo}   && $t++; | 
| 1185 |  |  |  |  |  |  | } | 
| 1186 |  |  |  |  |  |  |  | 
| 1187 | 0 |  |  |  |  | 0 | my $colours = $self->colours; | 
| 1188 | 0 |  |  |  |  | 0 | my ( $cstat, $cend ) = @{$colours}{qw[stat end]}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1189 |  |  |  |  |  |  |  | 
| 1190 | 0 |  |  |  |  | 0 | print <<"END_OF_STATS"; | 
| 1191 |  |  |  |  |  |  | ${cstat}Total messages${cend}: $total | 
| 1192 |  |  |  |  |  |  | ${cstat}Messages with local recipients${cend}: $l | 
| 1193 |  |  |  |  |  |  | ${cstat}Messages with remote recipients${cend}: $r | 
| 1194 |  |  |  |  |  |  | ${cstat}Messages with bounces${cend}: $b | 
| 1195 |  |  |  |  |  |  | ${cstat}Messages in preprocess${cend}: $t | 
| 1196 |  |  |  |  |  |  | END_OF_STATS | 
| 1197 | 0 |  |  |  |  | 0 | return; | 
| 1198 |  |  |  |  |  |  | } | 
| 1199 |  |  |  |  |  |  |  | 
| 1200 |  |  |  |  |  |  | =head2 qmail_pid() | 
| 1201 |  |  |  |  |  |  |  | 
| 1202 |  |  |  |  |  |  | Get the pid of the qmail daemon | 
| 1203 |  |  |  |  |  |  |  | 
| 1204 |  |  |  |  |  |  | =cut | 
| 1205 |  |  |  |  |  |  |  | 
| 1206 |  |  |  |  |  |  | sub qmail_pid { | 
| 1207 | 0 |  |  | 0 | 1 | 0 | my $self   = shift; | 
| 1208 | 0 |  |  |  |  | 0 | my $pidcmd = $self->commands->{pid}; | 
| 1209 | 0 |  |  |  |  | 0 | my $qmpid  = `$pidcmd`; | 
| 1210 | 0 | 0 |  |  |  | 0 | return 0 unless $qmpid; | 
| 1211 | 0 |  |  |  |  | 0 | chomp($qmpid); | 
| 1212 | 0 |  |  |  |  | 0 | $qmpid =~ s/\s+//g; | 
| 1213 | 0 | 0 |  |  |  | 0 | return 0 if $qmpid =~ /\D/; | 
| 1214 | 0 |  |  |  |  | 0 | return $qmpid; | 
| 1215 |  |  |  |  |  |  | } | 
| 1216 |  |  |  |  |  |  |  | 
| 1217 |  |  |  |  |  |  | =head2 usage() | 
| 1218 |  |  |  |  |  |  |  | 
| 1219 |  |  |  |  |  |  | Display usage information. | 
| 1220 |  |  |  |  |  |  |  | 
| 1221 |  |  |  |  |  |  | =cut | 
| 1222 |  |  |  |  |  |  |  | 
| 1223 |  |  |  |  |  |  | sub usage { | 
| 1224 | 1 |  |  | 1 | 1 | 16 | print <<"END_OF_HELP"; | 
| 1225 |  |  |  |  |  |  | $me v$VERSION | 
| 1226 |  |  |  |  |  |  | Copyright (c) 2016 Dave Cross <dave\@perlhacks.com> | 
| 1227 |  |  |  |  |  |  | Based on original version by Michele Beltrame <mb\@italpro.net> | 
| 1228 |  |  |  |  |  |  |  | 
| 1229 |  |  |  |  |  |  | Available parameters: | 
| 1230 |  |  |  |  |  |  | -a       : try to send queued messages now (qmail must be running) | 
| 1231 |  |  |  |  |  |  | -l       : list message queues | 
| 1232 |  |  |  |  |  |  | -L       : list local message queue | 
| 1233 |  |  |  |  |  |  | -R       : list remote message queue | 
| 1234 |  |  |  |  |  |  | -s       : show some statistics | 
| 1235 |  |  |  |  |  |  | -mN      : display message number N | 
| 1236 |  |  |  |  |  |  | -dN      : delete message number N | 
| 1237 |  |  |  |  |  |  | -fsender : delete message from sender | 
| 1238 |  |  |  |  |  |  | -F're'   : delete message from senders matching regular expression re | 
| 1239 |  |  |  |  |  |  | -Stext   : delete all messages that have/contain text as Subject | 
| 1240 |  |  |  |  |  |  | -h're'   : delete all messages with headers matching regular expression | 
| 1241 |  |  |  |  |  |  | re (case insensitive) | 
| 1242 |  |  |  |  |  |  | -b're'   : delete all messages with body matching regular expression | 
| 1243 |  |  |  |  |  |  | re (case insensitive) | 
| 1244 |  |  |  |  |  |  | -H're'   : delete all messages with headers matching regular expression | 
| 1245 |  |  |  |  |  |  | re (case sensitive) | 
| 1246 |  |  |  |  |  |  | -B're'   : delete all messages with body matching regular expression | 
| 1247 |  |  |  |  |  |  | re (case sensitive) | 
| 1248 |  |  |  |  |  |  | -t're'   : flag messages with recipients in regular expression 're' for | 
| 1249 |  |  |  |  |  |  | earlier retry (note: this lengthens the time message can | 
| 1250 |  |  |  |  |  |  | stay in queue) | 
| 1251 |  |  |  |  |  |  | -D       : delete all messages in the queue (local and remote) | 
| 1252 |  |  |  |  |  |  | -V       : print program version | 
| 1253 |  |  |  |  |  |  | -?       : Display this help | 
| 1254 |  |  |  |  |  |  |  | 
| 1255 |  |  |  |  |  |  | Additional (optional) parameters: | 
| 1256 |  |  |  |  |  |  | -c       : display colored output | 
| 1257 |  |  |  |  |  |  | -N       : list message numbers only | 
| 1258 |  |  |  |  |  |  | (to be used either with -l, -L or -R) | 
| 1259 |  |  |  |  |  |  |  | 
| 1260 |  |  |  |  |  |  | You can view/delete multiple message i.e. -d123 -m456 -d567 | 
| 1261 |  |  |  |  |  |  |  | 
| 1262 |  |  |  |  |  |  | END_OF_HELP | 
| 1263 |  |  |  |  |  |  | } | 
| 1264 |  |  |  |  |  |  |  | 
| 1265 | 1 |  |  | 1 |  | 9 | no Moose; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 7 |  | 
| 1266 |  |  |  |  |  |  | __PACKAGE__->meta->make_immutable; | 
| 1267 |  |  |  |  |  |  |  | 
| 1268 |  |  |  |  |  |  | =head2 version() | 
| 1269 |  |  |  |  |  |  |  | 
| 1270 |  |  |  |  |  |  | Display the version. | 
| 1271 |  |  |  |  |  |  |  | 
| 1272 |  |  |  |  |  |  | =cut | 
| 1273 |  |  |  |  |  |  |  | 
| 1274 |  |  |  |  |  |  | sub version { | 
| 1275 | 0 |  |  | 0 | 1 |  | print "$me v$VERSION\n"; | 
| 1276 | 0 |  |  |  |  |  | return; | 
| 1277 |  |  |  |  |  |  | } | 
| 1278 |  |  |  |  |  |  |  | 
| 1279 |  |  |  |  |  |  | =head2 AUTHOR | 
| 1280 |  |  |  |  |  |  |  | 
| 1281 |  |  |  |  |  |  | Copyright (c) 2016 Dave Cross E<lt>dave@perlhacks.comE<gt> | 
| 1282 |  |  |  |  |  |  |  | 
| 1283 |  |  |  |  |  |  | Based on original version by Michele Beltrame E<lt>mb@italpro.netE<gt> | 
| 1284 |  |  |  |  |  |  |  | 
| 1285 |  |  |  |  |  |  | =head2 LICENCE | 
| 1286 |  |  |  |  |  |  |  | 
| 1287 |  |  |  |  |  |  | This program is distributed under the GNU GPL. | 
| 1288 |  |  |  |  |  |  | For more information have a look at http://www.gnu.org | 
| 1289 |  |  |  |  |  |  |  | 
| 1290 |  |  |  |  |  |  | =cut | 
| 1291 |  |  |  |  |  |  |  | 
| 1292 |  |  |  |  |  |  | 1; |