From roam@orbitel.bg  Thu Nov 23 13:58:58 2000
Return-Path: <roam@orbitel.bg>
Received: from ringworld.nanolink.com (ringworld.nanolink.com [195.24.48.13])
	by hub.freebsd.org (Postfix) with SMTP id 1F43D37B4C5
	for <FreeBSD-gnats-submit@freebsd.org>; Thu, 23 Nov 2000 13:58:54 -0800 (PST)
Received: (qmail 1380 invoked by uid 1000); 23 Nov 2000 21:58:26 -0000
Message-Id: <20001123215826.1379.qmail@ringworld.nanolink.com>
Date: 23 Nov 2000 21:58:26 -0000
From: Peter Pentchev <roam@orbitel.bg>
Reply-To: Peter Pentchev <roam@orbitel.bg>
To: FreeBSD-gnats-submit@freebsd.org
Subject: [PATCH] rmuser fails to remove at jobs
X-Send-Pr-Version: 3.2

>Number:         23052
>Category:       bin
>Synopsis:       [PATCH] rmuser fails to remove at jobs
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    ghelmer
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Thu Nov 23 14:00:01 PST 2000
>Closed-Date:    Mon Jul 23 13:29:32 CDT 2001
>Last-Modified:  Mon Jul 23 13:30:04 CDT 2001
>Originator:     Peter Pentchev
>Release:        FreeBSD 4.2-STABLE i386
>Organization:
Orbitel JSCo.
>Environment:

RELENG_3, RELENG_4, HEAD

>Description:

The /usr/sbin/rmuser Perl script includes a function for removing
the late user's at-scheduled jobs.  However, that code has been subtly
wrong from the start, and for quite some time now has simply not worked.

It scans the /var/at/jobs directory for files belonging to the user
being removed, and then passes each filename as an argument to atrm.
However, atrm no longer accepts such filenames as arguments - it wants
a job ID.  Failing to find the "ID" specified, atrm quietly does nothing,
leaving the scheduled job to be executed, and not alerting the adminstrator
or whoever it is invoking rmuser.

Submitted by:	Mike Sellenschuetter <mike.sellenschuetter@bankofamerica.com>
		in a mail to -security;
		Message-Id: <8625699E.0061FCDC.00@dalnsd40.bankofamerica.com>

>How-To-Repeat:

[root@ringworld:v4 ~]# pw adduser test
[root@ringworld:v4 ~]# echo 'at -f /dev/null now + 1 hour' | su test
Job 24 will be executed using /bin/sh
[root@ringworld:v4 ~]# atq
Date                    Owner   Queue   Job#
00:45:00 11/24/00       test    c       24
[root@ringworld:v4 ~]# rmuser test
Matching password entry:

test:*:1007:1007::0:0:User &:/home/test:/bin/sh

Is this the entry you wish to remove? y
/usr/sbin/rmuser: Informational: Home /home/test is not a directory, so it won't
 be removed
Removing user's at jobs: c0001800f7f5d5 done.
Updating password file, updating databases, done.
Updating group file: (removing group test -- personal group is empty) done.
Removing user's incoming mail file /var/mail/test: done.
Removing files belonging to test from /tmp: done.
Removing files belonging to test from /var/tmp: done.
Removing files belonging to test from /var/tmp/vi.recover: done.
[root@ringworld:v4 ~]# atq
Date                    Owner   Queue   Job#
00:45:00 11/24/00       ???     c       24
[root@ringworld:v4 ~]#

>Fix:

Although it is trivial to extract the job ID from the filename - in the
example given, the job ID was 24, and the filename contained a 00018
hex string - I do not really think this is the way to go.  There's no
telling when the at job filename format shall be changed again, and it is
very easy to miss updating rmuser if/when that should happen.

Attached instead are two patches - one against RELENG_4, another against
-current - which add two additional subroutines to invoke atq, parse its
output, find the jobs belonging to this user, then invoke atrm and kill
off the correct job ID's.

The patch against RELENG_4 also applies cleanly to RELENG_3.  I think
this one might well be worth MFC'ing into 3.x.

Here's the diff against -current..

Index: rmuser.perl
===================================================================
RCS file: /home/ncvs/src/usr.sbin/adduser/rmuser.perl,v
retrieving revision 1.11
diff -u -r1.11 rmuser.perl
--- rmuser.perl	2000/11/21 05:52:35	1.11
+++ rmuser.perl	2000/11/23 21:27:47
@@ -199,7 +199,7 @@
 # Remove the user's at jobs, if any
 # (probably also needs to be done before password databases are updated)
 
-&remove_at_jobs($login_name, $uid);
+&remove_at_jobs($login_name);
 
 #
 # Kill all the user's processes
@@ -464,32 +464,75 @@
     printf STDERR " done.\n";
 }
 
-sub remove_at_jobs {
-    local($login_name, $uid) = @_;
-    local($i, $owner, $found);
-
-    $found = 0;
-    opendir(ATDIR, $atjob_dir) || return;
-    while ($i = readdir(ATDIR)) {
-	next if $i eq '.';
-	next if $i eq '..';
-	next if $i eq '.lockfile';
 
-	$owner = (stat("$atjob_dir/$i"))[4]; # UID
-	if ($uid == $owner) {
-	    if (!$found) {
-		print STDERR "Removing user's at jobs:";
-		$found = 1;
-	    }
-	    # Use atrm to remove the job
-	    print STDERR " $i";
-	    system('/usr/bin/atrm', $i);
+sub invoke_atq {
+    local *ATQ;
+    my($user) = (shift || "");
+    my($path_atq) = "/usr/bin/atq";
+    my(@at) = ();
+    my($pid, $line);
+    
+    return @at if ($user eq "");
+    
+    if (!defined($pid = open(ATQ, "-|"))) {
+	die("creating pipe to atq: $!\n");
+    } elsif ($pid == 0) {
+	exec($path_atq, $user);
+	die("executing $path_atq: $!\n");
+    }
+    
+    while(defined($_ = <ATQ>)) {
+	chomp;
+	if (/^\d\d:\d\d:\d\d\s+\d\d\/\d\d\/\d\d\s+(\S+)\s+\S+\s+(\d+)$/) {
+	    push(@at, $2) if ($1 eq $user);
 	}
     }
-    closedir(ATDIR);
-    if ($found) {
-	print STDERR " done.\n";
+    close ATQ;
+    return @at;
+}
+
+sub invoke_atrm {
+    local *ATRM;
+    my($user) = (shift || "");
+    my($path_atrm) = "/usr/bin/atrm";
+    my(@jobs) = @_;
+    my($pid);
+    my($txt) = "";
+    
+    return "Invalid arguments" if (($user eq "") || ($#jobs == -1));
+    
+    if (!defined($pid = open(ATRM, "-|"))) {
+	die("creating pipe to atrm: $!\n");
+    } elsif ($pid == 0) {
+	exec($path_atrm, $user, @jobs);
+    }
+    
+    while(defined($_ = <ATRM>)) {
+	$txt .= $_;
     }
+    close ATRM;
+    return $txt;
+}
+
+sub remove_at_jobs {
+    my($user) = (shift || "");
+    my(@at, $atrm);
+    
+    return 1 if ($user eq "");
+    
+    print STDERR "Removing user's at jobs:";
+    @at = invoke_atq($user);
+    return 0 if ($#at == -1);
+    
+    print STDERR " @at:";
+    $atrm = invoke_atrm($user, @at);
+    if ($atrm ne "") {
+	print STDERR " -- $atrm\n";
+	return 1;
+    }
+    
+    print STDERR "done.\n";
+    return 0;
 }
 
 sub resolvelink {

==================================================================

And here's the diff against RELENG_4..

Index: rmuser.perl
===================================================================
RCS file: /home/ncvs/src/usr.sbin/adduser/rmuser.perl,v
retrieving revision 1.8.2.1
diff -u -r1.8.2.1 rmuser.perl
--- rmuser.perl	2000/03/20 13:00:36	1.8.2.1
+++ rmuser.perl	2000/11/23 21:25:50
@@ -202,7 +202,7 @@
 # Remove the user's at jobs, if any
 # (probably also needs to be done before password databases are updated)
 
-&remove_at_jobs($login_name, $uid);
+&remove_at_jobs($login_name);
 
 #
 # Kill all the user's processes
@@ -495,32 +495,75 @@
     printf STDERR " done.\n";
 }
 
-sub remove_at_jobs {
-    local($login_name, $uid) = @_;
-    local($i, $owner, $found);
-
-    $found = 0;
-    opendir(ATDIR, $atjob_dir) || return;
-    while ($i = readdir(ATDIR)) {
-	next if $i eq '.';
-	next if $i eq '..';
-	next if $i eq '.lockfile';
 
-	$owner = (stat("$atjob_dir/$i"))[4]; # UID
-	if ($uid == $owner) {
-	    if (!$found) {
-		print STDERR "Removing user's at jobs:";
-		$found = 1;
-	    }
-	    # Use atrm to remove the job
-	    print STDERR " $i";
-	    system('/usr/bin/atrm', $i);
+sub invoke_atq {
+    local *ATQ;
+    my($user) = (shift || "");
+    my($path_atq) = "/usr/bin/atq";
+    my(@at) = ();
+    my($pid, $line);
+    
+    return @at if ($user eq "");
+    
+    if (!defined($pid = open(ATQ, "-|"))) {
+	die("creating pipe to atq: $!\n");
+    } elsif ($pid == 0) {
+	exec($path_atq, $user);
+	die("executing $path_atq: $!\n");
+    }
+    
+    while(defined($_ = <ATQ>)) {
+	chomp;
+	if (/^\d\d:\d\d:\d\d\s+\d\d\/\d\d\/\d\d\s+(\S+)\s+\S+\s+(\d+)$/) {
+	    push(@at, $2) if ($1 eq $user);
 	}
     }
-    closedir(ATDIR);
-    if ($found) {
-	print STDERR " done.\n";
+    close ATQ;
+    return @at;
+}
+
+sub invoke_atrm {
+    local *ATRM;
+    my($user) = (shift || "");
+    my($path_atrm) = "/usr/bin/atrm";
+    my(@jobs) = @_;
+    my($pid);
+    my($txt) = "";
+    
+    return "Invalid arguments" if (($user eq "") || ($#jobs == -1));
+    
+    if (!defined($pid = open(ATRM, "-|"))) {
+	die("creating pipe to atrm: $!\n");
+    } elsif ($pid == 0) {
+	exec($path_atrm, $user, @jobs);
+    }
+    
+    while(defined($_ = <ATRM>)) {
+	$txt .= $_;
     }
+    close ATRM;
+    return $txt;
+}
+
+sub remove_at_jobs {
+    my($user) = (shift || "");
+    my(@at, $atrm);
+    
+    return 1 if ($user eq "");
+    
+    print STDERR "Removing user's at jobs:";
+    @at = invoke_atq($user);
+    return 0 if ($#at == -1);
+    
+    print STDERR " @at:";
+    $atrm = invoke_atrm($user, @at);
+    if ($atrm ne "") {
+	print STDERR " -- $atrm\n";
+	return 1;
+    }
+    
+    print STDERR "done.\n";
+    return 0;
 }
 
 sub resolvelink {

>Release-Note:
>Audit-Trail:
Responsible-Changed-From-To: freebsd-bugs->ghelmer 
Responsible-Changed-By: ghelmer 
Responsible-Changed-When: Thu Dec 28 09:07:22 CST 2000 
Responsible-Changed-Why:  
I'll take this one. 
. 

http://www.freebsd.org/cgi/query-pr.cgi?pr=23052 
State-Changed-From-To: open->closed 
State-Changed-By: ghelmer 
State-Changed-When: Mon Jul 23 13:29:32 CDT 2001 
State-Changed-Why:  
Patch committed to -current and scheduled for MFC in 1 week. 
Thanks! 

http://www.FreeBSD.org/cgi/query-pr.cgi?pr=23052 
>Unformatted:
