/etc/services
... cvspserver 2401/tcp cvspserver 2401/udp ...
用xinetd运行: /etc/xinetd.d/cvs
service cvspserver { socket_type = stream protocol = tcp wait = no user = root server = /usr/bin/cvs server_args = -f --allow-root=/repos/root1 --allow-root=/repos/root2 pserver disable = no }
用inetd运行: /etc/inetd.conf
... cvspserver stream tcp nowait root /usr/bin/cvs cvs -f --allow-root=/repos/project --allow-root=/repos/user pserver ...
创建系统帐号
假设目录 /repos/project
作为多人共享项目的版本控制根目录,需要用组权限控制;/repos/user
作为存放个人独占地版本控制根目录。管理员帐号为cvsroot,项目版本控制的公共帐号为 cvsproject,用户版本控制的公共帐号为 cvsuser,相应的用户组为 cvsroot, cvsproject, cvsuser。
$ groupadd cvsproject $ groupadd cvsuser $ groupadd cvsroot $ useradd -g cvsproject -s /sbin/nologin cvsproject $ useradd -g cvsuser -s /sbin/nologin cvsuser $ useradd -g cvsroot -G cvsproject,cvsuser cvsroot $ useradd -g cvsuser -s /sbin/nologin cvs_jiangxin $ useradd -g cvsuser -s /sbin/nologin cvs_johnson
创建CVS根目录
$ mkdir -p /repos/project $ mkdir -p /repos/user $ chown cvsroot:cvsroot /repos/project $ chown cvsroot:cvsroot /repos/user $ chmod 775 /repos $ chmod 2775 /repos/project $ chmod 2775 /repos/user $ su - cvsroot $ cvs -d /repos/project init $ cvs -d /repos/user init
运行完毕 cvs init 之后,在CVS根目录下创建了配置目录CVSROOT
,权限如下:
$ ls -l /repos/project/CVSROOT/ -r--r--r-- 1 cvsroot cvsroot 493 Jan 21 10:37 checkoutlist -r--r--r-- 1 cvsroot cvsroot 696 Jan 21 10:37 checkoutlist,v -r--r--r-- 1 cvsroot cvsroot 760 Jan 21 10:37 commitinfo -r--r--r-- 1 cvsroot cvsroot 963 Jan 21 10:37 commitinfo,v -r--r--r-- 1 cvsroot cvsroot 527 Jan 21 10:37 config -r--r--r-- 1 cvsroot cvsroot 730 Jan 21 10:37 config,v -r--r--r-- 1 cvsroot cvsroot 753 Jan 21 10:37 cvswrappers -r--r--r-- 1 cvsroot cvsroot 956 Jan 21 10:37 cvswrappers,v -r--r--r-- 1 cvsroot cvsroot 1025 Jan 21 10:37 editinfo -r--r--r-- 1 cvsroot cvsroot 1228 Jan 21 10:37 editinfo,v drwxrwxr-x 2 cvsroot cvsroot 4096 Jan 21 10:37 Emptydir -rw-rw-rw- 1 cvsroot cvsroot 0 Jan 21 10:37 history -r--r--r-- 1 cvsroot cvsroot 1141 Jan 21 10:37 loginfo -r--r--r-- 1 cvsroot cvsroot 1344 Jan 21 10:37 loginfo,v -r--r--r-- 1 cvsroot cvsroot 1151 Jan 21 10:37 modules -r--r--r-- 1 cvsroot cvsroot 1354 Jan 21 10:37 modules,v -r--r--r-- 1 cvsroot cvsroot 564 Jan 21 10:37 notify -r--r--r-- 1 cvsroot cvsroot 767 Jan 21 10:37 notify,v -r--r--r-- 1 cvsroot cvsroot 649 Jan 21 10:37 rcsinfo -r--r--r-- 1 cvsroot cvsroot 852 Jan 21 10:37 rcsinfo,v -r--r--r-- 1 cvsroot cvsroot 879 Jan 21 10:37 taginfo -r--r--r-- 1 cvsroot cvsroot 1082 Jan 21 10:37 taginfo,v -rw-rw-rw- 1 cvsroot cvsroot 0 Jan 21 10:37 val-tags -r--r--r-- 1 cvsroot cvsroot 1026 Jan 21 10:37 verifymsg -r--r--r-- 1 cvsroot cvsroot 1229 Jan 21 10:37 verifymsg,v
可以看出文件 history, val-tags 的权限是任何人可读写,其它文件的权限是任何帐号只读。
文件 CVSROOT/val-tags
用来确定是否一个TAG是可用的;文件 CVSROOT/history
用来记录CVS的访问记录。
创建 CVS 用户帐号
使用系统帐号不安全,而CVS提供了独立于系统的用户帐号管理。
使用配置文件 CVSROOT/passwd
, CVSROOT/passwd
, CVSROOT/passwd
,来管理帐号。
$ cat /repos/project/CVSROOT/passwd jiangxinroot:_passwd_here_:cvsroot jiangxin:_passwd_here_:cvsproject johnson:_passwd_here_:cvsproject anonymous::cvsproject $ cat /repos/project/CVSROOT/readers anonymous
CVS 用户修改口令
我写了一个程序,作为用户登录的 shell,允许用户远程 SSH 登录,修改自己的 Unix 系统口令以及 CVS 账号口令,参见:附件。如果您有更好的方法,不吝赐教。
只能以 cvsroot 用户创建工程。因为 cvs 根目录的权限设置为 cvsroot 帐户可写。
在客户端创建工程
client$ cvs -d :pserver:jiangxinroot@10.0.0.7:/repos/project login client$ cvs -d :pserver:jiangxinroot@10.0.0.7:/repos/project import -m "add module test, vendor jiangxin, init_tag start." test jiangxin start $ ls -l /repos/project drwxrwxr-x 3 cvsroot cvsroot 4096 Jan 21 10:54 CVSROOT drwxrwsr-x 2 cvsroot cvsroot 4096 Jan 21 11:00 test $ chown -R cvsproject:cvsproject test $ chmod -R 770 test $ chmod -R g+s test client$ cvs -d :pserver:jiangxin@10.0.0.7:/repos/project co test client$ cvs -d :pserver:johnson@10.0.0.7:/repos/project co -d test2 test
于是创建了多用户共享的工程 test。
另一种创建工程的方法:在服务器端创建工程
可以直接在服务器端创建目录,设置权限,即完成工程的创建。当然这样创建的工程只是一个空的工程,需要在客户端为空的工程逐个添加文件和目录。
$ cat /repos/project/CVSROOT/passwd jiangxroot:_passwd_here_:cvsroot anonymous:_passwd_here_:cvsuser jiangxin:_passwd_here_:cvs_jiangxin johnson:_passwd_here_:cvs_johnson $ cd /repos/project $ mkdir jiangxin; chown -R cvs_jiangxin:cvsuser jiangxin; chmod -R 2700 jiangxin $ mkdir johnson; chown -R cvs_johnson:cvsuser johnson; chmod -R 2700 johnson
于是创建了两个用户独占的工程。
确省安装的CVS的权限仅仅作用于目录,而不能精细到文件级别。而且即使用户只需要拥有文件的只读权限,也要对相应的目录具有写权限,因为需要在目录下创建锁定文件。有一个办法可以避免此问题,即:通过配置文件 CVSROOT/config
的 LockDir 来设置单独的锁定目录,为该单独的锁定目录设置更宽泛的权限控制。
CVS 提供了功能的扩充接口:CVSROOT目录下的管理文件。这些文件提供了相应功能扩充的接口,不但可以完成精细的权限控制,还能完成更加个性化的功能。关于CVSROOT下的脚本,FreeBSD 的源代码就有一个非常好的CVSROOT脚本,可供我们参照:
Setting up a CVS repository - the FreeBSD way。我们可以参照这个指南,定制我们自己的CVSROOT脚本。
下载 FreeBSD 的 CVSROOT 脚本,可以以匿名用户连接到 FreeBSD 的 CVS 服务器:
$ cvs -d :pserver:anoncvs@anoncvs.freebsd.org:2401/home/ncvs login # 输入密码 anoncvs $ cvs -d :pserver:anoncvs@anoncvs.freebsd.org:2401/home/ncvs co CVSROOT-src
下载获得 FreeBSD 的 CVSROOT 代码后,需要进行相应的定制,然后才能 checkin 到自己的 CVSROOT 目录中。这个补丁是我对其的修改和定制:patch.txt。
定制过程:
升级CVS
确认安装的CVS服务器版本,要高于 1.11.2 。因为我们要用到在版本 1.11.2 才出现的功能:能够在检查commit log 后重新读入 commit log,以实现对 commit log 的格式化。
定制 PERL 模块 cfg.pm
文件 CVSROOT/cfg.pm
,是 perl脚本的核心包,对其做了一些改动,主要是添加了禁止某些用户发送 Email功能;还增加了部分子过程,部分是从原 log_acum.pl 中移动过来,目的将这些函数设置为模块内部的函数便于其它需要发送邮件的脚本调用,如脚本 log_accum.pl 和 tagcheck 都需要使用这些新增子过程。
对脚本 CVSROOT/cfg.pm
的改动如下:
diff -u -r1.1 -r1.2 --- cfg.pm 14 Aug 2003 10:00:53 -0000 1.1 +++ cfg.pm 15 Aug 2003 01:44:20 -0000 1.2 @@ -17,9 +17,10 @@ $ADD_TO_LINE $AVAIL_FILE $CHECK_HEADERS $COMMITCHECK_EXTRA @COMMIT_HOSTS $COMMITTER $DEBUG $DIFF_BLOCK_TOTAL_LINES $EXCLUDE_FILE $FILE_PREFIX $IDHEADER $LAST_FILE @LOG_FILE_MAP $MAILADDRS $MAILBANNER - $MAILCMD $MAIL_BRANCH_HDR $MAIL_ON_DIR_CREATION $MAIL_TRANSFORM + $MAILCMD $MAIL_BRANCH_HDR @MAIL_MAP $MAIL_ON_DIR_CREATION $MAIL_TRANSFORM $MINCVSVERSION $MAX_DIFF_SIZE $NO_DOS_LINEBREAKS $PID $PROG_CVS $PROG_MV %TEMPLATE_HEADERS $TMPDIR $UNEXPAND_RCSID $WARN_HEADERS + $BADSENDER_FILE ); my $CVSROOT = $ENV{'CVSROOT'} || die "Can't determine \$CVSROOT!"; @@ -52,7 +53,7 @@ $PROG_MV = '/bin/mv'; # mv(1) # The username of the committer. -$COMMITTER = $ENV{"LOGNAME"} || $ENV{'USER'} || getlogin +$COMMITTER = $ENV{"CVS_USER"} || $ENV{"LOGNAME"} || $ENV{'USER'} || getlogin || (getpwuid($<))[0] || sprintf("uid#%d",$<); @@ -83,6 +84,7 @@ # commit to what. $AVAIL_FILE = "$CVSROOT/CVSROOT/avail"; +$BADSENDER_FILE = "$CVSROOT/CVSROOT/blocksender"; ################ ### logcheck ### @@ -208,6 +210,10 @@ 'other' => '.*' ); +@MAIL_MAP = ( + 'nobody' => '.*' +); + # Include diffs of not greater than this size in kbytes in the # commit mail for each file modified. (0 = off). $MAX_DIFF_SIZE = 0; @@ -270,6 +276,64 @@ return @output; }; + +############################################################ +# +# Subroutines +# +############################################################ + +# !!! Mailing-list and commitlog history file mappings here !!! +# This needs pulling out as a configuration block somewhere so +# that others can easily change it. + +sub get_log_name { + my $dir = shift; # Directory name + + + for my $i (0 .. ($#cfg::LOG_FILE_MAP - 1) / 2) { + my $log = $cfg::LOG_FILE_MAP[$i * 2]; + my $pattern = $cfg::LOG_FILE_MAP[$i * 2 + 1]; + + return $log if $dir =~ /$pattern/; + } + + return 'other'; +} + +sub get_mail_name { + my $dir = shift; # Directory name + my $CVSROOT = $ENV{'CVSROOT'}; + $dir =~ s,^$CVSROOT[/]?,,g; + $dir .= "/" unless $dir =~ /\/$/; + + for my $i (0 .. ($#cfg::MAIL_MAP - 1) / 2) { + my $email = $cfg::MAIL_MAP[$i * 2]; + my $pattern = $cfg::MAIL_MAP[$i * 2 + 1]; + return $email if $dir =~ /$pattern/; + } + + return $cfg::MAILADDRS; +} + + +# do not send email, if committer is in badsender file... +sub sendmail_acl_check { + my $sendmail = 1; + if (-e $cfg::BADSENDER_FILE) + { + open (BADSENDER, $cfg::BADSENDER_FILE) || die "open $cfg::BADSENDER_FILE: $!\n"; + while (<BADSENDER>) { + if ($_ =~ /\b$cfg::COMMITTER\b/i) + { + $sendmail = 0; + last; + } + } + } + + return $sendmail; +} ###################################################################### # Load the local configuration file, that allows the entries in this
增加检查环境变量 CVS_USER。以能够正确反映使用 CVSROOT/password 文件进行身份验证的用户名。 |
|
通过文件 $BADSENDER_FILE 设置哪些用户对 CVS 操作不必发送邮件,这个功能可以用于自动编译下的特定用户的CVS操作不必发送邮件。 |
还在该perl模块中增加了几个过程,供其它程序调用。 |
文件 CVSROOT/cfg_local.pm
用于对模块 cfg.pm 进行定制:
hash$ diff -u -r1.1 cfg_local.pm --- cfg_local.pm 14 Aug 2003 10:00:53 -0000 1.1 +++ cfg_local.pm 15 Aug 2003 03:09:39 -0000 1.3 @@ -13,7 +13,7 @@ #################################################################### #################################################################### -$CHECK_HEADERS = 1; +$CHECK_HEADERS = 0; $IDHEADER = 'FreeBSD'; $UNEXPAND_RCSID = 1; @@ -29,25 +29,30 @@ $MAILCMD = "/usr/local/bin/mailsend -H"; $MAIL_BRANCH_HDR = "X-FreeBSD-CVS-Branch"; $ADD_TO_LINE = 0; -$MAILBANNER = "FreeBSD src repository"; +$MAILBANNER = "My repository"; if (defined $ENV{'CVS_COMMIT_ATTRIB'}) { my $attrib = $ENV{'CVS_COMMIT_ATTRIB'}; $MAILBANNER .= " ($attrib committer)"; } +# The minimum version of cvs that we will work with. +$MINCVSVERSION = "1110200"; # 1.11.2 + +$MAIL_ON_DIR_CREATION = 0; # Sanity check to make sure we've been run through the wrapper and are # now primary group 'ncvs'. # -$COMMITCHECK_EXTRA = sub { - my $GRP=`/usr/bin/id -gn`; - chomp $GRP; - unless ( $GRP =~ /^ncvs$/ ) { - print "You do not have group ncvs (commitcheck)!\n"; - exit 1; # We could return false here. But there's - # nothing to stop us taking action here instead. - } - return 1; -}; + +#$COMMITCHECK_EXTRA = sub { +# my $GRP=`/usr/bin/id -gn`; +# chomp $GRP; +# unless ( $GRP =~ /^ncvs$/ ) { +# print "You do not have group ncvs (commitcheck)!\n"; +# exit 1; # We could return false here. But there's +# # nothing to stop us taking action here instead. +# } +# return 1; +#}; # Wrap this in a hostname check to prevent mail to the FreeBSD # list if someone borrows this file and forgets to change it. @@ -91,6 +96,22 @@ @LOG_FILE_MAP = ( 'CVSROOT' => '^CVSROOT/', 'distrib' => '^distrib/', 'doc' => '^doc/', 'ports' => '^ports/', 'www' => '^www/', 'other' => '.*' ); +# CVSROOT is still shared between src, ports, doc at the moment. projects has +# its own CVSROOT. +@MAIL_MAP = ( + 'maillist1' => '^CVSROOT/', + 'maillist2' => '^src/', + 'cvsnone' => '.*', +); + +@TAG_MAP = ( + 'jiangxin' => '^(release|mailstome).*', +); + +# Email addresses of recipients of commit mail. +$MAILADDRS = 'cvsnone'; + + 1; # Perl requires all modules to return true. Don't delete!!!! #end
设置为0,不强制文件头包含特定的CVS关键字。 |
|
设置安装的CVS服务器的最低版本为 1.11.2; |
|
创建目录的事件,也发送邮件。参见脚本: |
|
注释该函数,不检查用户组。 |
|
定制该数组,将 CVS 模块的 commit log 存储在对应的文件中。 |
|
添加哈希表 @MAIL_MAP,设置模块和用户邮件地址的对应关系,相应模块的 commit,通过邮件通知相应用户。 |
|
添加哈希表 @TAG_MAP,设置某些格式的TAG只能被某些用户管理。 |
|
确省的邮件地址。对于没有在 MAIL_MAP 数组找到匹配的邮件地址,即使用该地址。确省为 'nobody'。 |
CVS 服务器配置文件:CVSROOT/config
#SystemAuth=no #LockDir=/var/lock/cvs #TopLevelAdmin=no LogHistory=TOFEWGCMAR RereadLogAfterVerify=always
设置版本控制过程中,需要忽略的文件。这些文件将不被显示为未知状态("?")。如:
*.db *.info *.[Sp]o *.core *.aps *.clw *.exe *.ncb *.obj *.opt *.plg Debug Release
亦可由每个目录下的文件
对于 WinCVS,则由文件 |
匹配文件名,并作相应处理。如: -kb 即以二进制方式处理文件。
*.gif -k 'b' *.GIF -k 'b' *.jpg -k 'b' ...
设置数据仓库中的模块名,可以通过命令:“cvs co -c”察看当前数据仓库(repository)中包含的模块/工程名称。也可以在调整服务器端目录结构时,设置 modules 来保持和以前设置的兼容性。
CVSROOT CVSROOT module1 module2 &module3
列在 checkoutlist 中的文件,在 checkin 后,能够自动在服务器 CVSROOT 目录中重建。
#access avail cfg.pm cfg_local.pm commit_prep.pl commitcheck cvs_acls.pl exclude log_accum.pl logcheck options rcstemplate tagcheck unwrap wrap
当用户设置了 watch 一个文件,可以定制该文件进行控制。在此该文件未被用到。
Commit 事件要触发三个脚本文件,依次是 commitinfo, verifymsg, loginfo。其中先遍历整个目录树对所有需要 commit 的文件执行 commitinfo文件。再分别针对每一个目录执行 verifymsg, loginfo 脚本。
commitinfo 会调用 commitcheck 脚本,完成的功能:通过用户主机名、用户名来检查权限;确认CVS服务器的版本号不低于某个版本;将遍历目录树的结果(最后一个目录名)记录下来,以便接下来运行 verifymsg, loginfo的脚本能够确认运行结束等。
相关文件:CVSROOT/commitcheck
,CVSROOT/cvs_acls.pl
,CVSROOT/avail
,CVSROOT/commit_prep.pl
,CVSROOT/exclude
,CVSROOT/cfg.pm
,CVSROOT/cfg_local.pm
。
文件 CVSROOT/avail
,被脚本 cvs_acls.pl 读取,再被脚本 commitcheck 调用,用以精细控制权限。例如:
group|meisters|peter,jdp,markm,joe # Pick up the list of bad users from ncvs/CVSROOT/badcommitters See that # file for details group|penaltybox|!badcommitters unavail avail||CVSROOT avail||distrib avail||doc avail||ports avail||src unavail||src/contrib/binutils,src/contrib/file avail|obrien|src/contrib/binutils,src/contrib/file unavail||src/contrib/tcpdump avail|fenner,nectar|src/contrib/tcpdump avail||www avail|:meisters unavail|:penaltybox
用于检查和格式化 commit log。禁止在版本控制提交时,使用空的 commit log。对于 wincvs 在用户不提交 commit log 时,会自动使用“no message”作为commit log。为了禁止该情况发生,需要定制该脚本:
相关文件:CVSROOT/logcheck
。
bash$ diff -u -r1.1 logcheck --- logcheck 14 Aug 2003 10:00:53 -0000 1.1 +++ logcheck 15 Aug 2003 02:01:37 -0000 1.2 @@ -47,8 +47,10 @@ # Remove leading and trailing blank lines. (There will be at most # one because of the duplicate removal above). +local $^W = 0; shift @log_in if $log_in[0] eq ""; pop @log_in if $log_in[-1] eq ""; +local $^W = 1; # Scan through the commit message looking for templated headers # as defined in the configuration file, and rcstemplate. @@ -104,6 +106,9 @@ # completely empty. This is a bug in cvs. my $log = "@log_in"; die "Log message contains no content!\n" if $log =~ /^\s*$/; + +# commit without commit log using WINCVS , will automatically provide commit log as "no message". +die "Log message contains no content using WINCVS!\n" if $log =~ /^no message$/ or $log =~ /^\.+$/;
将 commit log 分门别类存储在目录 CVSROOT/commitlogs 下,并同时通过邮件外发。为了防止一次事件触发多次的邮件外发,该脚本利用到 commitinfo 的运行结果,只有确认到了目录树的最后,才发送邮件。 模块和存储日志文件以及用户邮件列表在文件CVSROOT/cfg_local.pm
中定义。
相关文件:CVSROOT/log_accum.pl
,CVSROOT/cfg.pm
,CVSROOT/cfg_local.pm
。
文件 CVSROOT/loginfo
,调用脚本 log_accum.pl,并传递文件名等参数,用于组织日志和发送邮件。
原 FreeBSD 脚本存在一个 BUG,如果文件名或者目录名中存在空格,脚本运行不正常,出错,导致不能组织和发送邮件,这个 Bug 修正方法如下:
在文件 CVSROOT/loginfo
中调用 log_accum.pl 时使用的参数由原来的 %s 修改为 %{,,,s},即加入三个连续的逗号作为分隔符,否则使用空格作为分隔符,难以区分是文件的分隔符还是路径或文件名中的空格。
bash$ tail -1 loginfo DEFAULT $CVSROOT/CVSROOT/log_accum.pl %{,,,s}
对文件 CVSROOT/log_accum.pl
的修正如下:
bash$ diff -u -r1.1 log_accum.pl --- log_accum.pl 14 Aug 2003 10:00:53 -0000 1.1 +++ log_accum.pl 15 Aug 2003 02:00:35 -0000 1.2 @@ -47,6 +47,7 @@ my $LOG_FILE = "$BASE_FN.log"; my $SUMMARY_FILE = "$BASE_FN.summary"; my $LOGNAMES_FILE = "$BASE_FN.lognames"; +my $MAILNAMES_FILE = "$BASE_FN.mailnames"; my $SUBJ_FILE = "$BASE_FN.subj"; my $TAGS_FILE = "$BASE_FN.tags"; my $DIFF_FILE = "$BASE_FN.diff"; @@ -233,7 +234,8 @@ while (<RCS>) { if (/^[ \t]*Repository revision/) { chomp; - my @revline = split; + my @revline = split(/[ \t]+/, $_, 5); + shift @revline while($revline[0] eq ""); $revision = $revline[2]; $revline[3] =~ m|^$CVSROOT/+(.*),v$|; $rcsfile = $1; @@ -383,20 +385,6 @@ # !!! Mailing-list and commitlog history file mappings here !!! # This needs pulling out as a configuration block somewhere so # that others can easily change it. -sub get_log_name { - my $dir = shift; # Directory name - - - for my $i (0 .. ($#cfg::LOG_FILE_MAP - 1) / 2) { - my $log = $cfg::LOG_FILE_MAP[$i * 2]; - my $pattern = $cfg::LOG_FILE_MAP[$i * 2 + 1]; - - return $log if $dir =~ /$pattern/; - } - - return 'other'; -} - sub do_changes_file { my @text = @_; @@ -408,11 +396,17 @@ $unique{$category} = 1; my $changes = "$CVSROOT/CVSROOT/commitlogs/$category"; - open CHANGES, ">>$changes" - or die "Cannot open $changes.\n"; - print CHANGES map { "$_\n" } @text; - print CHANGES "\n\n\n"; - close CHANGES; + if (open CHANGES, ">>$changes") + { + print CHANGES map { "$_\n" } @text; + print CHANGES "\n\n\n"; + close CHANGES; + } + else + { + print "Cannot open $changes.\n"; + } + } } @@ -420,22 +414,29 @@ sub mail_notification { my @text = @_; -# This is turned off since the To: lines go overboard. -# Also it has bit-rotted since, and can't just be switched on again. -# - but keep it for the time being in case we do something like cvs-stable -# my @mailaddrs = &read_logfile($LOGNAMES_FILE); -# print(MAIL 'To: cvs-committers' . $dom . ", cvs-all" . $dom); -# foreach $line (@mailaddrs) { -# next if ($unique{$line}); -# $unique{$line} = 1; -# next if /^cvs-/; -# print(MAIL ", " . $line . $dom); -# } -# print(MAIL "\n"); + if (! &cfg::sendmail_acl_check()) + { + print "mail sent from $cfg::COMMITTER is blocked.\n"; + return 0; + } + + my %unique = (); + my @mailaddrs = &read_logfile($MAILNAMES_FILE); + # ok, this is kinda hokey, but I need to break up lines with multiple addresses + my $fu = join(" ", @mailaddrs); + @mailaddrs = split " ", $fu; + + my $to = ""; + foreach my $category (@mailaddrs) { + next if ($unique{$category}); + $unique{$category} = 1; + + $to .= " " unless $to eq ""; + $to .= $category; + } my @email = (); - my $to = $cfg::MAILADDRS; print "Mailing the commit message to '$to'.\n"; push @email, "To: $to" if $cfg::ADD_TO_LINE; @@ -497,10 +498,14 @@ } # Send the email. - open MAIL, "| $cfg::MAILCMD $to" - or die "Please check $cfg::MAILCMD."; - print MAIL map { "$_\n" } @email; - close MAIL; + if(fork() == 0) + { + open MAIL, "| $cfg::MAILCMD -F $cfg::COMMITTER $to" + or die "Please check $cfg::MAILCMD."; + print MAIL map { "$_\n" } @email; + close MAIL; + } + exit(0); } @@ -634,8 +639,9 @@ # # Initialize basic variables # +my $separator=",,,"; my $input_params = $ARGV[0]; -my ($directory, @filenames) = split " ", $input_params; +my ($directory, @filenames) = split / ${separator}/, $input_params; #@files = split(' ', $input_params); my @path = split('/', $directory); @@ -660,8 +666,9 @@ } # Was used for To: lines, still used for commitlogs naming. -&append_line($LOGNAMES_FILE, &get_log_name("$directory/")); -&append_line($SUBJ_FILE, "$directory " . join(" ", sort @filenames)); +&append_line($LOGNAMES_FILE, &cfg::get_log_name("$directory/")); +&append_line($MAILNAMES_FILE, &cfg::get_mail_name("$directory/")); +&append_line($SUBJ_FILE, "$directory/(" . join(",", sort @filenames) .") "); # # Check for a new directory first. This will always appear as a @@ -697,7 +704,7 @@ &do_changes_file(@text); &mail_notification(@text); - system("/usr/local/bin/awake", $directory); + # system("/usr/local/bin/awake", $directory); &cleanup_tmpfiles(); exit 0; } @@ -742,7 +749,28 @@ } # otherwise collect information about which files changed. - my @files = split; + my @tmpfiles = split; + my $strname = ""; + my @files; + while (my $item = shift(@tmpfiles)) + { + if ($strname eq "") + { + $strname = $item; + } + else + { + $strname .= " $item"; + } + for (my $i=0; $i<=$#filenames; $i++) + { + if ($strname eq $filenames[$i]) + { + push (@files, $strname); + $strname = ""; + } + } + } push @{ $changed_files{$tag} }, @files if $state == $STATE_CHANGED; push @{ $added_files{$tag} }, @files if $state == $STATE_ADDED; push @{ $removed_files{$tag} }, @files if $state == $STATE_REMOVED; @@ -896,7 +924,7 @@ &mail_notification(@log_msg); } -system("/usr/local/bin/awake", $directory); +# system("/usr/local/bin/awake", $directory); &cleanup_tmpfiles(); exit 0; # EOF
在执行 tag/rtag 命令前执行该脚本,如果脚本返回非零值,tag/rtag 动作取消。
相关脚本:CVSROOT/tagcheck
。负责对添加/删除 TAG 事件进行控制——允许/禁止/发送邮件。
由于 tag/rtag 事件不象 commit 事件,不是通过多个脚本的配合完成,而是只通过一个脚本 taginfo 完成。这就出现一个问题:如果为一个目录树打上TAG,则可能多次执行脚本,可能要多次触发邮件发送。我的解决办法是,根据TAG进程的 PID 确定在整个过程唯一的文件名,将日志记录到该文件中,taginfo 脚本本身无法知道是否结束,而是系统通过 crontab 定期执行脚本 CVSROOT/checkmailspool.sh来检查是否有完成的 tag 邮件需要外发。
#!/usr/bin/perl -w # # Author: Jiang Xin # Reference: http://www.worldhello.net/ # use strict; use lib $ENV{CVSROOT}; use CVSROOT::cfg; ############################################################# # # Main Body # # TAG add/mov/del repo files... # $1 $2 $3 $4 ... # ############################################################ my $tag = shift; my $action = shift; my $repos = shift; my $fileitem = ""; my $filerev= ""; my $filelist = ""; my $uid = $cfg::COMMITTER; my $userlist = ""; my $pattern = ""; my $permission = 1; my $to = ""; my $tmpstr = &cfg::get_mail_name($repos); $tmpstr =~ s/\@/\./g ; $tmpstr="nobody" unless $tmpstr; my $MAILFILE = "/var/spool/cvsmail/cvs.tag.$tmpstr.$cfg::PID"; die "Usage: tagcheck tag action repos files...\n" unless $repos; for my $i (0 .. ($#cfg::TAG_MAP - 1) / 2) { $userlist = $cfg::TAG_MAP[$i * 2]; $pattern = $cfg::TAG_MAP[$i * 2 + 1]; if ($tag =~ /$pattern/i) { if ($userlist =~ /\b$uid\b/i) { $permission=1; last; } else { $permission=0; last; } } } if ($permission == 0) { # normal users can not do this. print STDERR "User \"$cfg::COMMITTER\" canot perform this operation!\n"; print STDERR "Only users: $userlist, can handle tag patterm: \"$pattern\"!\n"; } while ($fileitem = shift) { $filerev = shift; $filelist = sprintf("%s\t%-24s:\t%s\n", $filelist, $fileitem, $filerev); } print "save message in spool `dirname $MAILFILE`...\n"; my @email = (); if (! -e $MAILFILE ) { $to = &cfg::get_mail_name($repos); push @email, "From: $uid<$uid>"; push @email, "To: $to"; $tmpstr = sprintf("Date: %s", `date -R`); chomp $tmpstr; push @email, $tmpstr; if ($permission == 0) { push @email, "Subject: cvs tag FAILED! ($action $tag on $repos)"; } else { push @email, "Subject: cvs tag success: $action $tag on $repos"; } push @email, ""; delete $ENV{'TZ'}; $tmpstr = sprintf("%-11s: %-8s", "Author", $cfg::COMMITTER); push @email, $tmpstr; $tmpstr = sprintf("%-11s: %-8s", "Date", `/bin/date +"%Y/%m/%d %H:%M:%S %Z"`); chomp $tmpstr; push @email, $tmpstr; $tmpstr = sprintf("%-11s: %-8s", "Tag", $tag); push @email, $tmpstr; $tmpstr = sprintf("%-11s: %-8s", "Operation", $action); push @email, $tmpstr; push @email, ""; push @email, " $cfg::MAILBANNER", "" if $cfg::MAILBANNER; } if ($permission == 0) { push @email, "Permission denied: $action $tag on $repos !"; push @email, "--------------------------------------------------"; } else { $tmpstr = sprintf("%-11s: %-8s", "Repository", $repos); push @email, $tmpstr; push @email, $filelist if $filelist; } #save mail to spool open MAIL, ">> $MAILFILE " or die "Cannot open file $MAILFILE for append."; print MAIL map { "$_\n" } @email; close MAIL; if ($permission == 0) { exit 1 } else { exit 0 }
确定进程唯一的文件名称; |
|
@cfg::TAG_MAP 数组定义了需要权限控制的 TAG 名称,以及授权人列表。受限的TAG名称对应于软件开发中的里程碑,要严格的权限控制。和该模式匹配的 tag,只能被授权人操作,其它名称的 TAG,所有用户都可以操作。 |
|
邮件地址亦从 MAIL_MAP 数组中获取; |
文件 CVSROOT/checkmailspool.sh
,加入到 crontab 中定期执行,检查 tagcheck 生成的邮件。
#!/bin/sh # checkmailspool.sh # Auther: Jiang Xin # # $CVSMAILPATH (cvs mail spool) is a spool directory created by user, # and cvs tag message will store in it. # run this script timely to check cvsmail spool and send messages... # please put this script in crontab. CVSMAILPATH=/var/spool/cvsmail if [ ! -d $CVSMAILPATH ]; then mkdir -p $CVSMAILPATH chmod 777 $CVSMAILPATH fi cd $CVSMAILPATH for i in `ls `; do pid=`echo $i| sed -e "s/.*\.\([0-9]*\)$/\1/"` xxx=0 while ps -p $pid>/dev/null; do xxx=`expr $xxx + 1` if [ $xxx -gt 10 ]; then break fi sleep 3 echo waiting $pid, $xxx times ... done echo -e "\n\n========================================" >> $i echo -e "End\n" >> $i cat $i | sendmail -oi -oem -odb -t rm -f $i done
以上 CVSROOT 脚步在执行过程中将会在临时目录中产生很多临时文件,如果不加以清理,不但会浪费磁盘空间,更有可能导致发送张冠李戴的错误邮件。在 crontab 中配置每隔一个小时执行一遍以下脚本:
#/bin/sh cd /tmp ls \#cvs.files.* | sed -e 's/\#cvs.files.\([0-9]*\)\..*$/\1/g' | sort -u | \ while read xxx; do if ps --pid $xxx>/dev/null 2>&1; then continue; fi ; \ rm -f /tmp/\#cvs.files.$xxx.* ; done
Linux ACL 的相关资源参见:
Linux ACL Homepage
Using ACLs with Fedora Core 2
还有我的一个 Wiki 页面 =)
为什么要使用 ACL 呢?因为对于一个大的CVS项目,需要对各个模块进行精确的权限控制,如果只靠传统的Unix的目录权限控制 user,group,other 将会大大增进系统管理员的负担,项目的“超级用户”需要同时属于多个用户组,才可以访问所有的模块。
突然有一天,发现权限设置不灵了,经过测试,原来 linux 2.4 内核一个用户最多属于 32 个用户组!看来是另辟蹊径的时候了。久闻 acl 的大名,对于2.4内核是以内核补丁方式存在,2.6内核已经集成进去了,这可真是一个好消息。(注意: 2.6.10 内核的ACL存在一个大BUG,已再 2.6.11-rc4 内核中修正。具体参见我的wiki =)
虽然使用 acl,对于CVS来说,前面提到的CVS权限控制依然试用。即:
项目相关的所有用户必须对CVS工程目录以及工程的CVSROOT目录具有 r-x 权限;
所有用户对文件 CVSROOT/history, CVSROOT/val-tags, 和目录 CVSROOT/commitlogs/(如果安装了CVSROOT扩展),必须拥有写权限;
设置目录的 setgid 位,使用户创建目录的时候,新目录能够保持上一级目录的用户属主;
CVSROOT 下的脚本,需要清除其 setuid, setgid 位,否则脚本执行时报错;
为工程添加权限时,不要修改 symbol link 文件权限,以免互相覆盖;
例如: 多个项目共享同一个 passwd 文件。执行 setfacl 需要加上 -P 参数;
一个权限设置脚本模块:
#!/bin/sh # acl.sh version 0.9 SETFACLCMD="setfacl -P" CVSHOMEDIR=/cvshome ############################################################################## #Declare Function: #################################################################### # /etc/group 记录: # tst_root:x:682:u_tst_root # tst_doc:x:705:u_tst_doc # tst_src:x:683:u_tst_src # tst_src_1:x:706:u_tst_src_1 # tst_src_2:x:707:u_tst_src_2 #################################################################### apply_acl_test() { if [ $# -ne 1 ];then echo format: `basename $0` project exit 1 fi project=$1 if [ ! -d $project ]; then echo "Can not find project: $project." fi # tst_root 组可以访问所有模块,包括 CVSROOT 的写权限 $SETFACLCMD -d -R -m g:tst_root:rwx $project $SETFACLCMD -R -m g:tst_root:rwx $project # tst_src 组能够访问所有代码、文档 $SETFACLCMD -d -R -m g:tst_src:rwx $project/src $project/doc $SETFACLCMD -R -m g:tst_src:rwx $project/src $project/doc # 所有用户均能访问文档 $SETFACLCMD -d -R -m g:tst_src:rwx,g:tst_src_1:rwx,g:tst_src_2:rwx,g:tst_doc:rwx $project/doc $SETFACLCMD -R -m g:tst_src:rwx,g:tst_src_1:rwx,g:tst_src_2:rwx,g:tst_doc:rwx $project/doc # 需要权限控制的小组,缺省访问权限 $SETFACLCMD -d -R -m g:tst_src_1:rwx,g:tst_src_2:rwx $project/src $SETFACLCMD -R -m g:tst_src_1:rwx,g:tst_src_2:rwx $project/src # 设置敏感模块的权限 $SETFACLCMD -d -R -x g:tst_src_1,g:tst_src_2 $project/src/mod1 $project/src/mod2 $SETFACLCMD -R -x g:tst_src_1,g:tst_src_2 $project/src/mod1 $project/src/mod2 $SETFACLCMD -d -R -m g:tst_src_1:rwx $project/src/mod1 $SETFACLCMD -R -m g:tst_src_1:rwx $project/src/mod1 $SETFACLCMD -d -R -m g:tst_src_2:rwx $project/src/mod2 $SETFACLCMD -R -m g:tst_src_2:rwx $project/src/mod2 # 需要权限控制的小组,需要能够进入 tst/src 目录,但只有 root,src 组能够在 src 下创建目录。 # 如此设置,这些用户不能修改 $project/src 目录下面的文件 #$SETFACLCMD -m g:tst_src_1:r-x,g:tst_src_2:r-x $project/src # 所有用户组都能够进入(只读访问) tst, tst/CVSROOT 目录,否则无法访问相应模块。但只有 root 组能够在 根 下创建目录和修改 CVSROOT。 $SETFACLCMD -m g:tst_src:r-x,g:tst_src_1:r-x,g:tst_src_2:r-x,g:tst_doc:r-x $project $SETFACLCMD -R -m g:tst_src:r-x,g:tst_src_1:r-x,g:tst_src_2:r-x,g:tst_doc:r-x $project/CVSROOT } apply_acl_other_project() { # ... ... } #End Declare Function: ############################################################################## ############################################################################## # MAIN Function Here ############################################################################## if [ $# -eq 0 ];then echo format: `basename $0` project ... exit 1 fi cd ${CVSHOMEDIR} while [ $# -gt 0 ]; do project=$1 if [ ! -d $project ]; then echo "Can not find project: $project." shift continue fi ############################################################################## # PRE, 预设置 ############################################################################## echo -n "Begin PRE ACL Setup for project: $project ..." # 设置 ${CVSHOMEDIR}/$project 下文件权限:rwx------, mask 为 rwx; # 添加 cvs 超级用户 cvsroot,cvsroot_ro 权限; $SETFACLCMD -R -m m::rwx,u::rwx,g::---,o::---,d:m::rwx,d:u::rwx,d:g::---,d:o::---,g:cvsroot:rwx,d:g:cvsroot:rwx,g:cvsroot_ro:r-x,d:g:cvsroot_ro:r-x ${CVSHOMEDIR}/$project # 目录属主被子目录集成 chmod -R g+s ${CVSHOMEDIR}/$project # 设置 ${CVSHOMEDIR} 的属主,设置为 nobody.cvsnull,注意最好任何用户不属于 cvsnull; chown -R nobody.cvsnull ${CVSHOMEDIR}/$project echo " done" ############################################################################## # 开始项目的权限设置 ############################################################################## echo -n "Begin ACL Setup for project: $project ..." case "$project" in test*) apply_acl_test $project ;; *) echo "Unknown project name: $project" exit 1 ;; esac echo " done" ############################################################################## # POST, 后设置 ############################################################################## echo -n "Begin POST ACL Setup for project: $project ..." # 将 CVSROOT 目录中的需要完全读写的文件,清除 ACL, 并清除组的粘贴位, ### 还要注意 symbol link 文件的权限也需要恢复,如文件 passwd, blocksender... # 因为 CVSROOT 下脚本需要执行,必须去除其 setgid 位 chmod -R g-s "$project/CVSROOT" # 由于去除了用户属主的保持位, 当用户checkin CVSROOT时?其它用户可能访问不到,因此需要设置组能够访问 # 不能使用 chmod g+rx , 因为对于设置了 ACL, chmod 是修改其 mask $SETFACLCMD -R -m m::rwx,u::rwx,g::rwx,o::r-x,d:m::rwx,d:u::rwx,d:g::rwx,d:o::r-x $project/CVSROOT # val-tags, history 文件需要所有人的读写权限 $SETFACLCMD -b "$project/CVSROOT/val-tags" "$project/CVSROOT/history" chmod 777 "$project/CVSROOT/val-tags" "$project/CVSROOT/history" # commitlogs/ 目录要可写 $SETFACLCMD -b -R "$project/CVSROOT/commitlogs/" chmod -R 777 "$project/CVSROOT/commitlogs/" echo " done" shift done # $SETFACLCMD -b "/etc/passwd.cvs" "/etc/blocksender" # chmod 444 "/etc/passwd.cvs" "/etc/blocksender" echo "" echo "Make sure every one has access right" ls -ld ${CVSHOMEDIR} ls -l "/etc/passwd.cvs" "/etc/blocksender"
Copyright © 2006 WorldHello 开放文档之源 计划 |