├── VERSION ├── .gitignore ├── webroot ├── .gitignore ├── site │ ├── leftside_public.mas │ ├── index.html │ ├── robots.txt │ ├── .gitignore │ ├── favicon.ico │ ├── default_face.png │ ├── decor.mas.template │ ├── style.css │ ├── index_private.mas │ ├── Makefile │ ├── emit_mlkey.html │ ├── emit_vouch.html │ ├── leftside_private.mas │ ├── emit_emerg.html │ ├── emit_pgp.html │ ├── index_public.mas.template │ ├── sitehandler.mas │ ├── list_airports.html │ ├── list_languages.html │ ├── chpw.html │ ├── select_tg.html.template │ ├── mailinglist.html │ ├── nominate.html │ ├── vouch_cp.html │ ├── list_members.html │ ├── edit_second_factor.html │ └── edit_email.html ├── mech │ ├── index.html │ ├── .gitignore │ ├── footer.mas │ ├── expired.html │ ├── leftside.mas.template │ ├── header.mas │ ├── Makefile │ ├── login.html.template │ └── logout.html ├── robots.txt ├── favicon.ico ├── index.html ├── Makefile ├── style.css └── autohandler.template ├── library ├── db_migrations │ ├── .gitignore │ ├── 30-NOV-2013.psql │ ├── per_tg_mail_footer.psql │ ├── 16-SEPT-2013_wiki.psql │ ├── 24-FEB-2014_pgp_expire.psql │ ├── DB_1.psql │ ├── 10-AUG-2013.psql │ ├── 14-SEPT-2013.psql │ ├── Setup_Schema_Version.psql │ ├── DB_4.psql │ ├── 12-OCT-2013_sysadmin.psql │ ├── 11-NOV-openid1.psql │ ├── 28-JAN-2014_2FA_extend.psql │ ├── 29-NOV-2013.psql │ ├── DB_5.psql │ ├── 20-AUG-2013.psql │ ├── 12-AUG-2013.psql │ ├── 09-DEC-2013_2FA.psql │ ├── DB_6.psql │ ├── Makefile │ ├── README_OPS_T_DB.txt │ ├── DB_7.psql │ ├── db_needs_update.sh │ ├── DB_3.psql │ ├── DB_2.psql │ ├── update_db.sh │ └── 12-AUG-2013-1.psql ├── portal.conf.template ├── signature.template ├── .gitignore ├── cronrun-week.sh ├── cronrun-minute.sh ├── remove-from-tg.sh ├── rename-email.sh ├── cronrun-day.sh ├── set-no-mail.sh ├── merge-member.sh ├── new-member.pl ├── fix-install.sh ├── gpgtest.pl ├── extract-sysadmins.sh ├── mh-wrapper.c ├── http-real.inc.template ├── approve.pl ├── funcs.sh ├── chpw.pl ├── notify-idle.pl ├── notify-unvetted.pl ├── report-unvetted.pl ├── Makefile ├── findmember.pl ├── dbck-password.pl ├── fsck-mlkeys.pl ├── tg-maker.sh ├── notify-stuck.pl └── fsck-pgpkeys.pl ├── siteconfig.testing ├── INSTALL.md ├── mycat.pl ├── siteconfig.template ├── Makefile ├── README.md ├── doc └── README.states └── LICENSE /VERSION: -------------------------------------------------------------------------------- 1 | 1.0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | siteconfig 2 | -------------------------------------------------------------------------------- /webroot/.gitignore: -------------------------------------------------------------------------------- 1 | autohandler 2 | -------------------------------------------------------------------------------- /webroot/site/leftside_public.mas: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /webroot/mech/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /webroot/site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /webroot/mech/.gitignore: -------------------------------------------------------------------------------- 1 | login.html 2 | leftside.mas 3 | -------------------------------------------------------------------------------- /webroot/robots.txt: -------------------------------------------------------------------------------- 1 | # go away 2 | User-agent: * 3 | Disallow: / 4 | -------------------------------------------------------------------------------- /webroot/site/robots.txt: -------------------------------------------------------------------------------- 1 | # go away 2 | User-agent: * 3 | Disallow: / 4 | -------------------------------------------------------------------------------- /library/db_migrations/.gitignore: -------------------------------------------------------------------------------- 1 | *.orig 2 | update_db 3 | db_needs_update 4 | -------------------------------------------------------------------------------- /webroot/site/.gitignore: -------------------------------------------------------------------------------- 1 | decor.mas 2 | index_public.mas 3 | select_tg.html 4 | -------------------------------------------------------------------------------- /webroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ops-trust/portal/HEAD/webroot/favicon.ico -------------------------------------------------------------------------------- /webroot/site/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ops-trust/portal/HEAD/webroot/site/favicon.ico -------------------------------------------------------------------------------- /webroot/site/default_face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ops-trust/portal/HEAD/webroot/site/default_face.png -------------------------------------------------------------------------------- /library/db_migrations/30-NOV-2013.psql: -------------------------------------------------------------------------------- 1 | ALTER TABLE member Add PRIMARY KEY (ident); 2 | ALTER TABLE web_sessions Add PRIMARY KEY (id); 3 | 4 | 5 | -------------------------------------------------------------------------------- /library/db_migrations/per_tg_mail_footer.psql: -------------------------------------------------------------------------------- 1 | --Add field for per mailinglist e-mail footer. 2 | ALTER TABLE mailinglist ADD email_footer TEXT; 3 | 4 | -------------------------------------------------------------------------------- /library/portal.conf.template: -------------------------------------------------------------------------------- 1 | # Portal configuration 2 | 3 | PORTAL_DB_HOST=!pghost! 4 | PORTAL_DB_PORT=!pgport! 5 | PORTAL_DB_NAME=!pgname! 6 | 7 | -------------------------------------------------------------------------------- /webroot/mech/footer.mas: -------------------------------------------------------------------------------- 1 | 2 | <& /site/decor.mas:copyright &> 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /library/db_migrations/16-SEPT-2013_wiki.psql: -------------------------------------------------------------------------------- 1 | ; Trustgroups should have the option of having a wiki 2 | ALTER TABLE trustgroup ADD has_wiki boolean DEFAULT false NOT NULL; 3 | -------------------------------------------------------------------------------- /library/signature.template: -------------------------------------------------------------------------------- 1 | [[ !brand! ]]: All message content remains the property of the author 2 | and must not be forwarded or redistributed without explicit permission. 3 | -------------------------------------------------------------------------------- /library/db_migrations/24-FEB-2014_pgp_expire.psql: -------------------------------------------------------------------------------- 1 | --Adding a pgpkey_expire field so that we can act based on key expiration 2 | ALTER TABLE member_email ADD pgpkey_expire TIMESTAMP; 3 | -------------------------------------------------------------------------------- /library/db_migrations/DB_1.psql: -------------------------------------------------------------------------------- 1 | --Schema Version 1 2 | BEGIN; 3 | 4 | -- Just test that we can update the schema version. 5 | UPDATE schema_version set current_version = 2; 6 | COMMIT; 7 | -------------------------------------------------------------------------------- /library/db_migrations/10-AUG-2013.psql: -------------------------------------------------------------------------------- 1 | ALTER TABLE trustgroup ADD target_invouch INTEGER NOT NULL DEFAULT 0; 2 | UPDATE trustgroup set target_invouch = min_invouch; 3 | ALTER TABLE trustgroup ALTER COLUMN target_invouch DROP DEFAULT; 4 | -------------------------------------------------------------------------------- /webroot/site/decor.mas.template: -------------------------------------------------------------------------------- 1 | <%method banner> 2 | 3 |

!title!

4 |
5 | 6 | 7 | <%method copyright> 8 | © !coname! 9 | 10 | 11 | <%method title> 12 | !brand! 13 | 14 | -------------------------------------------------------------------------------- /library/db_migrations/14-SEPT-2013.psql: -------------------------------------------------------------------------------- 1 | ;Adding SQL Schema for CGI Sessions 2 | ;per http://search.cpan.org/~sherzodr/CGI-Session-3.95/Session/PostgreSQL.pm 3 | CREATE TABLE web_sessions ( 4 | id CHAR(32) NOT NULL, 5 | a_session TEXT NOT NULL 6 | ); 7 | GRANT ALL ON TABLE web_sessions TO www,sysadmin; 8 | -------------------------------------------------------------------------------- /library/db_migrations/Setup_Schema_Version.psql: -------------------------------------------------------------------------------- 1 | --Schema Version 0 2 | BEGIN; 3 | 4 | CREATE TABLE schema_version ( 5 | current_version INT NOT NULL DEFAULT 0 6 | ); 7 | 8 | GRANT ALL ON schema_version TO sysadmin; 9 | INSERT into schema_version (current_version) VALUES (1); 10 | COMMIT; 11 | -------------------------------------------------------------------------------- /library/db_migrations/DB_4.psql: -------------------------------------------------------------------------------- 1 | -- Starting Version 4 2 | BEGIN; 3 | -- Create a user for cclark. 4 | create user cclark; 5 | 6 | -- Grant him sysadmin role. 7 | grant sysadmin to cclark; 8 | 9 | -- Set the db version properly. 10 | --Update Version. 11 | UPDATE schema_metadata 12 | SET value = 5 13 | WHERE value = 4 14 | AND key = 'portal_schema_version'; 15 | COMMIT; 16 | 17 | -------------------------------------------------------------------------------- /webroot/site/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #191950; color: #F8F8FF; 3 | font-family: arial, helvetica, sans-serif; 4 | } 5 | 6 | a { 7 | color: yellow; 8 | } 9 | 10 | .note { 11 | color: #F8A000; 12 | font-weight: bold; 13 | font-size: larger; 14 | } 15 | 16 | p.mh { 17 | background-color: #F8F8FF; color: #191950; 18 | text-align: center; 19 | font-variant: small-caps; 20 | font-size: 30px; 21 | } 22 | -------------------------------------------------------------------------------- /library/db_migrations/12-OCT-2013_sysadmin.psql: -------------------------------------------------------------------------------- 1 | ALTER TABLE member ADD sysadmin boolean not null default false; 2 | UPDATE member SET sysadmin = 't' WHERE ident = 'chrismorrow'; 3 | UPDATE member SET sysadmin = 't' WHERE ident = 'jtk'; 4 | UPDATE member SET sysadmin = 't' WHERE ident = 'paulvixie'; 5 | UPDATE member SET sysadmin = 't' WHERE ident = 'ktt'; 6 | UPDATE member SET sysadmin = 't' WHERE ident = 'bapril'; 7 | UPDATE member SET sysadmin = 't' WHERE ident = 'jeroen'; 8 | -------------------------------------------------------------------------------- /webroot/mech/expired.html: -------------------------------------------------------------------------------- 1 |

Expired

2 |

Your session has expired.

3 | 4 | <%method is_public> 5 | # no-op, just here to tell autohandler that this page can be 6 | # accessed without logging first. 7 | 8 | 9 | <%method tg_not_needed> 10 | # tells sitehandler.mas:request_ok that we don't need a trustgroup here 11 | 12 | 13 | <%method vetting_not_needed> 14 | # tells sitehandler.mas:request_ok that we don't need vetting here 15 | 16 | -------------------------------------------------------------------------------- /webroot/mech/leftside.mas.template: -------------------------------------------------------------------------------- 1 | <%perl> 2 | if (defined $Mech->{sess}->param('~logged-in')) { 3 | print $q->code($Mech->{sess}->param('member')), 4 | qq{(logout)
}; 5 | $m->comp('/site/leftside_private.mas'); 6 | } else { 7 | print qq{Login
}; 8 | $m->comp('/site/leftside_public.mas'); 9 | } 10 | 11 | 12 | <%shared> 13 | my $q = undef; 14 | 15 | 16 | <%init> 17 | $q = $Mech->{cgi}; 18 | 19 | -------------------------------------------------------------------------------- /webroot/site/index_private.mas: -------------------------------------------------------------------------------- 1 | <%args> 2 | $next => undef 3 | 4 | 5 | <%perl> 6 | if (!defined $Site->{tg}) { 7 | if (defined $next){ 8 | $m->redirect('/site/select_tg.html?next='.$next); 9 | } else { 10 | $m->redirect('/site/select_tg.html?last=1'); 11 | } 12 | } elsif ($Site->{change_pw}) { 13 | $m->redirect('/site/chpw.html'); 14 | } else { 15 | # XXX probably we should have a dashboard for a home page 16 | $m->redirect('/site/list_members.html'); 17 | } 18 | 19 | -------------------------------------------------------------------------------- /library/db_migrations/11-NOV-openid1.psql: -------------------------------------------------------------------------------- 1 | ; For openid functionality we need to keep track of the associations that are created. 2 | CREATE TABLE openid_associations ( 3 | uuid UUID NOT NULL UNIQUE PRIMARY KEY, 4 | assoc_type TEXT NOT NULL, 5 | session_type TEXT NOT NULL, 6 | mac_key TEXT NOT NULL, 7 | timestamp TIMESTAMP NOT NULL 8 | ); 9 | 10 | GRANT ALL ON openid_associations TO www,sysadmin; 11 | -------------------------------------------------------------------------------- /library/db_migrations/28-JAN-2014_2FA_extend.psql: -------------------------------------------------------------------------------- 1 | ALTER TABLE second_factor_types ADD COLUMN descr TEXT; 2 | 3 | UPDATE second_factor_types SET descr = 'Time based One Time Password - TOPT' WHERE type = 'TOTP'; 4 | UPDATE second_factor_types SET descr = 'HMAC based One Time Password - HOPT' WHERE type = 'HOTP'; 5 | 6 | ALTER TABLE second_factor_types ALTER COLUMN descr SET NOT NULL; 7 | 8 | INSERT INTO second_factor_types VALUES ('SOTP','static single use codes'); 9 | 10 | ALTER TABLE second_factors ADD descr TEXT; 11 | 12 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | *.orig 2 | mh-wrapper 3 | mh2-wrapper 4 | approve 5 | chpw 6 | dbck-password 7 | findmember 8 | fsck-mlkeys 9 | fsck-pgpkeys 10 | gpgtest 11 | mail-handler 12 | mh2 13 | new-member 14 | notify-idle 15 | notify-stuck 16 | notify-unvetted 17 | report-unvetted 18 | state-mon 19 | common.pm 20 | create-main 21 | cronrun-day 22 | cronrun-minute 23 | cronrun-week 24 | extract-sysadmins 25 | fix-install 26 | http-real.inc 27 | merge-member 28 | remove-from-tg 29 | rename-email 30 | tg-maker 31 | signature 32 | set-no-mail 33 | -------------------------------------------------------------------------------- /library/db_migrations/29-NOV-2013.psql: -------------------------------------------------------------------------------- 1 | ; To preven burte forcing we need to keep track of the source IP of login attempts. 2 | CREATE TABLE openid_source_cache ( 3 | src_ip INET NOT NULL UNIQUE PRIMARY KEY, 4 | last_try TIMESTAMP NOT NULL, 5 | attempt_count INT NOT NULL DEFAULT 1 6 | ); 7 | 8 | GRANT ALL ON openid_source_cache TO www,sysadmin; 9 | 10 | ALTER TABLE member ADD login_attempts INT NOT NULL DEFAULT 0; 11 | ALTER TABLE member ADD login_try_begin TIMESTAMP ; 12 | 13 | -------------------------------------------------------------------------------- /library/db_migrations/DB_5.psql: -------------------------------------------------------------------------------- 1 | -- Starting Version 4 2 | BEGIN; 3 | 4 | ALTER TABLE member_email ADD verify_token TEXT; 5 | 6 | ALTER TABLE member ADD recover_email TEXT 7 | CHECK (email_ok(recover_email)); 8 | ALTER TABLE member ADD FOREIGN KEY (ident, recover_email) 9 | REFERENCES member_email (member, email) 10 | ON UPDATE CASCADE;-- Note: no delete-cascade here 11 | 12 | -- Set the db version properly. 13 | --Update Version. 14 | UPDATE schema_metadata 15 | SET value = 6 16 | WHERE value = 5 17 | AND key = 'portal_schema_version'; 18 | COMMIT; 19 | 20 | -------------------------------------------------------------------------------- /webroot/mech/header.mas: -------------------------------------------------------------------------------- 1 | 2 | 3 | <& /site/decor.mas:title &> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | '; 24 | for (my $c = 0; $c < $nc; $c++) { 25 | print ''; 36 | } 37 | print "\n"; 38 | } 39 | print $q->end_table; 40 | 41 | if (defined $iata) { 42 | print $q->hr; 43 | if (!defined $airports->{$iata}) { 44 | print $q->p('No such airport: '.$q->code($q->escapeHTML($iata))); 45 | } else { 46 | my $number = 1 + $#{$airports->{$iata}->{members}}; 47 | my $disp_iata = (length $iata) 48 | ? $q->code($q->escapeHTML($iata)) 49 | : $q->em('No airport specified'); 50 | print $q->h4("$disp_iata ($number)"), $q->start_ul; 51 | foreach my $member (@{$airports->{$iata}->{members}}) { 52 | print $q->li($q->a({-href=>"/site/show_member.html" . 53 | "?member=$member->{ident}"}, 54 | $q->code($member->{ident})) . 55 | ' ('.$q->escapeHTML( 56 | $member->{affiliation}).')'); 57 | } 58 | print $q->end_ul; 59 | } 60 | } 61 | 62 | 63 | <%shared> 64 | my $q = undef; 65 | my $dbh = undef; 66 | my $tg = undef; 67 | my $airports = { }; 68 | my $n_airports = 0; 69 | 70 | 71 | <%init> 72 | $q = $Mech->{cgi}; 73 | $dbh = $Site->{dbh}; 74 | $tg = $Site->{tg}; 75 | 76 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 77 | SELECT m.ident AS ident, m.affiliation AS affiliation, 78 | m.airport AS airport 79 | FROM member m 80 | JOIN member_trustgroup mt ON (ROW(mt.member, mt.trustgroup) = 81 | ROW(m.ident, $tg->{db_ident})) 82 | JOIN member_state ms ON (ms.ident = mt.state) 83 | WHERE NOT ms.hidden 84 | ORDER BY m.entered 85 | }, {Slice => {}})}) { 86 | my $airport = $row->{airport}; 87 | if (defined $airport) { 88 | $airport = $q->escapeHTML($airport); 89 | } else { 90 | $airport = ''; 91 | } 92 | if (!defined $airports->{$airport}) { 93 | $airports->{$airport} = { members => [ ] }; 94 | $n_airports++; 95 | } 96 | push @{$airports->{$airport}->{members}}, { 97 | affiliation => $row->{affiliation}, 98 | ident => $row->{ident} 99 | }; 100 | } 101 | 102 | -------------------------------------------------------------------------------- /library/report-unvetted.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | use strict; 21 | use warnings; 22 | use FindBin; 23 | use lib $FindBin::Bin; 24 | use common; 25 | 26 | $ENV{PATH} .= ':/usr/local/sbin'; 27 | 28 | my $tgname = $ARGV[0] || 'main'; 29 | 30 | my $dbh = &common::get_dbh(); 31 | my $tg = &common::get_tg($dbh, $tgname); 32 | my $db_tgname = $dbh->quote($tgname); 33 | 34 | my $report1 = ''; 35 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 36 | SELECT m.ident, m.affiliation, 37 | (NOW()::DATE - mt.entered::DATE) AS days 38 | FROM member m 39 | JOIN member_trustgroup mt ON (ROW(mt.member, mt.trustgroup) = 40 | ROW(m.ident, $db_tgname)) 41 | WHERE mt.state = 'nominated' 42 | ORDER BY mt.entered; 43 | }, {Slice => {}})}) { 44 | $report1 .= sprintf " %3d %s (%s)\n", 45 | $row->{days}, $row->{ident}, $row->{affiliation}; 46 | } 47 | if (length $report1) { 48 | $report1 = 49 | qq[The following members have been nominated but remain unvetted due to 50 | lack of sufficient vouching from the membership. 51 | 52 | #/days nominee 53 | 54 | $report1 55 | ] ; 56 | } 57 | 58 | my $report2 = ''; 59 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 60 | SELECT mv.vouchor, mv.vouchee 61 | FROM member_vouch mv 62 | WHERE mv.positive 63 | AND mv.trustgroup = $db_tgname 64 | AND mv.entered > (NOW() - '1 day'::INTERVAL) 65 | ORDER BY mv.entered; 66 | }, {Slice => {}})}) { 67 | $report2 .= sprintf " %-35s %-35s\n", 68 | $row->{vouchor}, $row->{vouchee}; 69 | } 70 | if (length $report2) { 71 | $report2 = 72 | qq[The following vouches were recorded in the past 24 hours: 73 | 74 | vouchor vouchee 75 | 76 | $report2 77 | ] ; 78 | } 79 | 80 | if (length $report1 || length $report2) { 81 | my ($vet_email, $vet_comment) = &common::email_addr($tg, 'vetting'); 82 | $_ = &common::email_send($tg, $common::hostmaster, # from 83 | $vet_email, # to 84 | undef, # cc 85 | "$vet_email ($vet_comment)", # reply-to 86 | "nominations report", # subject 87 | \qq{$report1 88 | $report2 89 | Please visit the $common::portal_url web portal if you can vouch 90 | for any of these, or reply to this e-mail if you have any questions 91 | }); 92 | print "$_\n" if defined $_; 93 | } 94 | 95 | exit 0; 96 | -------------------------------------------------------------------------------- /library/Makefile: -------------------------------------------------------------------------------- 1 | # This file is part of the Ops-T Portal. 2 | # 3 | # Copyright 2014 Operations Security Administration, Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | 19 | PERL_SCRIPTS= approve chpw dbck-password findmember \ 20 | fsck-mlkeys fsck-pgpkeys gpgtest mail-handler \ 21 | new-member notify-idle notify-stuck notify-unvetted \ 22 | report-unvetted state-mon 23 | 24 | SHELL_SCRIPTS= cronrun-day cronrun-minute cronrun-week \ 25 | extract-sysadmins fix-install merge-member remove-from-tg \ 26 | rename-email set-no-mail tg-maker 27 | 28 | SCRIPTS= ${PERL_SCRIPTS} ${SHELL_SCRIPTS} 29 | 30 | MADE= portal.conf common.pm http-real.inc signature ${SCRIPTS} 31 | 32 | MADE_SGID= mh-wrapper 33 | 34 | OTHER= schema.psql funcs.sh 35 | 36 | SUBDIRS= db_migrations 37 | 38 | all: ${MADE} ${MADE_SGID} 39 | @set -e; for subdir in ${SUBDIRS}; do \ 40 | ( set -x && cd $$subdir && ${MAKE} ${MARGS} $@ ); \ 41 | done 42 | 43 | clean:; rm -f ${MADE} ${MADE_SGID} 44 | @set -e; for subdir in ${SUBDIRS}; do \ 45 | ( set -x && cd $$subdir && ${MAKE} ${MARGS} $@ ); \ 46 | done 47 | 48 | CFLAGS= -O -Wall 49 | 50 | portal.conf: portal.conf.template ../siteconfig 51 | perl ../mycat.pl ../siteconfig < $@.template > $@ 52 | 53 | common.pm: common.pm.template ../siteconfig 54 | perl ../mycat.pl ../siteconfig < $@.template > $@ 55 | 56 | http-real.inc: http-real.inc.template ../siteconfig 57 | perl ../mycat.pl ../siteconfig < $@.template > $@ 58 | 59 | signature: signature.template ../siteconfig 60 | perl ../mycat.pl ../siteconfig < $@.template > $@ 61 | 62 | mh-wrapper: ../siteconfig mh-wrapper.c Makefile 63 | @echo ${CC} ${CFLAGS} -DWRAPPED=!library!/mail-handler \ 64 | -o $@ mh-wrapper.c | \ 65 | perl ../mycat.pl ../siteconfig | \ 66 | sh -x 67 | 68 | # XXX not installing db_migrations or schema.psql since vixie thinks 69 | # those will always be used from this source directory, not in !library! 70 | install: ${MADE} ${MADE_SGID} ${OTHER} 71 | @( echo mkdir -p !library!; \ 72 | echo install -m 6755 -o !wwwuid! -g !wwwgid! \ 73 | mh-wrapper !library!/; \ 74 | echo install -m 755 -o !wwwuid! -g !wwwgid! \ 75 | ${MADE} !library!/; \ 76 | echo install -m 644 -o !wwwuid! -g !wwwgid! \ 77 | ${OTHER} !library!/; \ 78 | ) | \ 79 | perl ../mycat.pl ../siteconfig | \ 80 | sh -x -e; \ 81 | done 82 | 83 | .SUFFIXES: .pl .sh 84 | 85 | .pl: 86 | @echo $< =\> $@; \ 87 | rm -f $@ && \ 88 | perl ../mycat.pl ../siteconfig < $< > $@ && \ 89 | chmod +x,-w $@ 90 | 91 | .sh: 92 | @echo $< =\> $@; \ 93 | rm -f $@ && \ 94 | perl ../mycat.pl ../siteconfig < $< > $@ && \ 95 | chmod +x,-w $@ 96 | 97 | ${PERL_SCRIPTS} ${SHELL_SCRIPTS}: ../siteconfig Makefile 98 | -------------------------------------------------------------------------------- /webroot/site/list_languages.html: -------------------------------------------------------------------------------- 1 | <%args> 2 | $select => '' 3 | 4 | 5 |

Languages

6 | 7 | <%perl> 8 | my $margin = "5px"; 9 | my $language_total = 0; 10 | my @skills = qw{ native expert intermediate basic }; 11 | 12 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 13 | SELECT l.name,l.iso_639_1,count(mls.member) 14 | FROM languages l, member_language_skill mls 15 | JOIN member_trustgroup mt ON (ROW(mt.member, mt.trustgroup) = 16 | ROW(mls.member, $tg->{db_ident})) 17 | JOIN member_state ms ON (ms.ident = mt.state) 18 | WHERE mls.language = l.iso_639_1 19 | AND NOT ms.hidden 20 | AND NOT ms.blocked 21 | GROUP BY l.name,l.iso_639_1 order by l.name})}) 22 | { 23 | my $language_name = $row->[0]; 24 | my $language_iso = $row->[1]; 25 | my $language_count = $row->[2]; 26 | $language_total += $language_count; 27 | 28 | my $selector = ''; 29 | $selector = "?select=$language_iso" if $language_iso ne $select; 30 | print $q->h5({style => "margin-top: $margin; margin-bottom: $margin;"},$q->a({-href=>$q->url().$selector}, 31 | "[$language_name - $language_iso ]: $language_count")); 32 | if($select eq $language_iso || $select eq 'all') 33 | { 34 | print $q->start_ul({type => 'circle',style => "margin-top: $margin; margin-bottom: $margin;"}); 35 | foreach my $skill (@skills) 36 | { 37 | if(defined $languages{$language_iso}{$skill}) 38 | { 39 | print $q->li($q->h5({style => "margin-top: $margin; margin-bottom: $margin;"},$skill)); 40 | print $q->start_ul({type => 'circle'}); 41 | foreach my $row (@{$languages{$language_iso}{$skill}}) 42 | { 43 | print $q->li($q->a({-href=>"/site/show_member.html" . 44 | "?member=$row->{ident}"}, 45 | $q->code($row->{ident})) . 46 | ' ('.$q->escapeHTML( 47 | $row->{affiliation}).')'); 48 | } 49 | print $q->end_ul; 50 | } 51 | } 52 | print $q->end_ul; 53 | } 54 | } 55 | print $q->h5({style => "margin-top: $margin; margin-bottom: $margin;"},$q->a({-href=>$q->url()."?select=all"}, 56 | "[All ]: $language_total")); 57 | 58 | 59 | <%shared> 60 | my $q = undef; 61 | my $dbh = undef; 62 | my $tg = undef; 63 | my %languages = {}; 64 | 65 | 66 | <%init> 67 | $q = $Mech->{cgi}; 68 | $dbh = $Site->{dbh}; 69 | $tg = $Site->{tg}; 70 | 71 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 72 | SELECT m.ident AS ident, m.affiliation AS affiliation, 73 | mls.language AS language, mls.skill AS skill 74 | FROM member_language_skill mls, member m 75 | JOIN member_trustgroup mt ON (ROW(mt.member, mt.trustgroup) = 76 | ROW(m.ident, $tg->{db_ident})) 77 | JOIN member_state ms ON (ms.ident = mt.state) 78 | WHERE NOT ms.hidden 79 | AND NOT ms.blocked 80 | AND m.ident = mls.member 81 | ORDER BY m.entered 82 | }, {Slice => {}})}) { 83 | if (!defined $languages{$row->{'language'}}) 84 | { 85 | $languages{$row->{'language'}} = {}; 86 | if(!defined $languages{$row->{'language'}}) 87 | { 88 | $languages{$row->{'language'}}{$row->{'skill'}} = []; 89 | } 90 | } 91 | push @{$languages{$row->{'language'}}{$row->{'skill'}}}, $row; 92 | } 93 | 94 | -------------------------------------------------------------------------------- /library/findmember.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | use strict; 21 | use warnings; 22 | use FindBin; 23 | use lib $FindBin::Bin; 24 | use common; 25 | 26 | $| = 1; 27 | my $dbh = &common::get_dbh(); 28 | 29 | my ($search) = @ARGV; 30 | die "usage: $0 search_string" unless defined $search && length $search; 31 | my $db_search = $dbh->quote($search); 32 | 33 | my $save_ident = ''; 34 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 35 | SELECT m.ident, mt.email, mt.trustgroup, mt.state, me.pgpkey_id 36 | FROM member m 37 | JOIN member_trustgroup mt ON (mt.member = m.ident) 38 | INNER JOIN member_email me ON (mt.email = me.email) 39 | WHERE m.ident ~* $db_search 40 | OR m.descr ~* $db_search 41 | OR mt.email ~* $db_search 42 | GROUP BY mt.trustgroup, m.ident, mt.email, mt.state, me.pgpkey_id 43 | ORDER BY m.ident, mt.trustgroup 44 | }, {Slice => {}} )}) { 45 | if ($row->{ident} ne $save_ident) { 46 | $save_ident = $row->{ident}; 47 | my $q_ident = $dbh->quote($save_ident); 48 | 49 | my ($login_attempts, $descr, $uuid) = $dbh->selectrow_array(qq{ 50 | SELECT login_attempts, descr, uuid 51 | FROM member 52 | WHERE ident = $q_ident 53 | }); 54 | 55 | my $sft = ''; 56 | foreach my $sfts (@{$dbh->selectall_arrayref(qq{ 57 | SELECT sf.type AS sft, COUNT(*) AS cnt 58 | FROM member m 59 | JOIN second_factors sf ON (sf.member = m.ident) 60 | WHERE m.ident = $q_ident 61 | GROUP BY sf.type 62 | }, , {Slice => {}} )}) { 63 | $sft .= $sfts->{cnt}."x".$sfts->{sft}." "; 64 | } 65 | $sft = common::trim($sft); 66 | 67 | printf "[%s] '%s' %s LA: %s, SF: %s\n", 68 | $save_ident, $descr, $uuid, 69 | $login_attempts || "none", 70 | $sft || "none"; 71 | } 72 | my $db_ident = $dbh->quote($save_ident); 73 | my $db_trustgroup = $dbh->quote($row->{trustgroup}); 74 | my ($vouchor, $vouch_age) = 75 | $dbh->selectrow_array(qq{ 76 | SELECT mt.member, DATE_TRUNC('days', AGE(mv.entered)) 77 | FROM member_vouch mv 78 | JOIN member m ON (m.ident = mv.vouchor) 79 | JOIN member_trustgroup mt ON ROW(mt.member, mt.trustgroup) = 80 | ROW(m.ident, $db_trustgroup) 81 | JOIN member_email me ON ROW(me.member, me.email) = 82 | ROW(mt.member, mt.email) 83 | WHERE ROW(mv.vouchee, mv.trustgroup) = 84 | ROW($db_ident, $db_trustgroup) 85 | AND mv.positive 86 | ORDER BY mv.entered 87 | LIMIT 1 88 | }); 89 | printf " [%s] <%s> %s %s (%s, %s)\n", 90 | $row->{trustgroup}, $row->{email}, $row->{state}, 91 | $row->{pgpkey_id} || 'NO-PGP', 92 | $vouchor || '""', $vouch_age || '0'; 93 | } 94 | 95 | exit 0; 96 | -------------------------------------------------------------------------------- /webroot/site/chpw.html: -------------------------------------------------------------------------------- 1 | <%args> 2 | $old => '' 3 | $new1 => '' 4 | $new2 => '' 5 | $submit => '' 6 | $form_id => '' 7 | 8 | 9 |

Change Password

10 | <%perl> 11 | $q->delete_all(); 12 | SUBMITTED: while ($submit eq 'Do') { 13 | local($_); 14 | 15 | # $m->abort(403) unless $form_id eq $Mech->{sess}->param('form_id'); 16 | 17 | my $row = $Site->{dbh}->selectrow_hashref(qq{ 18 | SELECT password 19 | FROM member m 20 | WHERE m.ident = $Site->{db_member} 21 | AND m.password IS NOT NULL 22 | }); 23 | if (!defined $row) { 24 | print $q->p("No login information on file?"); 25 | last SUBMITTED; 26 | } 27 | my $oldcrypt = $row->{password}; 28 | if (((!length($oldcrypt)) != (!length($old))) || 29 | $oldcrypt ne crypt($old, $oldcrypt)) 30 | { 31 | print $q->p("Old password mismatch."); 32 | last SUBMITTED; 33 | } 34 | if (!length($new1) || !length($new2)) { 35 | print $q->p("Must enter new password twice."); 36 | last SUBMITTED; 37 | } 38 | if ($new1 ne $new2) { 39 | print $q->p("New password mismatch."); 40 | last SUBMITTED; 41 | } 42 | if ($new1 eq $old) { 43 | print $q->p("Password must be new."); 44 | last SUBMITTED; 45 | } 46 | my $db_newcrypt = $dbh->quote(&common::mkpw_portal($new1)); 47 | my $stmt = qq{ 48 | UPDATE member m SET 49 | password = $db_newcrypt, 50 | change_pw = FALSE 51 | WHERE m.ident = $Site->{db_member} 52 | }; 53 | my $errstr = &common::audited_do($dbh, $Site->{member}, $stmt); 54 | if (length $errstr) { 55 | print $q->p("Failure: $errstr"); 56 | } else { 57 | print $q->p("Password changed."); 58 | &common::password_synch($Site->{member}); 59 | $Site->{change_pw} = 0; 60 | return; 61 | } 62 | last SUBMITTED; 63 | } 64 | 65 |

To change your password, you must enter your existing password (if any), and 66 | then enter your proposed new password twice, and then click the "Do" 67 | button below.

68 | <%perl> 69 | $form_id = &common::new_form_id(); 70 | $Mech->{sess}->param('form_id', $form_id); 71 | $Mech->{sess}->flush(); 72 | print $q->start_form, $q->hidden({-override => 1}, 'form_id', $form_id), 73 | $q->table({-border => 0}, $q->Tr({-align=>'LEFT', -valign=>'MIDDLE'}, [ 74 | $q->td("Old password:") . 75 | $q->td($q->password_field(-name => 'old', 76 | -default => '', 77 | -override => 1, 78 | -size => 10, 79 | -maxlength => 50)), 80 | $q->td("New password:") . 81 | $q->td($q->password_field(-name => 'new1', 82 | -default => '', 83 | -override => 1, 84 | -size => 10, 85 | -maxlength => 50)), 86 | $q->td("Repeat new password:") . 87 | $q->td($q->password_field(-name => 'new2', 88 | -default => '', 89 | -override => 1, 90 | -size => 10, 91 | -maxlength => 50)) 92 | ])), 93 | $q->submit('submit', 'Do'), $q->end_form; 94 | 95 |
96 |

Manage Second-factor Authentication Tokens

97 | 98 | <%shared> 99 | my $q = undef; 100 | my $dbh = undef; 101 | 102 | 103 | <%init> 104 | $q = $Mech->{cgi}; 105 | $dbh = $Site->{dbh}; 106 | 107 | 108 | <%method tg_not_needed> 109 | # tells sitehandler.mas:request_ok that we don't need a trustgroup here 110 | 111 | 112 | <%method vetting_not_needed> 113 | # tells sitehandler.mas:request_ok that we don't need vetting here 114 | 115 | -------------------------------------------------------------------------------- /webroot/site/select_tg.html.template: -------------------------------------------------------------------------------- 1 | <%args> 2 | $tgname => '' 3 | $next => '' 4 | 5 | 6 | <%perl> 7 | my $redirect = undef; 8 | 9 | $tgname = $ftg unless length $tgname || $ntg != 1; 10 | 11 | my $supportre = '!supportre!'; 12 | my $wikire = '!wikire!'; 13 | 14 | $supportre =~ s/\//\\\//g; 15 | $supportre =~ s/\./\\./g; 16 | 17 | $wikire =~ s/\//\\\//g; 18 | $wikire =~ s/\./\\./g; 19 | 20 | my $referrer = $r->header_in('Referer'); 21 | 22 | if($referrer =~ m/openid.return_to=(.*)$/) { 23 | $next = $1; 24 | } 25 | 26 | if ($next =~ /^$supportre/) { 27 | $redirect = $next; 28 | } elsif ($next =~ /^$wikire/) { 29 | $redirect = $next; 30 | $tgname = $1; 31 | } 32 | 33 | my $postscript = ''; 34 | if (length $tgname) { 35 | my $trustgroup = $trustgroups{$tgname}; 36 | my $db_tgname = $dbh->quote($tgname); 37 | if (defined $trustgroup) { 38 | $Mech->{sess}->param('trustgroup', $tgname); 39 | $Mech->{sess}->param('admin', $trustgroup->{admin}); 40 | $Mech->{sess}->param('can_see', $trustgroup->{can_see}); 41 | $Mech->{sess}->param('ntg', $ntg); 42 | $Mech->{sess}->flush(); 43 | $dbh->do(qq{ 44 | UPDATE member_trustgroup 45 | SET activity = NOW()::TIMESTAMP 46 | WHERE ROW(member, trustgroup) = 47 | ROW($Site->{db_member}, $db_tgname); 48 | UPDATE member 49 | SET activity = NOW()::TIMESTAMP 50 | WHERE ident = $Site->{db_member}; 51 | }); 52 | if ($redirect =~ /^$supportre/) { 53 | $m->redirect($redirect); 54 | } elsif ($redirect =~ /^$wikire/) { 55 | $m->redirect($redirect); 56 | } else { 57 | $m->redirect('/'); 58 | } 59 | return; 60 | } 61 | $postscript .= $q->hr(). 62 | $q->p('You are not a member of trustgroup '.$q->code($tgname)); 63 | } 64 | my $printed_header = 0; 65 | foreach $tgname (sort keys %trustgroups) { 66 | if (!$printed_header) { 67 | print $q->h3('Select Trust Group:'), 68 | $q->start_table({-cellpadding=>3, -cellspacing=>3}); 69 | $printed_header = 1; 70 | } 71 | if ($redirect){ 72 | print $q->Tr($q->td($q->a({ 73 | -href=>$q->url().'?tgname='.$tgname.'&next='.$redirect}, 74 | $tgname)) . 75 | $q->td($q->em($trustgroups{$tgname}->{descr}))); 76 | } else { 77 | print $q->Tr($q->td($q->a({ 78 | -href=>$q->url().'?tgname='.$tgname}, $tgname)) . 79 | $q->td($q->em($trustgroups{$tgname}->{descr}))); 80 | } 81 | } 82 | if ($printed_header) { 83 | print $q->end_table; 84 | } else { 85 | $postscript .= $q->hr(). 86 | $q->p('You are not a member of any trustgroup'); 87 | } 88 | print $postscript; 89 | 90 | 91 | <%shared> 92 | my $q = undef; 93 | my $dbh = undef; 94 | my %trustgroups = ( ); 95 | my $ntg = 0; 96 | my $ftg = undef; 97 | 98 | 99 | <%init> 100 | $q = $Mech->{cgi}; 101 | $dbh = $Site->{dbh}; 102 | $ftg = undef; 103 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 104 | SELECT mt.trustgroup AS ident, mt.admin, ms.can_see, tg.descr 105 | FROM member_trustgroup mt 106 | JOIN member_state ms ON (ms.ident = mt.state) 107 | JOIN trustgroup tg ON (mt.trustgroup = tg.ident) 108 | WHERE mt.member = $Site->{db_member} 109 | AND ms.can_login 110 | }, {Slice => {}})}) { 111 | $trustgroups{$row->{ident}} = { 112 | descr => $row->{descr}, 113 | admin => $row->{admin}, 114 | can_see => $row->{can_see} 115 | }; 116 | $ntg++; 117 | $ftg = $row->{ident} unless defined $ftg; 118 | } 119 | 120 | 121 | <%method tg_not_needed> 122 | # tells sitehandler.mas:request_ok that we don't need a trustgroup here 123 | 124 | 125 | <%method vetting_not_needed> 126 | # tells sitehandler.mas:request_ok that we don't need vetting here 127 | 128 | -------------------------------------------------------------------------------- /webroot/site/mailinglist.html: -------------------------------------------------------------------------------- 1 | <%args> 2 | $ml => '' 3 | $submit => '' 4 | $form_id => '' 5 | 6 | 7 |

<%$tg->{shortname}%> Mailing Lists

8 | 9 | <%perl> 10 | $q->delete_all(); 11 | my $postscript = ''; 12 | my $stmts = ''; 13 | my $db_ml = $dbh->quote($ml); 14 | #if (length $submit) { 15 | # $m->abort(403) unless $form_id eq $Mech->{sess}->param('form_id'); 16 | #} 17 | if ($submit eq 'Subscribe') { 18 | $stmts .= qq{ 19 | INSERT INTO member_mailinglist 20 | (member, lhs, trustgroup) 21 | VALUES ($Site->{db_member}, $db_ml, $tg->{db_ident}); 22 | }; 23 | } elsif ($submit eq 'Unsubscribe') { 24 | $stmts .= qq{ 25 | DELETE FROM member_mailinglist mm 26 | WHERE ROW(mm.member, mm.lhs, mm.trustgroup) = 27 | ROW($Site->{db_member}, $db_ml, $tg->{db_ident}); 28 | }; 29 | } 30 | if (length $stmts) { 31 | my $errstr = &common::audited_do($dbh, $Site->{member}, $stmts); 32 | if (length $errstr) { 33 | $postscript .= $q->hr() . $q->p("Failure: $errstr"); 34 | } else { 35 | $postscript .= $q->hr() . $q->p('Profile has been updated.'); 36 | } 37 | } 38 | 39 | my %ml_all = ( ); 40 | foreach (@{$dbh->selectall_arrayref(qq{ 41 | SELECT lhs, descr 42 | FROM mailinglist ml 43 | WHERE ml.can_add_self 44 | AND ml.trustgroup = $tg->{db_ident} 45 | ORDER BY ml.lhs 46 | }, {Slice => {}})}) { 47 | my ($addr, $comment) = &common::email_addr($tg, $_->{lhs}); 48 | $ml_all{$_->{lhs}} = $_->{descr}.' <'.$addr.'>'; 49 | } 50 | my %ml_cur = ( ); 51 | foreach (@{$dbh->selectall_arrayref(qq{ 52 | SELECT lhs 53 | FROM member_mailinglist mm 54 | WHERE ROW(mm.member, mm.trustgroup) = 55 | ROW($Site->{db_member}, $Site->{tg}->{db_ident}) 56 | }, {Slice => {}})}) { 57 | $ml_cur{$_->{lhs}} = ''; 58 | } 59 | 60 | $form_id = &common::new_form_id(); 61 | $Mech->{sess}->param('form_id', $form_id); 62 | $Mech->{sess}->flush(); 63 | 64 | print $q->start_table({-cellpadding=>3, -cellspacing=>3, -rules=>'ROWS'}); 65 | foreach (@{$dbh->selectall_arrayref(qq{ 66 | SELECT ml.lhs AS lhs, ml.descr AS descr 67 | FROM mailinglist ml 68 | WHERE ml.trustgroup = $Site->{tg}->{db_ident} 69 | AND NOT ml.virtual 70 | ORDER BY ml.lhs 71 | }, {Slice => {}})}) { 72 | my ($addr, $comment) = &common::email_addr($tg, $_->{lhs}); 73 | my $descr = $q->escapeHTML($_->{descr}); 74 | my $actions = $q->start_form . 75 | $q->hidden({-override => 1}, 'form_id', $form_id) . "\t\t" . 76 | $q->hidden(-name=>'ml', -default=>$_->{lhs}, -override=>1) . 77 | "\n\t\t" . $q->submit('submit', 'View') . "\n"; 78 | if (defined $ml_all{$_->{lhs}}) { 79 | $actions .= ' ' . 80 | $q->submit('submit', (defined $ml_cur{$_->{lhs}} 81 | ? 'Unsubscribe' : 'Subscribe')); 82 | } 83 | $actions .= $q->end_form; 84 | print $q->Tr( 85 | $q->td({-align=>'RIGHT'}, $q->code($addr)) . 86 | $q->td('') . "\n\t" . 87 | $q->td($descr) . 88 | $q->td('') . "\n\t" . 89 | $q->td($q->a({href=>"/site/emit_mlkey.html?lhs=$_->{lhs}"}, 90 | 'PGP Key')) . 91 | $q->td('') . "\n\t" . 92 | $q->td({-valign=>'MIDDLE'}, $actions) 93 | ), "\n"; 94 | } 95 | print $q->end_table, $postscript; 96 | 97 | if ($submit eq 'View') { 98 | print $q->h5($q->code($ml).':'); 99 | my $db_ml = $dbh->quote($ml); 100 | print $q->start_pre; 101 | foreach (common::find_recipients($dbh, $tg, $ml)) { 102 | printf "\"%s\" <%s>\n", 103 | $q->escapeHTML($_->{descr}), $_->{email}; 104 | } 105 | print $q->end_pre; 106 | } 107 | 108 | 109 | <%shared> 110 | my $q = undef; 111 | my $dbh = undef; 112 | my $tg = undef; 113 | 114 | 115 | <%init> 116 | $q = $Mech->{cgi}; 117 | $dbh = $Site->{dbh}; 118 | $tg = $Site->{tg}; 119 | 120 | -------------------------------------------------------------------------------- /library/dbck-password.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | use strict; 21 | use warnings; 22 | use FindBin; 23 | use lib $FindBin::Bin; 24 | use common; 25 | use Text::Wrap; 26 | 27 | my $dbh = &common::get_dbh(); 28 | 29 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 30 | SELECT m.ident, mt.trustgroup, mt.email 31 | FROM member m 32 | JOIN member_trustgroup mt ON (mt.member = m.ident) 33 | JOIN member_state ms ON (ms.ident = mt.state) 34 | WHERE m.password IS NULL 35 | AND ms.can_login 36 | GROUP BY m.ident, mt.trustgroup, mt.email 37 | }, {Slice => {}})}) { 38 | my $db_trustgroup = $dbh->quote($row->{trustgroup}); 39 | my $db_ident = $dbh->quote($row->{ident}); 40 | my $tg = &common::get_tg($dbh, $row->{trustgroup}); 41 | my ($vouchor_email, $vouchor_uuid, $pgpkey_id) = 42 | $dbh->selectrow_array(qq{ 43 | SELECT mt.email, m.uuid, me.pgpkey_id 44 | FROM member_vouch mv 45 | JOIN member m ON (m.ident = mv.vouchor) 46 | JOIN member_trustgroup mt ON ROW(mt.member, mt.trustgroup) = 47 | ROW(m.ident, $db_trustgroup) 48 | JOIN member_email me ON ROW(me.member, me.email) = 49 | ROW(mt.member, mt.email) 50 | WHERE ROW(mv.vouchee, mv.trustgroup) = 51 | ROW($db_ident, $db_trustgroup) 52 | AND mv.positive 53 | AND me.pgpkey_id IS NOT NULL 54 | ORDER BY mv.entered 55 | LIMIT 1 56 | }); 57 | my $password = join('', 58 | ('.', '/', '0'..'9', 'A'..'Z', 'a'..'z') 59 | [rand 64, rand 64, rand 64, rand 64, 60 | rand 64, rand 64, rand 64, rand 64]); 61 | my $db_newcrypt = $dbh->quote(&common::mkpw_portal($password)); 62 | my $stmt = qq{ UPDATE member 63 | SET password = $db_newcrypt 64 | WHERE ident = $db_ident }; 65 | $dbh->do($stmt); 66 | #debug print "$row->{ident}: $vouchor_email ($vouchor_uuid) [$pgpkey_id]\n"; 67 | my ($size, $error) = &common::email_send_pgp($tg, $common::hostmaster, 68 | $vouchor_email, # recip email 69 | $vouchor_uuid, # recip uuid 70 | $pgpkey_id, # pgpkey id 71 | undef, # cc 72 | undef, # reply-to 73 | "New password for $row->{ident}", # subject 74 | Text::Wrap::fill('', '', ( 75 | qq{A new password has been assigned for $row->{ident} and as the vouchor 76 | or oldest nominator having a PGP key, you are hereby deputized to inform 77 | $row->{ident} of this password. Please use a non-plaintext delivery method 78 | such as in-person, telephone, or encrypted e-mail. Also please remind 79 | $row->{ident} that since you know their current password, they should set a 80 | new one, and ask them to review their contact information including their own 81 | PGP key as well as their portrait, closest airport, and so on. The web portal 82 | is located at $common::domain as before. 83 | 84 | The new (temporary) password for $row->{ident} is: $password 85 | 86 | Please do not print or save this e-mail. 87 | 88 | Sincerely yours, 89 | 90 | $tg->{descr} HOSTMASTER 91 | } )) # body 92 | ); 93 | # we don't look at $size since $error is always empty when 94 | # there has been no failure, but we want to see warnings. 95 | print STDERR $error if defined $error; 96 | } 97 | 98 | exit 0; 99 | -------------------------------------------------------------------------------- /doc/README.states: -------------------------------------------------------------------------------- 1 | (note: this is out of date, see state_mon.pl and schema.psql for truth.) 2 | 3 | on this table the rows are membership states and the columns are member 4 | capabilities/permissions. 5 | 6 | state | can_login | can_see | can_send | can_recv | blocked | hidden 7 | -----------+-----------+---------+----------+----------+---------+-------- 8 | nominated | false | false | false | false | false | false 9 | vetted | false | false | false | false | false | false 10 | approved | true | true | false | false | false | false 11 | active | true | true | true | true | false | false 12 | inactive | true | true | true | false | false | false 13 | blocked | false | false | false | false | true | true 14 | failed | false | false | false | false | false | true 15 | soonidle | true | true | true | true | false | false 16 | idle | true | true | true | false | false | false 17 | deceased | false | false | false | false | true | false 18 | 19 | permissions: 20 | 21 | "can_login" means your password works at the main web portal UI. 22 | "can_see" means you can see the membership list and other primary 23 | materials, including the wiki. 24 | "can_send" means you're allowed to send mail to the non-public-access 25 | mailing lists. 26 | "can_recv" means you can receive mail to the subscription-checkbox 27 | mailing lists. 28 | "blocked" means you can't be nominated, nor log in, nor receive or 29 | send e-mail, nor be seen. 30 | 31 | states: 32 | 33 | "nominated" means somebody has nominated you but you don't know yet. 34 | "vetted" means you've been invouched and you still don't know about it. 35 | "approved" will someday mean that admin@ has noted your vettedness and 36 | noted the absence of controversy about you. right now you just 37 | go from vetted to approved immediately (criteria is identical.) 38 | "active" means you've done everything you need to do and the system 39 | is not sending you any annoy-o-grams about your checklist. 40 | "inactive" means you used to be approved but lost your pgp key or lost 41 | a vouch or the vouch criteria was raised and now excludes you. 42 | "blocked" means somebody negvouched you and there's an investigation. 43 | "idle" means it's been X days (imagine "60") since you either 44 | logged into the UI or sent e-mail to one of the lists. 45 | "soonidle" means you will soon be "idle" (we send mail warning of this 46 | so that you can log into the portal and prevent going idle.) 47 | "failed" means your nomination timed out without reaching "vetted". 48 | 49 | transitions: 50 | 51 | NULL -> nominated (when somebody nominates you, and mail is sent to 52 | vetting@ asking that folks check you out) 53 | nominated -> vetted (when a cron job detects that you have enough 54 | invouches (target_invouches), and notifies admin@ about this) 55 | vetted -> approved (when an admin notes that there are no negvouches 56 | and manually slots you into "approved" status, and you finally 57 | hear for the first time that you are a member, or if that's 58 | not implemented yet, it's when a cron job notices that you've 59 | been vetted and automatically approves you) 60 | approved -> active (when a cron job detects that you're approved but 61 | that you need to input a pgp (if that's required) and outvouch 62 | (if that's required). 63 | active -> inactive (when you lose your pgp key or it's suddenly required, 64 | or when you used to have enough invouches (min_invouches) but now 65 | you don't.) 66 | inactive -> active (when a cron job detects that you've outvouched and 67 | input a pgp key, and notifies by e-mail you about this) 68 | ANY -> blocked (when an admin wants the system to camp onto your e-mail 69 | address and not allow further state changes or new nominations) 70 | active -> soonidle (when a cron job detects that you have not logged 71 | in or sent mail for some significant period of time, and sends 72 | you mail telling you that you will soon be idle.) 73 | soonidle -> active (when you log back into the UI or transmit to a 74 | mailinglist.) 75 | soonidle -> idle (when you go a few more days without activity after 76 | being told you will soon be idle) 77 | idle -> active (same as soonidle -> active) 78 | -------------------------------------------------------------------------------- /library/fsck-mlkeys.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | 21 | use strict; 22 | use warnings; 23 | use FindBin; 24 | use lib $FindBin::Bin; 25 | use common; 26 | use Fcntl qw(:flock LOCK_EX LOCK_NB); 27 | 28 | my $dbh = &common::get_dbh(); 29 | 30 | my $mll = {}; 31 | # 32 | # Find all Mailing-lists, generate any missing keys. 33 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 34 | SELECT lhs,descr,trustgroup,pubkey,seckey FROM mailinglist 35 | }, { Slice=>{} } )}) { 36 | my $ml = &common::email_ml_lhs($row->{trustgroup}, $row->{lhs}); 37 | $mll->{$ml} = $row; 38 | if (!&common::gpg_mlkey_present($dbh,$row->{lhs},$row->{trustgroup})){ 39 | &generate_ml_key($dbh,$ml,$row); 40 | } 41 | } 42 | 43 | my $lf = undef; 44 | open($lf, $common::ml_keys) || die "open $common::ml_keys: $!"; 45 | flock($lf, LOCK_EX | LOCK_NB) || die "flock $common::ml_keys: $!"; 46 | 47 | # 48 | # Find all list/keys already on disk. 49 | my $ml_keys = {}; 50 | my $mlkdir = undef; 51 | opendir($mlkdir, $common::ml_keys) || die "opendir: $!"; 52 | while ($_ = readdir($mlkdir)) { 53 | next if $_ eq '.' || $_ eq '..'; 54 | next if $_ eq 'random_seed'; 55 | if (! /\.(gpg|secring)~?$/) { 56 | print "mystery file: $_\n"; 57 | } elsif (-z $_) { 58 | print "zero length file: $_\n"; 59 | } else { 60 | $ml_keys->{$`} = undef; 61 | } 62 | } 63 | closedir($mlkdir); 64 | 65 | # 66 | # Removing old/stale files for keys. 67 | foreach my $ml_key (keys %{$ml_keys}) { 68 | next if $ml_key eq 'trustdb' || 69 | $ml_key eq 'pubring' || 70 | $ml_key eq 'secring'; 71 | if (!exists $mll->{$ml_key}) { 72 | print "removing listless list key: $ml_key\n"; 73 | unlink $common::ml_keys.'/'.$ml_key.'.gpg'; 74 | unlink $common::ml_keys.'/'.$ml_key.'.gpg~'; 75 | unlink $common::ml_keys.'/'.$ml_key.'.secring'; 76 | next; 77 | } 78 | } 79 | 80 | flock $lf, LOCK_UN; 81 | close $lf; 82 | $lf = undef; 83 | 84 | exit 0; 85 | 86 | sub generate_ml_key() { 87 | my ($dbh,$ml,$row) = @_; 88 | print "generating list key for: $ml\n"; 89 | my $gpg = undef; 90 | open($gpg, "| /usr/bin/gpg --homedir $common::ml_keys " . 91 | '--batch --no-secmem-warning ' . 92 | '--no-permission-warning ' . 93 | '--gen-key 2>&1') || die; 94 | print {$gpg} join("\n", 95 | qq{Key-Type: DSA}, 96 | qq{Key-Length: 1024}, 97 | qq{Subkey-Type: ELG-E}, 98 | qq{Subkey-Length: 2048}, 99 | qq{Name-Real: $row->{lhs}}, 100 | qq{Name-Comment: $row->{descr}}, 101 | qq{Name-Email: $ml\@$common::domain}, 102 | qq{Expire-Date: 0}, 103 | qq{\%pubring $common::ml_keys/$ml.gpg}, 104 | qq{\%secring $common::ml_keys/$ml.secring}); 105 | close($gpg); 106 | unlink $common::ml_keys.'/pubring.gpg'; 107 | chown $common::www_uid, $common::www_gid, 108 | $common::ml_keys.'/'.$ml.'.gpg', 109 | $common::ml_keys.'/'.$ml.'.secring'; 110 | my $secring_fh = undef; 111 | open($secring_fh,'gpg --export-secret-keys --armor '. 112 | '--homedir '.$common::ml_keys. 113 | ' --secret-keyring '.$common::ml_keys."/$ml.secring |"); 114 | my $seckey = ''; 115 | while (read($secring_fh, my $buffer, 4096)) { 116 | $seckey .= $buffer; 117 | } 118 | close($secring_fh); 119 | my $db_sec = $dbh->quote($seckey); 120 | my $pubring_fh = undef; 121 | open($pubring_fh,'gpg --export --armor --homedir '. 122 | $common::ml_keys.' --keyring '. 123 | $common::ml_keys."/$ml.gpg |"); 124 | my $pubkey = ""; 125 | while (read($pubring_fh, my $buffer, 4096)) { 126 | $pubkey .= $buffer; 127 | } 128 | close($pubring_fh); 129 | my $db_pub = $dbh->quote($pubkey); 130 | my $db_tg = $dbh->quote($row->{trustgroup}); 131 | my $db_lhs = $dbh->quote($row->{lhs}); 132 | 133 | $dbh->do( qq{ 134 | UPDATE mailinglist 135 | SET seckey = $db_sec, 136 | pubkey = $db_pub, 137 | key_update_at = now() 138 | WHERE trustgroup = $db_tg 139 | AND lhs = $db_lhs}); 140 | } 141 | -------------------------------------------------------------------------------- /library/tg-maker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | # Note that the for loop below is questions being asked and expected type() 21 | # of response. Valid types are: 22 | # s == string - this is a string 23 | # n == number - 12 24 | # i == interval - 7 days 25 | # b == boolean - true 26 | # 27 | # The end result of answering question is one large transactional sql statement. 28 | # The caller of this script should copy/paste it into: 29 | # psql -U sysadmin -h ops-trust 30 | # 31 | 32 | cd $(dirname $0) || exit 1 33 | 34 | . ./funcs.sh 35 | 36 | echo << EOT 37 | Note that the for loop below is questions being asked and expected type() 38 | of response. Valid types are: 39 | s == string - this is a string 40 | n == number - 12 41 | i == interval - 7 days 42 | b == boolean - true 43 | EOT 44 | 45 | for v in tgname/s descr/s shortname/s min_invouch/n target_invouch/n \ 46 | min_outvouch/n max_inactivity/i idle_guard/i pgp_required/b \ 47 | please_vouch/b vouch_adminonly/b nom_enabled/b can_time_out/b \ 48 | max_vouchdays/n has_wiki/b 49 | do 50 | IFS=/; set $v 51 | read -p "$1 ($2): " v 52 | echo "-- $v" 53 | case $2 in 54 | s) eval $1=\"\'$v\'\" ;; 55 | i) eval $1=\"\'$v\'::INTERVAL\" ;; 56 | n) eval $1=\"`expr 0 + $v`\" ;; 57 | b) eval $1=\"$v\" ;; 58 | esac 59 | done 60 | 61 | initmemb='' 62 | while read -p "Initial member ident (or ^D): " v; do 63 | echo "-- $v" 64 | initmemb="$initmemb $v" 65 | done 66 | 67 | echo 'BEGIN;' 68 | 69 | cat <<:EOF: 70 | INSERT INTO trustgroup 71 | (ident, descr, shortname, min_invouch, target_invouch, 72 | min_outvouch, max_inactivity, pgp_required, please_vouch, 73 | nom_enabled, vouch_adminonly, can_time_out, max_vouchdays, 74 | idle_guard, has_wiki) 75 | VALUES 76 | ($tgname, $descr, $shortname, $min_invouch, $target_invouch, 77 | $min_outvouch, $max_inactivity, $pgp_required, $please_vouch, 78 | $nom_enabled, $vouch_adminonly, $can_time_out, $max_vouchdays, 79 | $idle_guard, $has_wiki); 80 | 81 | INSERT INTO attestations 82 | (ident, descr, trustgroup) 83 | VALUES 84 | ('met', 'I have met them in person more than once.', $tgname); 85 | INSERT INTO attestations 86 | (ident, descr, trustgroup) 87 | VALUES 88 | ('trust', 'I trust them to take action.', $tgname); 89 | INSERT INTO attestations 90 | (ident, descr, trustgroup) 91 | VALUES 92 | ('fate', 'I will share membership fate with them.', $tgname); 93 | 94 | INSERT INTO mailinglist 95 | (lhs, descr, members_only, can_add_self, automatic, trustgroup, 96 | virtual) 97 | VALUES 98 | ('admin', 'TG Administration', false, false, false, $tgname, 99 | true); 100 | INSERT INTO mailinglist 101 | (lhs, descr, members_only, can_add_self, automatic, trustgroup) 102 | VALUES 103 | ('general', 'General Discussion', true, true, true, $tgname); 104 | INSERT INTO mailinglist 105 | (lhs, descr, members_only, can_add_self, automatic, trustgroup) 106 | VALUES 107 | ('vetting', 'Vetting and Vouching', true, true, true, $tgname); 108 | :EOF: 109 | 110 | IFS=' '; set $initmemb 111 | for v1; do 112 | cat <<:EOF: 113 | INSERT INTO member_trustgroup 114 | (member, trustgroup, email, admin, state) 115 | SELECT '$v1' AS member, 116 | $tgname AS trustgroup, 117 | (SELECT email FROM member_email WHERE member = '$v1' LIMIT 1) AS email, 118 | TRUE AS admin, 119 | 'active' AS state; 120 | INSERT INTO member_mailinglist 121 | (member, lhs, trustgroup) 122 | VALUES 123 | ('$v1', 'general', $tgname); 124 | INSERT INTO member_mailinglist 125 | (member, lhs, trustgroup) 126 | VALUES 127 | ('$v1', 'vetting', $tgname); 128 | :EOF: 129 | done 130 | 131 | IFS=' '; set $initmemb 132 | for v1; do 133 | IFS=' '; set $initmemb 134 | for v2; do 135 | if [ $v1 != $v2 ]; then 136 | cat <<:EOF: 137 | INSERT INTO member_vouch 138 | (vouchor, vouchee, comment, trustgroup, positive) 139 | VALUES 140 | ('$v1', '$v2', '', $tgname, true); 141 | :EOF: 142 | fi 143 | done 144 | done 145 | 146 | echo 'COMMIT;' 147 | 148 | exit 149 | -------------------------------------------------------------------------------- /webroot/site/nominate.html: -------------------------------------------------------------------------------- 1 | <%args> 2 | $email => '' 3 | $descr => '' 4 | $bio_info => '' 5 | $attestation => '' 6 | @attestations => () 7 | $submit => '' 8 | $form_id => '' 9 | 10 | 11 |

Nominate and Vouch

12 | <%perl> 13 | $q->delete_all(); 14 | my $postscript = ''; 15 | $email =~ tr/A-Z/a-z/; 16 | $email = &common::ExtractAddr($email); 17 | my $db_email = $dbh->quote($email); 18 | my $existing = 0; 19 | my $ident; 20 | if (length $email) { 21 | my $row = $dbh->selectrow_hashref(qq{ 22 | SELECT m.ident, m.descr, m.bio_info 23 | FROM member m 24 | JOIN member_email me ON me.member = m.ident 25 | WHERE me.email = $db_email; 26 | }); 27 | if (defined $row) { 28 | ($ident, $descr, $bio_info) = 29 | ($row->{ident}, $row->{descr}, $row->{bio_info}); 30 | $existing = (defined $ident && 31 | defined $descr && 32 | defined $bio_info); 33 | } 34 | # XXX this way of checking for blocked members is shitcrockful 35 | # in that it does not handle any reasonable sort of antialiasing. 36 | my $db_ident = $dbh->quote($ident); 37 | $row = $dbh->selectrow_hashref(qq{ 38 | SELECT COUNT(*) AS count 39 | FROM member_trustgroup mt 40 | JOIN member_state ms ON (ms.ident = mt.state) 41 | WHERE ROW(mt.member, mt.trustgroup) = 42 | ROW($db_ident, $tg->{db_ident}) 43 | AND ms.blocked 44 | }); 45 | $blocked = ($row->{count} != 0); 46 | if ($blocked) { 47 | $postscript .= $q->p($q->code("$email ($descr)") . qq{ 48 | blocked, talk to admin@ 49 | for more information.} 50 | ); 51 | } 52 | } 53 | 54 | #if (length $submit) { 55 | # $m->abort(403) unless $form_id eq $Mech->{sess}->param('form_id'); 56 | #} 57 | 58 | if ($submit eq 'Nominate') { 59 | my %attestations = map { $_ => '' } @attestations; 60 | my $unchecked = 0; 61 | map { $unchecked++ unless defined $attestations{$_} } keys %$att; 62 | if ((!length $email) || (!length $descr) || (!length $attestation) || 63 | $unchecked != 0) 64 | { 65 | $postscript .= $q->hr . $q->p('All fields are required.'); 66 | } else { 67 | my $affiliation = $email; 68 | $affiliation =~ s/^[^@]+@/@/; 69 | my $msg = &common::new_member($dbh, $tg, $Site->{member}, 70 | $email, $descr, $bio_info, 71 | $affiliation, $attestation); 72 | $msg =~ s/[\;\.]\s+/$1

/go; 73 | $postscript .= $q->hr . $msg; 74 | } 75 | } else { 76 | $form_id = &common::new_form_id(); 77 | $Mech->{sess}->param('form_id', $form_id); 78 | $Mech->{sess}->flush(); 79 | print $q->start_form, 80 | $q->hidden({-override => 1}, 'form_id', $form_id), 81 | $q->start_table({-border => 0}); 82 | if ($submit eq '') { 83 | print $q->Tr({-align=>'LEFT', -valign=>'MIDDLE'}, [ 84 | $q->td('E-mail address of nominee:') . 85 | $q->td($q->textfield(-name => 'email', 86 | -size => 65, 87 | -maxlength => 100)) 88 | ]); 89 | $submit = 'Search'; 90 | } elsif ($existing && !$blocked) { 91 | print $q->Tr({-align=>'LEFT', -valign=>'MIDDLE'}, [ 92 | $q->td(['E-mail address:', $q->code($email)]), 93 | $q->td(['Full name:', $q->code($descr)]), 94 | $q->td(['Biography:', $q->code($bio_info)]) 95 | ]); 96 | print $q->hidden('email', $email), 97 | $q->hidden('descr', $descr), 98 | $q->hidden('bio_info', $bio_info); 99 | $submit = 'Nominate'; 100 | } elsif (!$blocked) { 101 | print $q->Tr({-align=>'LEFT', -valign=>'MIDDLE'}, [ 102 | $q->td(['E-mail address:', $q->code($email)]), 103 | $q->td('Full name:') . 104 | $q->td($q->textfield(-name => 'descr', 105 | -size => 65, 106 | -maxlength => 100)), 107 | $q->td('Biography:') . 108 | $q->td($q->textarea(-name => 'bio_info', 109 | -rows => 5, 110 | -columns => 65)) 111 | ]); 112 | print $q->hidden('email', $email); 113 | $submit = 'Nominate'; 114 | } 115 | if ($submit ne 'Search' && !$blocked) { 116 | print $q->Tr({-align=>'LEFT', -valign=>'MIDDLE'}, [ 117 | $q->td({-valign=>'TOP', -align=>'LEFT'}, 118 | qq{Attestation: 119 |

(do not say
120 | know-met-trust!)
}) . 121 | $q->td({-valign=>'TOP', -align=>'LEFT'}, 122 | $q->textarea(-name => 'attestation', 123 | -rows => 5, 124 | -columns => 65) . 125 | qq{
Explain how you know, 126 | when you met, and why you trust.}), 127 | $q->td({-align=>'CENTER'}, 128 | '

(must
check
all
boxes)

') . 129 | $q->td($q->checkbox_group(-name => 'attestations', 130 | -values => [keys %$att], 131 | -default => [ ], 132 | -columns => 1, 133 | -labels => $att)) 134 | ]); 135 | } 136 | print $q->end_table; 137 | print $q->p . $q->submit('submit', $submit) unless $blocked; 138 | print $q->end_form; 139 | } 140 | print $postscript; 141 | 142 | 143 | <%shared> 144 | my $q = undef; 145 | my $dbh = undef; 146 | my $tg = undef; 147 | my $att = {}; 148 | my $blocked = undef; 149 | 150 | 151 | <%init> 152 | $q = $Mech->{cgi}; 153 | $dbh = $Site->{dbh}; 154 | $tg = $Site->{tg}; 155 | if (!$Site->{admin} && !$tg->{nom_enabled}) { 156 | print $q->h3('Only admins can nominate in this trustgroup'); 157 | return; 158 | } 159 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 160 | SELECT ident, descr 161 | FROM attestations 162 | WHERE trustgroup = $tg->{db_ident} 163 | }, {Slice => {}})}) { 164 | $att->{$row->{ident}} = $row->{descr}; 165 | } 166 | 167 | -------------------------------------------------------------------------------- /library/notify-stuck.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | # -s: -debug 21 | our $debug = $debug if defined $debug; 22 | 23 | # notify-stuck -- send e-mail to nominator for each nominee who is 24 | # still approved (and therefore not yet active) 25 | 26 | use strict; 27 | use warnings; 28 | use FindBin; 29 | use lib $FindBin::Bin; 30 | use common; 31 | use Text::Wrap; 32 | 33 | $ENV{PATH} .= ':/usr/local/sbin'; 34 | 35 | my $dbh = &common::get_dbh(); 36 | my %tgh = (); 37 | 38 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 39 | SELECT mt.trustgroup, m.ident, m.descr, mt.state, me.email, 40 | DATE_TRUNC('days', AGE(mt.entered)) AS entered_age, 41 | DATE_TRUNC('days', AGE(mt.activity)) AS activity_age, 42 | me.pgpkey_id IS NOT NULL AS haspgp, 43 | COALESCE(invouch.num, 0) AS inv, 44 | COALESCE(outvouch.num, 0) AS outv 45 | FROM member m 46 | JOIN member_trustgroup mt ON mt.member = m.ident 47 | JOIN member_email me 48 | ON ROW(me.member, me.email) = ROW(mt.member, mt.email) 49 | LEFT OUTER JOIN ( 50 | SELECT mv.vouchee, mv.trustgroup, COUNT(mv.vouchee) AS num 51 | FROM member_vouch mv 52 | WHERE mv.positive 53 | GROUP BY mv.vouchee, mv.trustgroup 54 | ) AS invouch 55 | ON ROW(invouch.vouchee, invouch.trustgroup) = 56 | ROW(m.ident, mt.trustgroup) 57 | LEFT OUTER JOIN ( 58 | SELECT mv.vouchor, mv.trustgroup, COUNT(mv.vouchor) AS num 59 | FROM member_vouch mv 60 | WHERE mv.positive 61 | GROUP BY mv.vouchor, mv.trustgroup 62 | ) AS outvouch 63 | ON ROW(outvouch.vouchor, outvouch.trustgroup) = 64 | ROW(m.ident, mt.trustgroup) 65 | WHERE mt.state IN ('nominated', 'approved') 66 | AND DATE_TRUNC('days', AGE(mt.entered)) > '10 days'::INTERVAL 67 | AND DATE_TRUNC('days', AGE(mt.activity)) > '5 days'::INTERVAL 68 | ORDER BY mt.trustgroup 69 | }, {Slice => {}})}) { 70 | # maintain a local cache of trustgroup refs 71 | my $tgname = $row->{trustgroup}; 72 | $tgh{$tgname} = &common::get_tg($dbh, $tgname) 73 | unless defined $tgh{$tgname}; 74 | die "odd trustgroup $tgname" unless defined $tgh{$tgname}; 75 | my $tg = $tgh{$tgname}; 76 | 77 | ¬ify($dbh, $tg, @$row{qw[ 78 | ident descr state email 79 | entered_age activity_age 80 | haspgp inv outv 81 | ]}); 82 | } 83 | 84 | exit 0; 85 | 86 | sub notify { 87 | my ($dbh, $tg, $ident, $descr, $state, $email, 88 | $entered_age, $activity_age, 89 | $haspgp, $inv, $outv) = @_; 90 | 91 | $descr =~ s/"//go; 92 | my $db_ident = $dbh->quote($ident); 93 | my ($vouchor, $vouchor_descr, $vouchor_email) = 94 | $dbh->selectrow_array(qq{ 95 | SELECT mv.vouchor, m.descr, mt.email 96 | FROM member_vouch mv 97 | JOIN member m ON (mv.vouchor = m.ident) 98 | JOIN member_trustgroup mt ON 99 | ROW(mt.member, mt.trustgroup) = 100 | ROW(m.ident, $tg->{db_ident}) 101 | WHERE ROW(mv.vouchee, mv.trustgroup) = 102 | ROW($db_ident, $tg->{db_ident}) 103 | AND mv.positive 104 | ORDER BY mv.entered 105 | LIMIT 1 106 | }); 107 | return unless defined $vouchor; 108 | 109 | my $body = Text::Wrap::fill('', '', ( 110 | qq{$ident ($descr) was nominated 111 | $entered_age ago 112 | to the $tg->{descr} 113 | by $vouchor ($vouchor_descr) 114 | but appears to be stuck in the '$state' state 115 | and has been stuck there for $activity_age. 116 | } ))."\n"; 117 | 118 | if ($tg->{pgp_required} && !$haspgp) { 119 | $body .= qq{\nA PGP key is required for this trust group.\n}; 120 | } 121 | 122 | if ($inv < $tg->{target_invouch}) { 123 | my $n = $tg->{target_invouch} - $inv; 124 | my $s = ($n == 1) ? "" : "s"; 125 | my $t = sprintf "%d more member%s", $n, $s; 126 | $body .= qq{\nAt least ${t} need to vouch for this nominee.\n}; 127 | } 128 | 129 | if ($outv < $tg->{min_outvouch}) { 130 | my $n = $tg->{min_outvouch} - $outv; 131 | my $s = ($n == 1) ? "" : "s"; 132 | my $t = sprintf "%d more member%s", $n, $s; 133 | $body .= qq{\nThis nominee needs to vouch for at least ${t}.\n}; 134 | } 135 | 136 | my $cc = undef; 137 | if ($tg->{ident} ne 'main') { 138 | $cc = 'admin@'.$common::domain; 139 | $cc = $tg->{ident}.'-'.$cc unless $tg->{ident} eq 'main'; 140 | } 141 | 142 | print "stuck [$tg->{ident}]: $ident ($state) [$vouchor]\n"; 143 | if ($debug) { 144 | print "To: $vouchor_email, $email;\n", 145 | defined $cc ? "cc: $cc;\n" : '', 146 | "Subj: $ident (descr) is stuck in '$state'\n", 147 | "\n", $body, "---\n"; 148 | } else { 149 | $_ = &common::email_send($tg, $common::hostmaster, # tg, from 150 | [$vouchor_email, $email], # to 151 | $cc, # cc 152 | undef, # reply-to 153 | "$ident ($descr) stuck in '$state'", # subject 154 | $body # body 155 | ); 156 | print "$_\n" if defined $_; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /webroot/site/vouch_cp.html: -------------------------------------------------------------------------------- 1 | <%args> 2 | $state => 'Unmarked' 3 | $limit => 25 4 | $submit => undef 5 | $offset => 0 6 | 7 | 8 |

Personalized Vouching Control Panel

9 | 10 | <%perl> 11 | $q->Delete(qw[state limit submit]); 12 | 13 | $state = 'Unmarked' unless $state eq 'Dunno' || $state eq 'Vouched'; 14 | $limit = 0 + $limit; 15 | $limit = 50 if $limit > 50; 16 | 17 | # 18 | # select for display 19 | # 20 | my $display_list = $q->start_form(-action => 'vouch_cp.html') . 21 | $q->hidden('state', $state) . 22 | $q->hidden('limit', $limit) . 23 | $q->hidden('offset', $offset) . 24 | $q->start_table; 25 | my $and_where = 'AND mv.vouchee IS NULL'; 26 | my $action = 'Dunno'; 27 | if ($state eq 'Dunno') { 28 | $and_where = 'AND mv.vouchee IS NOT NULL AND NOT mv.positive'; 29 | $action = 'Reconsider'; 30 | } elsif ($state eq 'Vouched') { 31 | $and_where = 'AND mv.vouchee IS NOT NULL AND mv.positive'; 32 | $action = 'Unvouch'; 33 | } 34 | 35 | if (defined $submit) { 36 | my @profiles = $q->param('member[]'); 37 | foreach my $ident (@profiles) { 38 | next if $ident eq ''; 39 | next unless $submit =~ 40 | /^(Dunno|Reconsider|Unvouch)\sSelected$/; 41 | my $dispo = $1; 42 | my $db_ident = $dbh->quote($ident); 43 | my $stmt; 44 | if ($dispo eq 'Dunno') { 45 | $stmt = qq{ 46 | INSERT INTO member_vouch 47 | (vouchor, vouchee, trustgroup, positive) 48 | VALUES ($Site->{db_member}, $db_ident, 49 | $tg->{db_ident}, FALSE); 50 | }; 51 | } else { 52 | my $positivity; 53 | 54 | if ($dispo eq 'Reconsider') { 55 | $positivity = 'NOT positive'; 56 | } elsif ($dispo eq 'Unvouch') { 57 | $positivity = 'positive'; 58 | } else { 59 | $m->abort(403); 60 | } 61 | $stmt = qq{ 62 | DELETE FROM member_vouch mv 63 | WHERE mv.vouchor = $Site->{db_member} 64 | AND mv.vouchee = $db_ident 65 | AND mv.trustgroup = $tg->{db_ident} 66 | AND $positivity; 67 | }; 68 | } 69 | my $errstr = &common::audited_do($dbh, $Site->{member}, $stmt); 70 | if (length $errstr) { 71 | print $q->p("$stmt: $errstr"); 72 | } else { 73 | print $dispo, ' ', $q->code($ident), $q->br(); 74 | } 75 | } 76 | print $q->hr(); 77 | } 78 | 79 | 80 |

Every member of the community has their own set of people for whom they've 81 | vouched, and also their own set of people whom they just don't know well 82 | enough to vouch for. This page gives you a way to see and manage this 83 | personalized information.

84 | 85 |

People you have not input any trust information about will show here as 86 | "unmarked". The people you've vouched for will show as "vouched". The 87 | ones you just don't well enough to vouch for will show as "dunno". If you 88 | can move everybody to either the "vouched" set (if you can vouch for them 89 | according to the trust community's criteria) or to the "dunno" set, then 90 | you'll be left with a short list of people you havn't yet thought about.

91 | 92 |

The goal of this tool is to increase the trust density within our community. 93 | Therefore, a great personal goal would be an empty list of "unmarked" members. 94 | One common way to do this is to open tabs on each member you intend to vouch 95 | for, then say 'Dunno' to the rest, lather, rinse, repeat. 96 |

97 | 98 | <%perl> 99 | 100 | my $n = 0; 101 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 102 | SELECT m.descr, m.ident, m.affiliation 103 | FROM member m 104 | JOIN member_trustgroup mt 105 | ON (mt.member = m.ident 106 | AND mt.trustgroup = $tg->{db_ident} 107 | AND mt.member <> $Site->{db_member}) 108 | JOIN member_state ms ON (ms.ident = mt.state) 109 | LEFT OUTER JOIN member_vouch mv 110 | ON (mv.trustgroup = mt.trustgroup 111 | AND mv.vouchee = m.ident 112 | AND mv.vouchor = $Site->{db_member}) 113 | WHERE NOT ms.hidden $and_where 114 | ORDER BY mt.entered 115 | LIMIT $limit OFFSET $offset 116 | }, {Slice => {}})}) { 117 | $display_list .= 118 | $q->Tr($q->th(["$state Members (oldest first)", 'Action']) . 119 | $q->td('   ') . 120 | $q->td({-rowspan => 1 + $limit}, 121 | $q->submit('submit', "$action Selected") )) 122 | if !$n++; 123 | 124 | my $member = $row->{ident}; 125 | $_ = $row->{descr}; s/^\s+//o; s/\s+$//o; 126 | my $descr = /\s+([^\s]+)$/o ? "$1, $`" : $_; 127 | my $class = ($n %2) ? "a" : "b"; 128 | $display_list .= sprintf qq{
\n}, 129 | $q->escapeHTML($descr), 130 | $q->a({-target=>"VCP", -href=>"/site/show_member.html?member=$member"}, 131 | $q->code($member)), 132 | $q->escapeHTML($row->{affiliation}), 133 | $q->checkbox(-name => "member[]", 134 | -checked => 0, 135 | -value => $member, 136 | -label => $action); 137 | } 138 | $display_list .= 139 | $q->p($q->em('Nothing here, try going back or changing the criteria')) 140 | unless $n > 0; 141 | $display_list .= $q->end_table . 142 | $q->end_form; 143 | 144 | # 145 | # allow selection of which kind of thing to display 146 | # 147 | print $q->start_form(-action => 'vouch_cp.html'), $q->start_table; 148 | print $q->Tr($q->td(['Criteria:', 149 | join("\n", $q->radio_group(-name => 'state', 150 | -values => ['Unmarked', 'Dunno', 'Vouched'], 151 | -default => $state, 152 | -linebreak => 0 153 | -rows => 1))]), 154 | $q->td('   '), 155 | $q->td({-rowspan => 2}, 156 | $q->submit('submit', 'Change Criteria') . 157 | $q->br() )); 158 | print $q->Tr($q->td(['Limit:', 159 | join("\n", $q->radio_group(-name => 'limit', 160 | -values => ['10', '25', '50'], 161 | -default => $limit, 162 | -linebreak => 0, 163 | -rows => 1))] )); 164 | print $q->end_table; 165 | print $q->end_form; 166 | print $q->hr, $display_list; 167 | 168 | #Display pagination 169 | print $q->start_table; 170 | my $page = ($offset / $limit) + 1; 171 | if($page > 1) { 172 | #Prev Page < 173 | my $noff = $offset - $limit; 174 | print ""; 175 | print "<"; 176 | } 177 | print " - $page - "; 178 | if($n == $limit){ 179 | #Next Page > 180 | my $noff = $offset + $limit; 181 | print ""; 182 | print ">"; 183 | } 184 | print $q->end_table; 185 | 186 | 187 | 188 | <%shared> 189 | my $q = undef; 190 | my $dbh = undef; 191 | my $tg = undef; 192 | 193 | 194 | <%init> 195 | $q = $Mech->{cgi}; 196 | $dbh = $Site->{dbh}; 197 | $tg = $Site->{tg}; 198 | 199 | -------------------------------------------------------------------------------- /webroot/site/list_members.html: -------------------------------------------------------------------------------- 1 | <%args> 2 | $select => '' 3 | $search => '' 4 | 5 | 6 |

Members

7 | 8 | <%perl> 9 | # output search form 10 | print $q->start_form(-action=>$q->url()), 11 | $q->hidden({-override=>1}, 'select', ''), 12 | $q->textfield(-name=>'search', -override=>1, -default=>$search, 13 | -size=>65, -maxlength=>100), 14 | ' ', $q->submit('submit', 'Search'), $q->end_form, $q->p; 15 | 16 | # output members by state-category, possibly limited by $search and $select 17 | my $n = 0; 18 | 19 | # Protect lamely against lame input (search for everyone: *) 20 | 21 | $search =~ s/(\W)/\\$1/g; 22 | 23 | foreach my $state (qw[nominated vetted approved active soonidle 24 | idle inactive failed blocked all]) 25 | { 26 | next unless $by_state{$state}->{count} > 0 || $state eq 'all'; 27 | my $selector = ''; 28 | $selector = "?select=$state" if $state ne $select; 29 | print $q->h5($q->a({-href=>$q->url().$selector}, 30 | "[$state]: $by_state{$state}->{count}")); 31 | print $q->start_ul({type => 'circle'}); 32 | foreach my $ident (sort { $members->{$a}->{sortdescr} cmp 33 | $members->{$b}->{sortdescr} } 34 | keys %{$by_state{$state}->{ident}}) 35 | { 36 | my $member = $members->{$ident}; 37 | if ($select eq $state || 38 | $select eq 'all' || 39 | (length($search) != 0 && 40 | ($ident =~ /$search/si || 41 | $member->{email} =~ /$search/si || 42 | $member->{descr} =~ /$search/si || 43 | $member->{affiliation} =~ /$search/si || 44 | $member->{details} =~ /$search/si || 45 | $member->{bio_info} =~ /$search/si))) 46 | { 47 | my @notes = @{$by_state{$state}->{ident} 48 | ->{$ident}->{notes}}; 49 | my $notestr = ($#notes == -1) ? '' : 50 | $q->em(sprintf('  (%s)', 51 | join(', ', @notes))); 52 | my $show_member = '/site/show_member.html' . 53 | "?member=$ident"; 54 | my $render = sprintf '%s   %s   (%s) '. 55 | '(%s%d ↔ %d%s)%s%s%s%s%s', 56 | $q->escapeHTML($member->{descr}), 57 | $q->a({-href=>$show_member}, 58 | $q->code($q->escapeHTML($ident))), 59 | $q->escapeHTML($member->{affiliation}), 60 | ($member->{by_me} ? '♦' : ''), 61 | $member->{for} + 0, 62 | $member->{by} + 0, 63 | ($member->{for_me} ? '♦' : ''), 64 | ($member->{admin} ? 'Ω' : ''), 65 | ($member->{has_face} ? ' ℵ' : '', 66 | $member->{haspgp} ? ' ψ' : '', 67 | $member->{never} ? ' Ø' : '', 68 | $notestr); 69 | print $q->li($render); 70 | $n++; 71 | } 72 | } 73 | print $q->end_ul; 74 | } 75 | if ($n != 0) { 76 | print $q->hr({-align => 'LEFT', -width => '15%'}), 77 | $q->p(qq{ 78 | Ω = administrator
79 | ♦ = vouched by/for you
80 | ℵ = has portrait
81 | ψ = has PGP key
82 | Ø = has never logged in 83 | }); 84 | } 85 | 86 | 87 | <%shared> 88 | my $q = undef; 89 | my $dbh = undef; 90 | my $tg = undef; 91 | my $members = {}; 92 | my %by_state = (); 93 | 94 | 95 | <%init> 96 | $q = $Mech->{cgi}; 97 | $dbh = $Site->{dbh}; 98 | $tg = $Site->{tg}; 99 | 100 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 101 | SELECT m.ident AS ident, 102 | mt.email AS email, 103 | m.descr AS descr, 104 | m.affiliation AS affiliation, 105 | m.bio_info AS bio_info, 106 | m.image IS NOT NULL AS has_face, 107 | me.pgpkey_id IS NOT NULL AS haspgp, 108 | mt.admin AS admin, 109 | mt.state AS state, 110 | COALESCE(vouches.num, 0) AS vouches, 111 | vouches.dir AS dir, 112 | mt.entered::DATE AS entered, 113 | (NOW()::DATE - mt.entered::DATE) AS age, 114 | (m.activity = m.entered) AS never, 115 | array_to_string(ARRAY(SELECT value FROM member_details WHERE 116 | member = m.ident),',') AS details 117 | FROM member m 118 | JOIN member_trustgroup mt ON (ROW(mt.member, mt.trustgroup) = 119 | ROW(m.ident, $tg->{db_ident})) 120 | JOIN member_email me ON (ROW(me.member, me.email) = 121 | ROW(mt.member, mt.email)) 122 | JOIN member_state ms ON (ms.ident = mt.state) 123 | LEFT OUTER JOIN ( 124 | SELECT 'for' AS dir, mv.vouchee AS member, COUNT(*) AS num 125 | FROM member_vouch mv 126 | WHERE mv.trustgroup = $tg->{db_ident} 127 | AND mv.positive 128 | GROUP BY mv.vouchee 129 | UNION 130 | SELECT 'by' AS dir, mv.vouchor AS member, COUNT(*) AS num 131 | FROM member_vouch mv 132 | WHERE mv.trustgroup = $tg->{db_ident} 133 | AND mv.positive 134 | GROUP BY mv.vouchor 135 | UNION 136 | SELECT 'for_me' AS dir, mv.vouchor AS member, COUNT(*) AS num 137 | FROM member_vouch mv 138 | WHERE ROW(mv.trustgroup, mv.vouchee) = 139 | ROW($tg->{db_ident}, $Site->{db_member}) 140 | AND mv.positive 141 | GROUP BY mv.vouchor 142 | UNION 143 | SELECT 'by_me' AS dir, mv.vouchee AS member, COUNT(*) AS num 144 | FROM member_vouch mv 145 | WHERE ROW(mv.trustgroup, mv.vouchor) = 146 | ROW($tg->{db_ident}, $Site->{db_member}) 147 | AND mv.positive 148 | GROUP BY mv.vouchee 149 | ) AS vouches ON (m.ident = vouches.member) 150 | WHERE mt.trustgroup = $tg->{db_ident} 151 | AND NOT ms.hidden 152 | }, {Slice => {}})}) { 153 | my $ident = $row->{ident}; 154 | if (!defined $members->{$ident}) { 155 | $_ = $row->{descr}; s/^\s+//o; s/\s+$//o; 156 | my $sortdescr = /\s+([^\s]+)$/o ? "$1$`" : $_; 157 | $sortdescr =~ tr/A-Z/a-z/; 158 | my $descr = /\s+([^\s]+)$/o ? "$1, $`" : $_; 159 | # note: "age" is not stored, we only use it for exclusion 160 | $members->{$ident} = { 161 | email => $row->{email}, 162 | descr => $descr, 163 | affiliation => $row->{affiliation}, 164 | bio_info => $row->{bio_info}, 165 | admin => $row->{admin}, 166 | state => $row->{state}, 167 | sortdescr => $sortdescr, 168 | haspgp => $row->{haspgp}, 169 | entered => $row->{entered}, 170 | details => $row->{details}, 171 | has_face => $row->{has_face}, 172 | for => undef, 173 | by => undef, 174 | for_me => undef, 175 | by_me => undef, 176 | never => $row->{never} 177 | }; 178 | } 179 | my $member = $members->{$ident}; 180 | my $dir = $row->{dir}; 181 | $member->{$dir} = $row->{vouches}; 182 | } 183 | 184 | while (my ($ident, $member) = each %$members) { 185 | my $pile = undef; 186 | my @notes = ( ); 187 | if ($member->{for} < $tg->{min_invouch}) { 188 | push(@notes, 'since '.$member->{entered}); 189 | } else { 190 | if ($tg->{pgp_required} && !$member->{haspgp}) { 191 | push(@notes, 'needs pgp key'); 192 | } 193 | if ($member->{by} == 0 && $tg->{please_vouch}) { 194 | push(@notes, 'needs to vouch'); 195 | } 196 | } 197 | my $state = $member->{state}; 198 | $by_state{$state}->{count}++; 199 | $by_state{'all'}->{count}++; 200 | $by_state{$state}->{ident}->{$ident}->{notes} = \@notes; 201 | } 202 | 203 | -------------------------------------------------------------------------------- /library/fsck-pgpkeys.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | 21 | use strict; 22 | use warnings; 23 | use FindBin; 24 | use lib $FindBin::Bin; 25 | use common; 26 | use Mail::SendEasy; 27 | use Text::Wrap; 28 | use Data::Dumper; 29 | use File::Find; 30 | 31 | sub bad_pgpfile($); 32 | sub reset_keyid($$$); 33 | 34 | my $debug = 0; 35 | $Data::Dumper::Indent = 1; 36 | 37 | our $dbh = &common::get_dbh(); 38 | our $tg = &common::get_tg($dbh, 'main'); 39 | 40 | our %notices = ( ); 41 | 42 | print "loading emails from database\n" if $debug; 43 | my $loaded_uuid = { }; 44 | my $key_uuids = { }; 45 | my $emails = { }; 46 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 47 | SELECT DISTINCT ON (me.email) m.ident, m.no_email, m.uuid, me.pgpkey_id, 48 | me.email, date(me.pgpkey_expire) as pgpkey_expire, 49 | date_part('epoch', me.pgpkey_expire - now()) as pgpkey_remain, 50 | date_part('epoch', me.keyring_update_at) as pgpkey_update 51 | FROM member_trustgroup mt, member_email me 52 | JOIN member m ON (m.ident = me.member) 53 | WHERE m.ident = mt.member AND (mt.state = 'active' or mt.state = 'soonidle'); 54 | }, {Slice => {}})}) { 55 | if (defined $row->{pgpkey_id}){ 56 | if (!defined $loaded_uuid->{$row->{uuid}}) { 57 | &common::gpg_key_present($dbh, $row->{uuid}); 58 | $row = &check_expired($dbh,$row); 59 | $loaded_uuid->{$row->{uuid}} = [ ]; 60 | &gen_email_list($row->{uuid},\@{$loaded_uuid->{$row->{uuid}}}); 61 | } 62 | if (!&key_has_email($row->{email},$loaded_uuid->{$row->{uuid}})) { 63 | push @{$notices{$row->{email}}}, 64 | qq{The PGP keyring file you uploaded $row->{pgpkey_id} 65 | does not contain an alias for your currently selected e-mail $row->{email} }; 66 | print 'No e-mail match: '.$row->{uuid}."\n"; 67 | print 'E-mail '.$row->{email}."\n"; 68 | foreach my $key (@{$loaded_uuid->{$row->{uuid}}}) { 69 | print "\t$key\n"; 70 | } 71 | } 72 | $key_uuids->{$row->{uuid}} = $row; 73 | } 74 | if ($row->{no_email}) { 75 | $emails->{$row->{email}}->{no_email} = 1; 76 | } 77 | } 78 | 79 | print "reading the pgpkeys directory\n" if $debug; 80 | my $pgpkey_files = { }; 81 | my $pgpdir = undef; 82 | 83 | my $paths = { }; 84 | 85 | finddepth(\&wanted, $common::pgpkeys); 86 | 87 | 88 | print "sending notices\n" if $debug; 89 | foreach my $notice_email (keys %notices) { 90 | my $body = join("\n\n", @{$notices{$notice_email}}); 91 | my $to = $notice_email; 92 | 93 | if ($debug) { 94 | print "notice ($to)\n", $body, "---\n"; 95 | } elsif (!$emails->{$to}->{no_email}) { 96 | &common::email_send($tg, $common::hostmaster, # from 97 | $to, # to 98 | undef, # cc 99 | undef, # reply-to 100 | "PGP key ring trouble", # subject 101 | $body); # body 102 | } 103 | } 104 | 105 | sub wanted { 106 | if (-d $File::Find::name) { 107 | if ($File::Find::name =~ m/^$common::pgpkeys\/([a-f0-9])\/([a-f0-9])$/){ 108 | if (!defined $pgpkey_files->{"$1/$2"} ){ 109 | rmdir $File::Find::name; 110 | print "Unused dir: $File::Find::name\n"; 111 | } 112 | } elsif ($File::Find::name =~ m/^$common::pgpkeys\/([a-f0-9])$/){ 113 | if (!defined $pgpkey_files->{$1} ){ 114 | rmdir $File::Find::name; 115 | print "Unused dir: $File::Find::name\n"; 116 | } 117 | } elsif ($File::Find::name =~ m/^$common::pgpkeys$/){ 118 | #This is our root, don't act on it. 119 | } else { 120 | print "UNKNOWN DIR: $File::Find::name\n"; 121 | } 122 | } else { 123 | if ($File::Find::name =~ m/^$common::pgpkeys\/([a-f0-9])\/([a-f0-9])\/([a-f0-9-]{36}).(gpg|gpg\~|secring|trustdb)$/) { 124 | my $uuid = $3; 125 | if (defined $key_uuids->{$uuid}){ 126 | $pgpkey_files->{$1} = 1; 127 | $pgpkey_files->{"$1/$2"} = 1; 128 | } else { 129 | unlink $File::Find::name; 130 | print "\tKEY BAD: $uuid \n" 131 | } 132 | } else { 133 | print "Mystery_File: $File::Find::name\n"; 134 | } 135 | } 136 | } 137 | 138 | sub check_expired { 139 | my ($dbh,$row) = @_; 140 | my @expire_at = &common::gpgcmd_keyexpire($row->{uuid},$row->{pgpkey_id}); 141 | my $pgp_change = 0; 142 | if (@expire_at) { 143 | if (defined $row->{pgpkey_expire}) { 144 | if ($expire_at[0] ne $row->{pgpkey_expire}) { 145 | $pgp_change = 1; 146 | &update_gpgkey_expire($dbh,$row->{email},$row->{uuid}, 147 | $row->{pgpkey_id},$expire_at[0]); 148 | print "PGP Key updated (change expiration): ".$row->{uuid}."\n"; 149 | $row->{pgpkey_expire} = $expire_at[0]; 150 | } 151 | } else { 152 | $pgp_change = 1; 153 | &update_gpgkey_expire($dbh,$row->{email},$row->{uuid}, 154 | $row->{pgpkey_id},$expire_at[0]); 155 | print "PGP Key updated (set expiration): "; 156 | print $row->{uuid}." Expire_at: ".$expire_at[0]." was: "; 157 | print $row->{pgpkey_expire}."\n"; 158 | $row->{pgpkey_expire} = $expire_at[0]; 159 | } 160 | } else { 161 | if ($row->{expire_at}) { 162 | $pgp_change = 1; 163 | &update_gpgkey_expire($dbh,$row->{email},$row->{uuid}, 164 | $row->{pgpkey_id},$expire_at[0]); 165 | print "PGP Key updated (removed expiration): ".$row->{uuid}."\n"; 166 | } 167 | } 168 | if (@expire_at and !$pgp_change) { 169 | if($row->{pgpkey_remain} < 1) { 170 | print "PGP Key expired: ".$row->{uuid}."\n"; 171 | push @{$notices{$row->{email}}}, 172 | qq{PGP key ID $row->{pgpkey_id} for $row->{email} has expired. Please update your PGP key to 173 | receive encrypted messages.}; 174 | } elsif ($row->{pgpkey_remain} < 2592000) { 175 | print "PGP Key close to expiration: ".$row->{uuid}."\n"; 176 | push @{$notices{$row->{email}}}, 177 | qq{PGP key ID $row->{pgpkey_id} for $row->{email} is about to expire. Please update your PGP key.}; 178 | } 179 | 180 | } 181 | return $row; 182 | } 183 | 184 | # 185 | # update_gpgkey_expire -- Set GPG key expire date in db. 186 | # 187 | 188 | sub update_gpgkey_expire($$$$$) { 189 | my ($dbh,$email,$uuid, $key_id,$expire) = @_; 190 | my $db_email = $dbh->quote($email); 191 | my $db_uuid = $dbh->quote($uuid); 192 | my $db_key_id = $dbh->quote($key_id); 193 | my $db_expire = (defined $expire) ? $dbh->quote($expire) : 'NULL'; 194 | my $stmt = qq{ 195 | UPDATE member_email me 196 | SET pgpkey_expire = $db_expire 197 | FROM member m 198 | WHERE me.member = m.ident 199 | AND m.uuid = $db_uuid 200 | AND me.email = $db_email 201 | AND me.pgpkey_id = $db_key_id 202 | }; 203 | my $rv = $debug || $dbh->do($stmt); 204 | print "{$stmt}: $rv\n" if $debug; 205 | return $rv; 206 | } 207 | 208 | sub gen_email_list() { 209 | my ($uuid,$ref) = @_; 210 | my @keylist = split('\n',&common::gpgcmd_allkeys($uuid)); 211 | foreach my $key (@keylist) { 212 | $key = lc($key); 213 | # XXX: misses single char usernames 214 | if ($key =~ m/([a-z0-9\.\_+-]+\w@[a-z0-9\.\_-]+\w)/) { 215 | push($ref,$1); 216 | } 217 | } 218 | } 219 | 220 | 221 | sub key_has_email() { 222 | my ($email,$ref) = @_; 223 | foreach my $line (@{$ref}) { 224 | if ($line eq $email) { 225 | return 1; 226 | } 227 | } 228 | return 0; 229 | } 230 | 231 | exit 0; 232 | -------------------------------------------------------------------------------- /webroot/site/edit_second_factor.html: -------------------------------------------------------------------------------- 1 | <%args> 2 | $submit => '' 3 | $type => '' 4 | $HELPFUL_NAME => '' 5 | $DEVICE_NAME => '' 6 | $form_id => '' 7 | $token_uuid => '' 8 | $code => '' 9 | 10 | 11 |

Edit Second Factor Tokens

12 | <%perl> 13 | use Digest::SHA; 14 | my $postscript = ''; 15 | my @stmts; 16 | if ($submit eq 'Disable Token' or $submit eq 'Enable Token') { 17 | my $uuid = $dbh->quote($token_uuid); 18 | my $detail = $dbh->selectrow_hashref(qq{ 19 | SELECT sf.key,sf.counter 20 | FROM second_factors sf 21 | WHERE sf.uuid = $uuid 22 | }); 23 | use Authen::OATH; 24 | my $oath = Authen::OATH->new(); 25 | my $otp; 26 | my $add_to_query = ''; 27 | if ($type eq 'TOTP'){ 28 | $otp = $oath->totp( $detail->{'key'} ); 29 | } elsif ($type eq 'HOTP'){ 30 | $otp = $oath->hotp( $detail->{'key'}, $detail->{'counter'} ); 31 | $add_to_query = ", COUNTER = COUNTER + 1"; 32 | } else { $postscript .= $q->hr() . $q->p("Unknown type: $type.");} 33 | my $was = 't'; 34 | my $to_be = 'f'; 35 | if ($submit eq 'Enable Token'){ 36 | $was = 'f'; 37 | $to_be = 't'; 38 | } 39 | if($otp eq $code){ 40 | push @stmts, "UPDATE second_factors 41 | SET active = '$to_be' 42 | $add_to_query 43 | WHERE member = $Site->{db_member} 44 | AND active = '$was' 45 | AND uuid = $uuid"; 46 | $postscript .= $q->hr() . $q->p("Token State changed."); 47 | } else { 48 | $code = substr($code, 0,3); 49 | $otp = substr($otp, 0,3); 50 | $postscript .= $q->hr() . $q->p("Invalid Token Code. ".$otp."*** - ".$code."***"); 51 | } 52 | } elsif ($submit eq 'Delete Token'){ 53 | my $uuid = $dbh->quote($token_uuid); 54 | push @stmts, "DELETE FROM second_factors 55 | WHERE member = $Site->{db_member} 56 | AND (active = 'f' or type = 'SOTP') 57 | AND uuid = $uuid"; 58 | } elsif ($submit eq 'Create Token'){ 59 | print $q->start_table({border => 0}); 60 | my $helpful_name = $dbh->quote($HELPFUL_NAME); 61 | my $device_name = $dbh->quote($DEVICE_NAME); 62 | if ($type eq 'TOTP' or $type eq 'HOTP'){ 63 | use MIME::Base32 qw( RFC ); 64 | use HTML::Barcode::QRCode; 65 | #Generate Key. 66 | my $key = substr &common::rand_str().&common::rand_str(),1,10; 67 | my $encoded = MIME::Base32::encode($key); 68 | my $key_hex = join(" ",map sprintf("%X",ord($_)), split("",$key)); 69 | #Create URL: 70 | my $url_type = lc($type); 71 | my $db_type = $dbh->quote($type); 72 | my $url = "otpauth://$url_type/".$HELPFUL_NAME."?secret=$encoded"; 73 | #Generate QR 74 | my $qrcode = HTML::Barcode::QRCode->new(text => $url); 75 | print $q->start_table({border => 0, class => "hbc"}); 76 | print $q->Tr( 77 | $q->th("Device:"). 78 | $q->td({colspan => 2},"$DEVICE_NAME") 79 | ); 80 | print $q->Tr( 81 | $q->th("Account Name:"). 82 | $q->td({colspan => 2},"$HELPFUL_NAME") 83 | ); 84 | print $q->Tr( 85 | $q->td(" "). $q->td(" "). $q->td(" ") 86 | ); 87 | print $q->Tr( 88 | $q->th("QR Code:"). 89 | $q->td($qrcode->render_barcode). 90 | $q->td(" ") 91 | ); 92 | print $q->Tr( 93 | $q->td(" "). $q->td(" "). $q->td(" ") 94 | ); 95 | print $q->Tr( 96 | $q->th("otpauth URL:"). 97 | $q->td({colspan => 2}, $url) 98 | ); 99 | print $q->Tr( 100 | $q->th("Secret Key (Base32):"). 101 | $q->td({colspan => 2}, $encoded) 102 | ); 103 | print $q->Tr( 104 | $q->th("Key (Hex):"). 105 | $q->td({colspan => 2}, $key_hex) 106 | ); 107 | 108 | print $q->end_table; 109 | 110 | #Add DB Record. 111 | my $uuid = &common::newmember_uuid(); 112 | push @stmts, "INSERT INTO second_factors (member,type,key,active,entered,uuid,descr) 113 | VALUES ($Site->{db_member},$db_type,'$key','f',now(),'$uuid',$device_name)"; 114 | } elsif ($type eq 'SOTP'){ 115 | my $count = 5; 116 | print $q->Tr($q->td({colspan => 2},"Generated $count new SOTP keys.")); 117 | while($count){ 118 | #TODO use Base58 to generate key, 119 | my $key = &common::rand_str(); 120 | print $q->Tr($q->th("Key:"),$q->td($key)); 121 | my $hash = Digest::SHA::sha256_hex($key); 122 | my $uuid = &common::newmember_uuid(); 123 | push @stmts, "INSERT INTO second_factors (member,type,key,active,entered,uuid,descr) 124 | VALUES ($Site->{db_member},'SOTP','$hash','t',now(),'$uuid',$device_name)"; 125 | $count--; 126 | } 127 | print $q->Tr($q->td({colspan => 2},"Record these codes now. Only hashes are stores in the database.")); 128 | } 129 | print $q->end_table; 130 | } 131 | if (@stmts){ 132 | my $errstr = &common::audited_do($dbh, $Site->{member}, @stmts); 133 | if (length $errstr) { 134 | $postscript .= $q->hr() . $q->p("Failure: $errstr"); 135 | } else { 136 | $postscript .= $q->hr() . $q->p('Profile has been updated.'); 137 | } 138 | } 139 | $q->delete_all(); 140 | 141 | # 142 | # display 143 | # 144 | $form_id = &common::new_form_id(); 145 | $Mech->{sess}->param('form_id', $form_id); 146 | $Mech->{sess}->flush(); 147 | 148 | print $q->start_table({border => 0}); 149 | print $q->Tr( 150 | $q->th('Name'), 151 | $q->th('Type'), 152 | $q->th('Active'), 153 | $q->th('Counter'), 154 | $q->th('Created at'), 155 | $q->th('Edit'), 156 | ); 157 | my $row = 0; 158 | foreach my $detail (@{$dbh->selectall_arrayref(qq{ 159 | SELECT sf.descr,sf.type, sf.entered, sf.active, sf.counter, sf.uuid,sf.counter 160 | FROM second_factors sf 161 | WHERE sf.member = $Site->{db_member} 162 | },{Slice => {}})}){ 163 | my $active; 164 | my $disable_button = ""; 165 | my $delete_button = ""; 166 | my $enable_button = ""; 167 | my $zebra_class = ($row %2) ? "a" : "b"; 168 | if ($detail->{'active'} == 1){ 169 | $active = 'true'; 170 | if ($detail->{'type'} eq "SOTP"){ 171 | $delete_button = $q->start_form(-action => '#'.$detail->{'uuid'}) . 172 | $q->hidden({-override => 1}, 'form_id', $form_id) . 173 | $q->submit('submit', 'Delete Token') . 174 | $q->hidden('member', $Site->{db_member}) . 175 | $q->hidden('token_uuid', $detail->{'uuid'}) . 176 | $q->end_form ; 177 | } else { 178 | $disable_button = $q->start_form(-action => '#'.$detail->{'uuid'}) . 179 | $q->hidden({-override => 1}, 'form_id', $form_id) . 180 | $q->hidden('type', $detail->{'type'}) . 181 | "Code ".$q->textfield(-name => 'code', 182 | -override => 1, 183 | -size => 25, 184 | -maxlength => 100) . 185 | $q->submit('submit', 'Disable Token') . 186 | $q->hidden('member', $Site->{db_member}) . 187 | $q->hidden('token_uuid', $detail->{'uuid'}) . 188 | $q->end_form ; 189 | } 190 | } else { 191 | $active = 'false'; 192 | $delete_button = $q->start_form(-action => '#'.$detail->{'uuid'}) . 193 | $q->hidden({-override => 1}, 'form_id', $form_id) . 194 | $q->submit('submit', 'Delete Token') . 195 | $q->hidden('member', $Site->{db_member}) . 196 | $q->hidden('token_uuid', $detail->{'uuid'}) . 197 | $q->end_form ; 198 | $enable_button = $q->start_form(-action => '#'.$detail->{'uuid'}) . 199 | $q->hidden({-override => 1}, 'form_id', $form_id) . 200 | $q->hidden( 'type', $detail->{'type'}) . 201 | "Code ".$q->textfield(-name => 'code', 202 | -override => 1, 203 | -size => 25, 204 | -autocomplete => 'off', 205 | -maxlength => 100) . 206 | $q->submit('submit', 'Enable Token') . 207 | $q->hidden('member', $Site->{db_member}) . 208 | $q->hidden('token_uuid', $detail->{'uuid'}) . 209 | $q->end_form ; 210 | } 211 | print $q->Tr( 212 | {-class => "$zebra_class"}, 213 | $q->td($detail->{'descr'}), 214 | $q->td($detail->{'type'}), 215 | $q->td($active), 216 | $q->td($detail->{'counter'}), 217 | $q->td($detail->{'entered'}), 218 | $q->td($enable_button,$delete_button,$disable_button) 219 | ); 220 | $row++; 221 | } 222 | print $q->end_table; 223 | print $q->br(); 224 | print $q->h3('Create a new Second-Factor Device:'); 225 | print $q->start_multipart_form; 226 | print $q->start_table({border => 0}); 227 | print $q->Tr( 228 | $q->th("Name of Device:"), 229 | $q->td( $q->textfield(-name => 'DEVICE_NAME', 230 | -override => 1, 231 | -size => 25, 232 | -maxlength => 100). 233 | "(will identify this device on this portal.)") 234 | ); 235 | print $q->Tr( 236 | $q->th("Name on Device:"), 237 | $q->td( $q->textfield(-name => 'HELPFUL_NAME', 238 | -default => $common::domain, 239 | -override => 1, 240 | -size => 25, 241 | -maxlength => 100). 242 | "(will identify service on the device.)") 243 | ); 244 | my @types = &common::list_second_factor_types($Site->{dbh}); 245 | print $q->Tr( 246 | $q->th("Type:"), 247 | $q->td($q->popup_menu(-name => 'type', 248 | -values => $types[0], 249 | -labels => $types[1], 250 | -override => 1)) 251 | ); 252 | 253 | print $q->Tr( 254 | $q->td($q->submit('submit', 'Create Token')), 255 | ); 256 | print $q->end_table; 257 | print $postscript; 258 | 259 | 260 | 261 | <%shared> 262 | my $q = undef; 263 | my $dbh = undef; 264 | my $tg = undef; 265 | my $keyring = undef; 266 | my $my_gpgcmd = undef; 267 | 268 | 269 | <%init> 270 | $q = $Mech->{cgi}; 271 | $dbh = $Site->{dbh}; 272 | $tg = $Site->{tg}; 273 | $keyring = sprintf '%s/%s', $common::pgpkeys, $Site->{uuid}; 274 | $my_gpgcmd = &common::gpgcmd_user($Site->{uuid}); 275 | 276 | 277 | <%method vetting_not_needed> 278 | # tells sitehandler.mas:request_ok that we don't need vetting here 279 | 280 | 281 | <%method tg_not_needed> 282 | # tells sitehandler.mas:request_ok that we don't need a trustgroup here 283 | 284 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /library/db_migrations/12-AUG-2013-1.psql: -------------------------------------------------------------------------------- 1 | CREATE TABLE languages ( 2 | iso_639_1 VARCHAR(2) NOT NULL 3 | UNIQUE, 4 | name TEXT NOT NULL 5 | UNIQUE 6 | ); 7 | 8 | GRANT SELECT ON TABLE languages to www; 9 | GRANT ALL ON TABLE languages TO sysadmin; 10 | 11 | INSERT INTO languages (iso_639_1,name) values ('aa','Afar'); 12 | INSERT INTO languages (iso_639_1,name) values ('ak','Akan'); 13 | INSERT INTO languages (iso_639_1,name) values ('as','Assamese'); 14 | INSERT INTO languages (iso_639_1,name) values ('ba','Bashkir'); 15 | INSERT INTO languages (iso_639_1,name) values ('bi','Bislama'); 16 | INSERT INTO languages (iso_639_1,name) values ('br','Breton'); 17 | INSERT INTO languages (iso_639_1,name) values ('ch','Chamorro'); 18 | INSERT INTO languages (iso_639_1,name) values ('cs','Czech'); 19 | INSERT INTO languages (iso_639_1,name) values ('da','Danish'); 20 | INSERT INTO languages (iso_639_1,name) values ('ee','Ewe'); 21 | INSERT INTO languages (iso_639_1,name) values ('es','Spanish'); 22 | INSERT INTO languages (iso_639_1,name) values ('ff','Fulah'); 23 | INSERT INTO languages (iso_639_1,name) values ('fr','French'); 24 | INSERT INTO languages (iso_639_1,name) values ('gl','Galician'); 25 | INSERT INTO languages (iso_639_1,name) values ('ha','Hausa'); 26 | INSERT INTO languages (iso_639_1,name) values ('hr','Croatian'); 27 | INSERT INTO languages (iso_639_1,name) values ('hz','Herero'); 28 | INSERT INTO languages (iso_639_1,name) values ('ig','Igbo'); 29 | INSERT INTO languages (iso_639_1,name) values ('io','Ido'); 30 | INSERT INTO languages (iso_639_1,name) values ('iu','Inuktitut'); 31 | INSERT INTO languages (iso_639_1,name) values ('jv','Javanese'); 32 | INSERT INTO languages (iso_639_1,name) values ('ki','Kikuyu'); 33 | INSERT INTO languages (iso_639_1,name) values ('km','Central Khmer'); 34 | INSERT INTO languages (iso_639_1,name) values ('ks','Kashmiri'); 35 | INSERT INTO languages (iso_639_1,name) values ('ky','Kirghiz'); 36 | INSERT INTO languages (iso_639_1,name) values ('li','Limburgan'); 37 | INSERT INTO languages (iso_639_1,name) values ('lu','Luba-Katanga'); 38 | INSERT INTO languages (iso_639_1,name) values ('mi','Maori'); 39 | INSERT INTO languages (iso_639_1,name) values ('my','Burmese'); 40 | INSERT INTO languages (iso_639_1,name) values ('ne','Nepali'); 41 | INSERT INTO languages (iso_639_1,name) values ('no','Norwegian'); 42 | INSERT INTO languages (iso_639_1,name) values ('oc','Occitan (post 1500)'); 43 | INSERT INTO languages (iso_639_1,name) values ('os','Ossetian'); 44 | INSERT INTO languages (iso_639_1,name) values ('ps','Pushto'); 45 | INSERT INTO languages (iso_639_1,name) values ('rn','Rundi'); 46 | INSERT INTO languages (iso_639_1,name) values ('sa','Sanskrit'); 47 | INSERT INTO languages (iso_639_1,name) values ('sg','Sango'); 48 | INSERT INTO languages (iso_639_1,name) values ('sl','Slovenian'); 49 | INSERT INTO languages (iso_639_1,name) values ('sq','Albanian'); 50 | INSERT INTO languages (iso_639_1,name) values ('su','Sundanese'); 51 | INSERT INTO languages (iso_639_1,name) values ('te','Telugu'); 52 | INSERT INTO languages (iso_639_1,name) values ('tk','Turkmen'); 53 | INSERT INTO languages (iso_639_1,name) values ('tr','Turkish'); 54 | INSERT INTO languages (iso_639_1,name) values ('ty','Tahitian'); 55 | INSERT INTO languages (iso_639_1,name) values ('uz','Uzbek'); 56 | INSERT INTO languages (iso_639_1,name) values ('wa','Walloon'); 57 | INSERT INTO languages (iso_639_1,name) values ('yo','Yoruba'); 58 | INSERT INTO languages (iso_639_1,name) values ('ab','Abkhazian'); 59 | INSERT INTO languages (iso_639_1,name) values ('am','Amharic'); 60 | INSERT INTO languages (iso_639_1,name) values ('av','Avaric'); 61 | INSERT INTO languages (iso_639_1,name) values ('be','Belarusian'); 62 | INSERT INTO languages (iso_639_1,name) values ('bm','Bambara'); 63 | INSERT INTO languages (iso_639_1,name) values ('bs','Bosnian'); 64 | INSERT INTO languages (iso_639_1,name) values ('co','Corsican'); 65 | INSERT INTO languages (iso_639_1,name) values ('cu','Church Slavic'); 66 | INSERT INTO languages (iso_639_1,name) values ('de','German'); 67 | INSERT INTO languages (iso_639_1,name) values ('el','Greek, Modern (1453-)'); 68 | INSERT INTO languages (iso_639_1,name) values ('et','Estonian'); 69 | INSERT INTO languages (iso_639_1,name) values ('fi','Finnish'); 70 | INSERT INTO languages (iso_639_1,name) values ('fy','Western Frisian'); 71 | INSERT INTO languages (iso_639_1,name) values ('gn','Guarani'); 72 | INSERT INTO languages (iso_639_1,name) values ('he','Hebrew'); 73 | INSERT INTO languages (iso_639_1,name) values ('ht','Haitian'); 74 | INSERT INTO languages (iso_639_1,name) values ('ia','Interlingua (International Auxiliary Language Association)'); 75 | INSERT INTO languages (iso_639_1,name) values ('ii','Sichuan Yi'); 76 | INSERT INTO languages (iso_639_1,name) values ('is','Icelandic'); 77 | INSERT INTO languages (iso_639_1,name) values ('kj','Kuanyama'); 78 | INSERT INTO languages (iso_639_1,name) values ('kn','Kannada'); 79 | INSERT INTO languages (iso_639_1,name) values ('ku','Kurdish'); 80 | INSERT INTO languages (iso_639_1,name) values ('la','Latin'); 81 | INSERT INTO languages (iso_639_1,name) values ('ln','Lingala'); 82 | INSERT INTO languages (iso_639_1,name) values ('lv','Latvian'); 83 | INSERT INTO languages (iso_639_1,name) values ('mk','Macedonian'); 84 | INSERT INTO languages (iso_639_1,name) values ('mr','Marathi'); 85 | INSERT INTO languages (iso_639_1,name) values ('na','Nauru'); 86 | INSERT INTO languages (iso_639_1,name) values ('ng','Ndonga'); 87 | INSERT INTO languages (iso_639_1,name) values ('nr','Ndebele, South'); 88 | INSERT INTO languages (iso_639_1,name) values ('oj','Ojibwa'); 89 | INSERT INTO languages (iso_639_1,name) values ('pa','Panjabi'); 90 | INSERT INTO languages (iso_639_1,name) values ('pt','Portuguese'); 91 | INSERT INTO languages (iso_639_1,name) values ('ro','Romanian'); 92 | INSERT INTO languages (iso_639_1,name) values ('sc','Sardinian'); 93 | INSERT INTO languages (iso_639_1,name) values ('sm','Samoan'); 94 | INSERT INTO languages (iso_639_1,name) values ('sr','Serbian'); 95 | INSERT INTO languages (iso_639_1,name) values ('sv','Swedish'); 96 | INSERT INTO languages (iso_639_1,name) values ('tg','Tajik'); 97 | INSERT INTO languages (iso_639_1,name) values ('tl','Tagalog'); 98 | INSERT INTO languages (iso_639_1,name) values ('ts','Tsonga'); 99 | INSERT INTO languages (iso_639_1,name) values ('ug','Uighur'); 100 | INSERT INTO languages (iso_639_1,name) values ('ve','Venda'); 101 | INSERT INTO languages (iso_639_1,name) values ('wo','Wolof'); 102 | INSERT INTO languages (iso_639_1,name) values ('za','Zhuang'); 103 | INSERT INTO languages (iso_639_1,name) values ('ae','Avestan'); 104 | INSERT INTO languages (iso_639_1,name) values ('an','Aragonese'); 105 | INSERT INTO languages (iso_639_1,name) values ('ay','Aymara'); 106 | INSERT INTO languages (iso_639_1,name) values ('bg','Bulgarian'); 107 | INSERT INTO languages (iso_639_1,name) values ('bn','Bengali'); 108 | INSERT INTO languages (iso_639_1,name) values ('ca','Catalan'); 109 | INSERT INTO languages (iso_639_1,name) values ('cv','Chuvash'); 110 | INSERT INTO languages (iso_639_1,name) values ('dv','Divehi'); 111 | INSERT INTO languages (iso_639_1,name) values ('en','English'); 112 | INSERT INTO languages (iso_639_1,name) values ('eu','Basque'); 113 | INSERT INTO languages (iso_639_1,name) values ('fj','Fijian'); 114 | INSERT INTO languages (iso_639_1,name) values ('ga','Irish'); 115 | INSERT INTO languages (iso_639_1,name) values ('gu','Gujarati'); 116 | INSERT INTO languages (iso_639_1,name) values ('hi','Hindi'); 117 | INSERT INTO languages (iso_639_1,name) values ('hu','Hungarian'); 118 | INSERT INTO languages (iso_639_1,name) values ('id','Indonesian'); 119 | INSERT INTO languages (iso_639_1,name) values ('ik','Inupiaq'); 120 | INSERT INTO languages (iso_639_1,name) values ('ja','Japanese'); 121 | INSERT INTO languages (iso_639_1,name) values ('ka','Georgian'); 122 | INSERT INTO languages (iso_639_1,name) values ('kk','Kazakh'); 123 | INSERT INTO languages (iso_639_1,name) values ('ko','Korean'); 124 | INSERT INTO languages (iso_639_1,name) values ('kv','Komi'); 125 | INSERT INTO languages (iso_639_1,name) values ('lb','Luxembourgish'); 126 | INSERT INTO languages (iso_639_1,name) values ('lo','Lao'); 127 | INSERT INTO languages (iso_639_1,name) values ('mg','Malagasy'); 128 | INSERT INTO languages (iso_639_1,name) values ('ml','Malayalam'); 129 | INSERT INTO languages (iso_639_1,name) values ('ms','Malay'); 130 | INSERT INTO languages (iso_639_1,name) values ('nb','Bokm\u00E5l, Norwegian'); 131 | INSERT INTO languages (iso_639_1,name) values ('nl','Dutch'); 132 | INSERT INTO languages (iso_639_1,name) values ('nv','Navajo'); 133 | INSERT INTO languages (iso_639_1,name) values ('om','Oromo'); 134 | INSERT INTO languages (iso_639_1,name) values ('pi','Pali'); 135 | INSERT INTO languages (iso_639_1,name) values ('qu','Quechua'); 136 | INSERT INTO languages (iso_639_1,name) values ('ru','Russian'); 137 | INSERT INTO languages (iso_639_1,name) values ('sd','Sindhi'); 138 | INSERT INTO languages (iso_639_1,name) values ('si','Sinhala'); 139 | INSERT INTO languages (iso_639_1,name) values ('sn','Shona'); 140 | INSERT INTO languages (iso_639_1,name) values ('ss','Swati'); 141 | INSERT INTO languages (iso_639_1,name) values ('sw','Swahili'); 142 | INSERT INTO languages (iso_639_1,name) values ('th','Thai'); 143 | INSERT INTO languages (iso_639_1,name) values ('tn','Tswana'); 144 | INSERT INTO languages (iso_639_1,name) values ('tt','Tatar'); 145 | INSERT INTO languages (iso_639_1,name) values ('uk','Ukrainian'); 146 | INSERT INTO languages (iso_639_1,name) values ('vi','Vietnamese'); 147 | INSERT INTO languages (iso_639_1,name) values ('xh','Xhosa'); 148 | INSERT INTO languages (iso_639_1,name) values ('zh','Chinese'); 149 | INSERT INTO languages (iso_639_1,name) values ('af','Afrikaans'); 150 | INSERT INTO languages (iso_639_1,name) values ('ar','Arabic'); 151 | INSERT INTO languages (iso_639_1,name) values ('az','Azerbaijani'); 152 | INSERT INTO languages (iso_639_1,name) values ('bh','Bihari languages'); 153 | INSERT INTO languages (iso_639_1,name) values ('bo','Tibetan'); 154 | INSERT INTO languages (iso_639_1,name) values ('ce','Chechen'); 155 | INSERT INTO languages (iso_639_1,name) values ('cr','Cree'); 156 | INSERT INTO languages (iso_639_1,name) values ('cy','Welsh'); 157 | INSERT INTO languages (iso_639_1,name) values ('dz','Dzongkha'); 158 | INSERT INTO languages (iso_639_1,name) values ('eo','Esperanto'); 159 | INSERT INTO languages (iso_639_1,name) values ('fa','Persian'); 160 | INSERT INTO languages (iso_639_1,name) values ('fo','Faroese'); 161 | INSERT INTO languages (iso_639_1,name) values ('gd','Gaelic'); 162 | INSERT INTO languages (iso_639_1,name) values ('gv','Manx'); 163 | INSERT INTO languages (iso_639_1,name) values ('ho','Hiri Motu'); 164 | INSERT INTO languages (iso_639_1,name) values ('hy','Armenian'); 165 | INSERT INTO languages (iso_639_1,name) values ('ie','Interlingue'); 166 | INSERT INTO languages (iso_639_1,name) values ('it','Italian'); 167 | INSERT INTO languages (iso_639_1,name) values ('kg','Kongo'); 168 | INSERT INTO languages (iso_639_1,name) values ('kl','Kalaallisut'); 169 | INSERT INTO languages (iso_639_1,name) values ('kr','Kanuri'); 170 | INSERT INTO languages (iso_639_1,name) values ('kw','Cornish'); 171 | INSERT INTO languages (iso_639_1,name) values ('lg','Ganda'); 172 | INSERT INTO languages (iso_639_1,name) values ('lt','Lithuanian'); 173 | INSERT INTO languages (iso_639_1,name) values ('mh','Marshallese'); 174 | INSERT INTO languages (iso_639_1,name) values ('mn','Mongolian'); 175 | INSERT INTO languages (iso_639_1,name) values ('mt','Maltese'); 176 | INSERT INTO languages (iso_639_1,name) values ('nd','Ndebele, North'); 177 | INSERT INTO languages (iso_639_1,name) values ('nn','Norwegian Nynorsk'); 178 | INSERT INTO languages (iso_639_1,name) values ('ny','Chichewa'); 179 | INSERT INTO languages (iso_639_1,name) values ('or','Oriya'); 180 | INSERT INTO languages (iso_639_1,name) values ('pl','Polish'); 181 | INSERT INTO languages (iso_639_1,name) values ('rm','Romansh'); 182 | INSERT INTO languages (iso_639_1,name) values ('rw','Kinyarwanda'); 183 | INSERT INTO languages (iso_639_1,name) values ('se','Northern Sami'); 184 | INSERT INTO languages (iso_639_1,name) values ('sk','Slovak'); 185 | INSERT INTO languages (iso_639_1,name) values ('so','Somali'); 186 | INSERT INTO languages (iso_639_1,name) values ('st','Sotho, Southern'); 187 | INSERT INTO languages (iso_639_1,name) values ('ta','Tamil'); 188 | INSERT INTO languages (iso_639_1,name) values ('ti','Tigrinya'); 189 | INSERT INTO languages (iso_639_1,name) values ('to','Tonga (Tonga Islands)'); 190 | INSERT INTO languages (iso_639_1,name) values ('tw','Twi'); 191 | INSERT INTO languages (iso_639_1,name) values ('ur','Urdu'); 192 | INSERT INTO languages (iso_639_1,name) values ('vo','Volap\u00FCk'); 193 | INSERT INTO languages (iso_639_1,name) values ('yi','Yiddish'); 194 | INSERT INTO languages (iso_639_1,name) values ('zu','Zulu'); 195 | 196 | CREATE TABLE language_skill ( 197 | skill TEXT NOT NULL 198 | UNIQUE, 199 | seq INTEGER NOT NULL 200 | UNIQUE 201 | ); 202 | GRANT SELECT ON TABLE language_skill to www; 203 | GRANT ALL ON TABLE language_skill TO sysadmin; 204 | 205 | INSERT INTO language_skill (skill,seq) VALUES ('native',4); 206 | INSERT INTO language_skill (skill,seq) VALUES ('expert',3); 207 | INSERT INTO language_skill (skill,seq) VALUES ('intermediate',2); 208 | INSERT INTO language_skill (skill,seq) VALUES ('basic',1); 209 | 210 | CREATE TABLE member_language_skill ( 211 | member TEXT NOT NULL REFERENCES member(ident) 212 | ON UPDATE CASCADE 213 | ON DELETE CASCADE, 214 | language TEXT NOT NULL REFERENCES languages(iso_639_1) 215 | ON UPDATE CASCADE 216 | ON DELETE CASCADE, 217 | skill TEXT NOT NULL REFERENCES language_skill(skill) 218 | ON UPDATE CASCADE 219 | ON DELETE CASCADE, 220 | entered TIMESTAMP NOT NULL DEFAULT NOW()::TIMESTAMP 221 | ); 222 | 223 | GRANT ALL ON TABLE member_language_skill TO sysadmin; 224 | GRANT ALL ON TABLE member_language_skill TO www; 225 | -------------------------------------------------------------------------------- /webroot/site/edit_email.html: -------------------------------------------------------------------------------- 1 | <%args> 2 | $ident => '' 3 | $email => '' 4 | $descr => '' 5 | $pgpkey_id => '' 6 | $no_email => '' 7 | $submit => '' 8 | $form_id => '' 9 | $token => '' 10 | $trustgroup => '' 11 | 12 | 13 |

Edit E-mail and PGP Information

14 | <%perl> 15 | use Digest::SHA; 16 | # - E-mails from any "known" address will be accepted. 17 | # - Each TG will only send TO it's designated email. 18 | 19 | my $postscript = ''; 20 | my @stmts = (); 21 | $email =~ tr/A-Z/a-z/; 22 | $email = &common::ExtractAddr($email); 23 | my $db_email = $dbh->quote($email); 24 | 25 | if ($submit eq 'Add Email') { 26 | push @stmts, "INSERT INTO member_email 27 | (member,email) 28 | VALUES ($Site->{db_member},$db_email)"; 29 | $postscript .= $q->hr() . $q->p("E-mail address added."); 30 | } elsif ($submit eq 'Verify' or $submit eq 'Restart Verify'){ 31 | #Generate token 32 | my $token = substr &common::rand_str().&common::rand_str(),1,10; 33 | my $db_token = $dbh->quote(Digest::SHA::sha256_hex($token)); 34 | #record in DB 35 | push @stmts, " 36 | UPDATE member_email 37 | SET verify_token = $db_token 38 | WHERE member = $Site->{db_member} 39 | AND email = $db_email"; 40 | #E-mail Token. 41 | &common::notify_email_verify($Site->{member}, $email, $token); 42 | $postscript .= $q->hr() . $q->p("Token issued, check $email"); 43 | } elsif ($submit eq 'Submit Verify') { #Attempt to Confirm Verify Token. 44 | my $detail = $dbh->selectrow_hashref(qq{ 45 | SELECT me.verified,me.verify_token 46 | FROM member_email me 47 | WHERE member = $Site->{db_member} 48 | AND email = $db_email 49 | }); 50 | my $sha_token = Digest::SHA::sha256_hex($token); 51 | if ($sha_token eq $detail->{'verify_token'}) { 52 | $postscript .= "E-mail address Verified."; 53 | push @stmts, " 54 | UPDATE member_email 55 | SET verify_token = NULL, verified = 't' 56 | WHERE member = $Site->{db_member} 57 | AND email = $db_email"; 58 | } else { 59 | $postscript .= "Invalid Verify Token."; 60 | } 61 | } elsif ($submit eq 'Set Email'){ 62 | my $db_trustgroup = $dbh->quote($trustgroup); 63 | #Confirm PGP/verified status. 64 | my $update = 1; 65 | my $row = $Site->{dbh}->selectrow_hashref(qq{ 66 | SELECT tg.pgp_required, me.pgpkey_id 67 | FROM trustgroup tg 68 | JOIN member_trustgroup mt ON mt.trustgroup = tg.ident 69 | JOIN member_email me on mt.member = me.member 70 | WHERE tg.ident = $db_trustgroup 71 | AND mt.member = $Site->{db_member} 72 | AND me.email = $db_email 73 | AND me.verified 74 | }); 75 | if (!defined $row) { 76 | $postscript .= $q->hr() . $q->p("Invalid Request"); 77 | $update = 0; 78 | } else { 79 | if ($row->{pgp_required} and !defined $row->{pgpkey_id}){ 80 | $postscript .= $q->hr() . $q->p("PGP required for TG:$trustgroup"); 81 | $update = 0; 82 | } 83 | } 84 | if ($update){ 85 | #XXX: Add check for freemail per tg setting. 86 | push @stmts, " 87 | UPDATE member_trustgroup 88 | SET email = $db_email 89 | WHERE member = $Site->{db_member} 90 | AND trustgroup = $db_trustgroup"; 91 | $postscript .= $q->hr() . $q->p("New E-mail address set."); 92 | } 93 | } elsif ($submit eq 'Delete Email'){ 94 | my $update = 1; 95 | #Confirm that e-mail is not recovery, 96 | my $row = $Site->{dbh}->selectrow_hashref(qq{ 97 | SELECT m.recover_email 98 | FROM member m 99 | WHERE m.ident = $Site->{db_member} 100 | }); 101 | if (defined $row) { 102 | if ($row->{recover_email} eq $email){ 103 | $postscript .= $q->hr() . $q->p("E-mail is set as recovery"); 104 | $update = 0; 105 | } 106 | } 107 | #Confirm that e-mail is not a member of a TG. 108 | $row = $Site->{dbh}->selectrow_hashref(qq{ 109 | SELECT mt.trustgroup 110 | FROM member_trustgroup mt 111 | WHERE mt.member = $Site->{db_member} 112 | AND mt.email = $db_email 113 | }); 114 | if (defined $row) { 115 | $postscript .= $q->hr() . $q->p("E-mail in use by TG: $row->{trustgroup}"); 116 | $update = 0; 117 | } 118 | if ($update) { 119 | push @stmts, " 120 | DELETE FROM member_email 121 | WHERE member = $Site->{db_member} 122 | AND email = $db_email"; 123 | $postscript .= $q->hr() . $q->p("$db_email Deleted."); 124 | } 125 | } elsif ($submit eq 'Upload PGP'){ 126 | my $keyring_data = ''; 127 | my $fh = $q->upload('pgpkey'); 128 | if (defined $fh) { 129 | unlink "${keyring}.temp"; 130 | my $len = 0; 131 | my $keyfile = undef; 132 | open($keyfile, ">${keyring}.temp"); 133 | while (read($fh, my $buffer, 4096)) { 134 | print {$keyfile} $buffer; 135 | $len += length $buffer; 136 | # store it in a string for db storage 137 | $keyring_data .= $buffer; 138 | } 139 | close($keyfile); 140 | # Only accept ASCII armored data 141 | if ($len == 0) { 142 | $postscript .= $q->hr . 143 | $q->p('Empty file, not installed.'); 144 | } elsif ($keyring_data =~ /PGP PUBLIC KEY/) { 145 | $postscript .= $q->hr . 146 | $q->p('loading ASCII armored keys'); 147 | unlink &common::gpgcmd_myfiles($Site->{uuid}); 148 | my $cmd = "$my_gpgcmd --ignore-time-conflict ". 149 | "--import ${keyring}.temp 2>&1"; 150 | $postscript .= $q->hr; 151 | foreach (`$cmd`) { 152 | chomp; 153 | # XXX: should hide our paths 154 | $postscript .= $q->code($q->escapeHTML($_)) . 155 | $q->br; 156 | } 157 | my $db_uuid = $dbh->quote($Site->{uuid}); 158 | foreach my $detail (@{$dbh->selectall_arrayref(qq{ 159 | SELECT m.ident 160 | FROM member_email me 161 | JOIN member m ON m.ident = me.member 162 | WHERE m.uuid = $db_uuid 163 | AND me.email = $db_email 164 | }, {Slice => {}})}) { 165 | my $keyid = (&common::gpgcmd_mykeys($Site->{uuid},$email))[0]; 166 | my @expire = &common::gpgcmd_keyexpire($Site->{uuid},$keyid); 167 | $postscript .= $q->hr . 168 | $q->p("Loading Key: $keyid for $email\n") . 169 | $q->br; 170 | my $db_expire = @expire ? $dbh->quote($expire[0]) : 'NULL'; 171 | my $db_keyid = $dbh->quote($keyid); 172 | my $db_email = $dbh->quote($email); 173 | my $db_keydata = $dbh->quote($keyring_data); 174 | my $db_member = $dbh->quote($detail->{'ident'}); 175 | push @stmts, qq{ 176 | UPDATE member_email me 177 | SET 178 | pgpkey_id = $db_keyid, 179 | pgpkey_expire = $db_expire, 180 | keyring = $db_keydata, 181 | keyring_update_at = now() 182 | WHERE me.email = $db_email 183 | AND me.member = $db_member; 184 | }; 185 | } 186 | } else { 187 | $postscript .= $q->hr . 188 | $q->p('We only accept ASCII armored keys'); 189 | } 190 | rename "${keyring}.temp", 191 | $common::tmpdir.'/'.$Site->{member}.'.temp'; 192 | } 193 | } elsif ($submit eq 'Make recovery E-mail'){ 194 | #TODO: Confirm that E-mail has PGP before setting. 195 | push @stmts, " 196 | UPDATE member 197 | SET recover_email = $db_email 198 | WHERE ident = $Site->{db_member}"; 199 | $postscript .= $q->hr() . $q->p("$db_email Set as password recover address."); 200 | } 201 | 202 | if (@stmts){ 203 | my $errstr = &common::audited_do($dbh, $Site->{member}, @stmts); 204 | if (length $errstr) { 205 | $postscript .= $q->hr() . $q->p("Failure: $errstr"); 206 | } else { 207 | $postscript .= $q->hr() . $q->p('Profile has been updated.'); 208 | } 209 | } 210 | $q->delete_all(); 211 | 212 | # 213 | # Collect main details for member. 214 | # 215 | my $row = $Site->{dbh}->selectrow_hashref(qq{ 216 | SELECT m.ident, m.descr, m.no_email, m.recover_email 217 | FROM member m 218 | WHERE m.ident = $Site->{db_member} 219 | }); 220 | if (!defined $row) { 221 | print $q->p("No contact information on file?"); 222 | return; 223 | } 224 | 225 | if ($row->{no_email}) { 226 | print $q->h2("E-mail has been disabled for this account."); 227 | print $q->h3("Please contact technical\@support.ops-trust.net."); 228 | return; 229 | } 230 | 231 | $form_id = &common::new_form_id(); 232 | $Mech->{sess}->param('form_id', $form_id); 233 | $Mech->{sess}->flush(); 234 | 235 | # 236 | # Generate list of E-mail addresses. 237 | # 238 | 239 | my $emails; 240 | my @verified_emails = (); 241 | my @verified_pgp_emails = (); 242 | my $row_count = 0; 243 | 244 | foreach my $email (@{$dbh->selectall_arrayref(qq{ 245 | SELECT me.email,me.pgpkey_id,me.verified,me.verify_token, 246 | me.pgpkey_expire,me.keyring,me.keyring_update_at, 247 | ARRAY( 248 | SELECT trustgroup 249 | FROM member_trustgroup mt 250 | JOIN member_state ms ON ms.ident = mt.state 251 | WHERE email = me.email 252 | AND ms.can_see 253 | ) as trustgroups 254 | FROM member_email me 255 | WHERE me.member = $Site->{db_member} 256 | ORDER BY me.email 257 | }, {Slice => {}})}) { 258 | if ($email->{verified}) { 259 | push @verified_emails,$email->{email}; 260 | if (defined $email->{pgpkey_id}) { 261 | push @verified_pgp_emails,$email->{email}; 262 | } 263 | } 264 | $email->{recovery_address} = $row->{recover_email}; 265 | my $zebra_class = ($row_count %2) ? "a" : "b"; 266 | my $tg_list = join(" | ",@{$email->{trustgroups}}); 267 | $emails .= $q->Tr({-class => "$zebra_class"}, 268 | $q->td($email->{email} ), 269 | $q->td( &render_pgp($email) ), 270 | $q->td( &render_verify($email) ), 271 | $q->td( 272 | $q->start_table({-border=>0, -cellspacing=>0, -cellpadding=>0}), 273 | $q->Tr($q->td({-valign=>'MIDDLE'},$tg_list)), 274 | $q->end_table 275 | ), 276 | $q->td( &render_actions($email) ) 277 | ); 278 | $row_count++; 279 | }; 280 | 281 | my $email_list = 282 | $emails . 283 | $q->Tr($q->td({-colspan=>3}, 284 | $q->start_form(), 285 | $q->start_table({-border=>0, -cellspacing=>0, -cellpadding=>0}), 286 | $q->Tr( 287 | $q->td({-colspan=>2}, 288 | $q->textfield( 289 | -name => 'email', 290 | -override => 1, 291 | -size => 30) . 292 | ' (e.g.,bob@aol.com)'. 293 | $q->td( $q->submit('submit','Add Email')) 294 | )), 295 | $q->end_table, 296 | $q->end_form 297 | )); 298 | 299 | #List Trustgroup 300 | 301 | my $trustgroups; 302 | $row_count = 0; 303 | foreach my $tg (@{$dbh->selectall_arrayref(qq{ 304 | SELECT tg.ident,tg.descr,mt.email,mt.state,tg.pgp_required 305 | FROM member_trustgroup mt 306 | JOIN member_state ms ON ms.ident = mt.state 307 | JOIN trustgroup tg ON tg.ident = mt.trustgroup 308 | WHERE mt.member = $Site->{db_member} 309 | AND ms.can_see 310 | ORDER BY tg.ident; 311 | }, {Slice => {}})}) { 312 | if ($tg->{pgp_required}) { 313 | $tg->{email_list} = \@verified_pgp_emails; 314 | } else { 315 | $tg->{email_list} = \@verified_emails; 316 | } 317 | my $zebra_class = ($row_count %2) ? "a" : "b"; 318 | $trustgroups .= $q->Tr({-class => "$zebra_class"}, 319 | $q->td($tg->{ident}) . 320 | $q->td($tg->{descr}) . 321 | $q->td($tg->{state}) . 322 | $q->td($tg->{email}) . 323 | &render_email_select($tg) 324 | ); 325 | $row_count++; 326 | }; 327 | 328 | print $q->start_table({-border=>0, -cellspacing=>1, -cellpadding=>1}); 329 | print $q->Tr( 330 | $q->td({colspan => 4},$q->h3("E-mail addresses:")) 331 | ); 332 | print $q->Tr( 333 | $q->td("E-mail") . 334 | $q->td("PGP Key") . 335 | $q->td("Verify") . 336 | $q->td("Trustgroups") . 337 | $q->td("Actions") 338 | ); 339 | print $email_list; 340 | print $q->end_table; 341 | print $q->br; 342 | print $q->start_table({-border=>0, -cellspacing=>1, -cellpadding=>1}); 343 | print $q->Tr( 344 | $q->td($q->h3("Trustgroups:")) 345 | ); 346 | print $q->Tr( 347 | $q->td("Trustgroup") . 348 | $q->td("Description") . 349 | $q->td("State") . 350 | $q->td("E-mail") . 351 | $q->td("Set E-mail") 352 | ); 353 | print $trustgroups ; 354 | print $q->end_table; 355 | print $postscript; 356 | 357 | sub render_verify($) { 358 | my $row = shift; 359 | if (defined $row->{verify_token}) { 360 | $q->start_table({-border=>0, -cellspacing=>0, -cellpadding=>0}) . 361 | $q->Tr($q->td( 362 | $q->start_form(-action => '#'.$row->{email}) . 363 | $q->hidden('email', $row->{email}) . 364 | $q->textfield( 365 | -name => 'token', 366 | -override => 1, 367 | -size => 15) . 368 | $q->submit('submit', 'Submit Verify') . 369 | $q->end_form 370 | ).$q->td( 371 | $q->start_form(-action => '#'.$row->{email}) . 372 | $q->hidden('email', $row->{email}) . 373 | $q->submit('submit', 'Restart Verify') . 374 | $q->end_form)). 375 | $q->end_table 376 | } elsif (!$row->{verified}) { 377 | $q->start_form(-action => '#'.$row->{email}) . 378 | $q->hidden('email', $row->{email}) . 379 | $q->submit('submit', 'Verify') . 380 | $q->end_form; 381 | } else { 382 | if ($row->{recovery_address} eq $row->{email}) { 383 | $q->p("Verified
Recovery"); 384 | } else { 385 | $q->p("Verified"); 386 | } 387 | } 388 | } 389 | 390 | sub render_pgp($) { 391 | my $row = shift; 392 | my $table = ''; 393 | if (defined $row->{pgpkey_id}) { 394 | $table = 395 | $q->start_table({-border=>0, -cellspacing=>0, -cellpadding=>0}) . 396 | $q->Tr($q->td("KeyID:"),$q->td($row->{pgpkey_id})) . 397 | $q->Tr($q->td("Expire:"),$q->td($row->{pgpkey_expire})) . 398 | $q->Tr($q->td("Update:"),$q->td($row->{keyring_update_at})) . 399 | $q->end_table 400 | } 401 | $table . 402 | $q->start_form(-action => '#'.$row->{email}) . 403 | $q->p('Upload PGP Key:') . 404 | $q->hidden('email', $row->{email}) . 405 | $q->filefield(-name => 'pgpkey', -default => '') . 406 | $q->submit('submit', 'Upload PGP') . 407 | $q->end_form; 408 | } 409 | 410 | sub render_actions($) { 411 | my $row = shift; 412 | $q->start_table({-border=>0, -cellspacing=>0, -cellpadding=>0}) . 413 | &render_delete($row) . 414 | &render_recover($row) . 415 | $q->end_table; 416 | } 417 | 418 | sub render_delete($) { 419 | my $row = shift; 420 | return if (defined $row->{trustgroups}[0]); 421 | return if ($row->{recovery_address} eq $row->{email}); 422 | $q->Tr($q->td( 423 | $q->start_form(), 424 | $q->hidden('email', $row->{email}) . 425 | $q->submit('submit', 'Delete Email') . 426 | $q->end_form 427 | )); 428 | } 429 | 430 | sub render_recover($) { 431 | my $row = shift; 432 | return if ($row->{recovery_address} eq $row->{email}); 433 | return if (!defined $row->{pgpkey_id}); 434 | $q->Tr($q->td( 435 | $q->start_form(), 436 | $q->hidden('email', $row->{email}) . 437 | $q->submit('submit', 'Make recovery E-mail') . 438 | $q->end_form 439 | )); 440 | } 441 | 442 | sub render_email_select() { 443 | my ($row) = shift; 444 | $q->td( 445 | $q->start_form(-action => '#'.$row->{ident}) . 446 | $q->hidden('trustgroup', $row->{ident}) . 447 | $q->popup_menu(-name => 'email', 448 | -values => $row->{email_list}) . 449 | $q->submit('submit', 'Set Email') . 450 | $q->end_form 451 | ); 452 | } 453 | 454 | 455 | 456 | <%shared> 457 | our $q = undef; 458 | my $dbh = undef; 459 | my $tg = undef; 460 | my $keyring = undef; 461 | my $my_gpgcmd = undef; 462 | 463 | 464 | <%init> 465 | $q = $Mech->{cgi}; 466 | $dbh = $Site->{dbh}; 467 | $tg = $Site->{tg}; 468 | $keyring = sprintf '%s/%s', &common::gpg_key_path($Site->{uuid}), $Site->{uuid}; 469 | $my_gpgcmd = &common::gpgcmd_user($Site->{uuid}); 470 | 471 | 472 | <%method vetting_not_needed> 473 | # tells sitehandler.mas:request_ok that we don't need vetting here 474 | 475 | 476 | <%method tg_not_needed> 477 | # tells sitehandler.mas:request_ok that we don't need a trustgroup here 478 | 479 | --------------------------------------------------------------------------------
<& /site/decor.mas:banner &>
<& /mech/leftside.mas &> 15 | -------------------------------------------------------------------------------- /webroot/index.html: -------------------------------------------------------------------------------- 1 | <%args> 2 | $next => undef 3 | 4 | % if (defined $Mech->{sess}->param('~logged-in')) { 5 | <& /site/index_private.mas, next => "$next" &> 6 | % } else { 7 | <& /site/index_public.mas &> 8 | % } 9 | 10 | <%method is_public> 11 | # no-op, here to allow display even while not logged in 12 | 13 | 14 | <%method tg_not_needed> 15 | # tells sitehandler.mas:request_ok that we don't need a trustgroup here 16 | 17 | 18 | <%method vetting_not_needed> 19 | # tells sitehandler.mas:request_ok that we don't need vetting here 20 | 21 | -------------------------------------------------------------------------------- /library/db_migrations/20-AUG-2013.psql: -------------------------------------------------------------------------------- 1 | ; Add a name field to the member_detail_types table 2 | ALTER TABLE member_detail_types add display_name text; 3 | UPDATE member_detail_types set display_name = 'Amateur radio callsign' where type = 'callsign'; 4 | ALTER TABLE member_detail_types ALTER COLUMN display_name SET NOT NULL; 5 | ALTER TABLE member_detail_types ADD CONSTRAINT member_detail_types_display_name_idx UNIQUE (display_name); 6 | 7 | ; Add an index that prevents a user from having more than one language. 8 | ALTER TABLE member_language_skill ADD CONSTRAINT member_language_skill_member_language_idx UNIQUE (member, language); 9 | -------------------------------------------------------------------------------- /webroot/mech/Makefile: -------------------------------------------------------------------------------- 1 | FILES= expired.html header.mas logout.html footer.mas index.html 2 | MADE= login.html leftside.mas 3 | 4 | all: ${MADE} 5 | 6 | clean:; rm -f ${MADE} 7 | 8 | install: all 9 | @( echo mkdir -p !mechdir!; \ 10 | echo install -m 644 -o !wwwuid! -g !wwwgid! \ 11 | ${MADE} ${FILES} !mechdir!/; \ 12 | ) | \ 13 | perl ../../mycat.pl ../../siteconfig | \ 14 | sh -x 15 | 16 | login.html: login.html.template ../../siteconfig 17 | perl ../../mycat.pl ../../siteconfig < login.html.template > $@ 18 | 19 | leftside.mas: leftside.mas.template ../../siteconfig 20 | perl ../../mycat.pl ../../siteconfig < leftside.mas.template > $@ 21 | -------------------------------------------------------------------------------- /library/db_migrations/12-AUG-2013.psql: -------------------------------------------------------------------------------- 1 | CREATE TABLE member_detail_types ( 2 | type TEXT NOT NULL 3 | UNIQUE 4 | ); 5 | GRANT SELECT ON TABLE member_detail_types to www; 6 | GRANT ALL ON TABLE member_detail_types TO sysadmin; 7 | 8 | INSERT INTO member_detail_types VALUES ('callsign'); 9 | 10 | CREATE TABLE member_details ( 11 | member TEXT NOT NULL REFERENCES member(ident) 12 | ON UPDATE CASCADE 13 | ON DELETE CASCADE, 14 | type TEXT NOT NULL REFERENCES member_detail_types(type) 15 | ON UPDATE CASCADE 16 | ON DELETE CASCADE, 17 | entered TIMESTAMP NOT NULL DEFAULT NOW()::TIMESTAMP, 18 | value TEXT NOT NULL 19 | ); 20 | GRANT ALL ON TABLE member_details TO www,sysadmin; 21 | 22 | -------------------------------------------------------------------------------- /webroot/mech/login.html.template: -------------------------------------------------------------------------------- 1 | <%args> 2 | $next => undef 3 | 4 | 5 | <%perl> 6 | print qq{ 7 |

This login form is no longer in-service. 8 | Please update your links and bookmarks to refer to : !authurl! 9 | }; 10 | if($next) { 11 | print '!authurl!?next='.$next.'.

'; 12 | } else { 13 | print '!authurl!.

'; 14 | } 15 | 16 | 17 | 18 | <%method is_public> 19 | # no-op, just here to tell autohandler that this page can be 20 | # accessed without logging in first. 21 | 22 | 23 | <%method tg_not_needed> 24 | # tells sitehandler.mas:request_ok that we don't need a trustgroup here 25 | 26 | 27 | <%method vetting_not_needed> 28 | # tells sitehandler.mas:request_ok that we don't need vetting here 29 | 30 | -------------------------------------------------------------------------------- /webroot/Makefile: -------------------------------------------------------------------------------- 1 | SUBDIRS= mech site 2 | 3 | FILES= favicon.ico index.html robots.txt style.css 4 | 5 | MADE= autohandler 6 | 7 | all: ${MADE} 8 | @set -e -x; for subdir in ${SUBDIRS}; do \ 9 | (cd $$subdir; ${MAKE} ${MARGS} $@); \ 10 | done 11 | 12 | clean: 13 | rm -f ${MADE} 14 | @set -e -x; for subdir in ${SUBDIRS}; do \ 15 | (cd $$subdir; ${MAKE} ${MARGS} $@); \ 16 | done 17 | 18 | install: 19 | @( echo mkdir -p !webroot!; \ 20 | echo install -m 644 -o !wwwuid! -g !wwwgid! \ 21 | ${MADE} ${FILES} !webroot!; \ 22 | ) | \ 23 | perl ../mycat.pl ../siteconfig | \ 24 | sh -x 25 | @set -e -x; for subdir in ${SUBDIRS}; do \ 26 | (cd $$subdir; ${MAKE} ${MARGS} install); \ 27 | done 28 | 29 | autohandler: autohandler.template ../siteconfig 30 | perl ../mycat.pl ../siteconfig < autohandler.template > $@ 31 | 32 | -------------------------------------------------------------------------------- /siteconfig.testing: -------------------------------------------------------------------------------- 1 | brand='Ops-TT' 2 | title='Operations Security Test' 3 | coname='OpSecTest' 4 | domain='ops-test.local' 5 | wiki='https://wiki.ops-trust.net/%s/bin/view' 6 | supportre='/^https:\/\/ops.ops-trust.net\/rt/' 7 | wikire='/^https:\/\/wiki.ops-trust.net\/([a-z]+)\//' 8 | pghost='/var/run/postgresql' 9 | pgport=5433 10 | pgname='ops-trust' 11 | pguser='postgres' 12 | portal='/proj/ops-trust' 13 | library='/proj/ops-trust/library' 14 | webroot='/proj/ops-trust/webroot' 15 | sitedir='/proj/ops-trust/webroot/site' 16 | mechdir='/proj/ops-trust/webroot/mech' 17 | webvar='/proj/ops-trust/webvar' 18 | wwwuid=33 19 | wwwgid=33 20 | portalname='portal' 21 | sslcertfile='/etc/ssl/certs/yourcert.pem' 22 | sslkeyfile='/etc/ssl/private/yourkeyhere' 23 | sslchainfile='/etc/ssl/certs/chainlovin' 24 | supportemail='technical\@support.!domain!' 25 | authurl='https://openid.ops-trust.net/' 26 | -------------------------------------------------------------------------------- /library/cronrun-week.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | cd $(dirname $0) || exit 1 21 | 22 | . ./funcs.sh 23 | 24 | # Lock this crontab 25 | cron_lock $(basename $0) 26 | 27 | ./notify-idle 28 | 29 | # Unlock this crontab 30 | cron_unlock 31 | 32 | exit 33 | -------------------------------------------------------------------------------- /webroot/mech/logout.html: -------------------------------------------------------------------------------- 1 | <%perl> 2 | my $username = undef; 3 | $username = $Mech->{sess}->param('member') 4 | unless $Mech->{sess}->is_empty(); 5 | if (defined $username && length $username) { 6 | $m->comp('/site/sitehandler.mas:logout'); 7 | } 8 | print qq{

You should now be redirected to 9 | the main page.

}; 10 | $r->header_out('Refresh' => '0; url="/"'); 11 | 12 | 13 | <%method is_logout> 14 | # this does nothing, it's here to tell autohandler not to say 'expired' 15 | # if the user is in the process of logging out. 16 | 17 | 18 | <%method is_public> 19 | # this does nothing, it's here to tell autohandler that we can be called 20 | # even when we're not logged in. 21 | 22 | 23 | <%method tg_not_needed> 24 | # tells sitehandler.mas:request_ok that we don't need a trustgroup here 25 | 26 | 27 | <%method vetting_not_needed> 28 | # tells sitehandler.mas:request_ok that we don't need vetting here 29 | 30 | -------------------------------------------------------------------------------- /library/cronrun-minute.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | cd $(dirname $0) || exit 1 21 | 22 | . ./funcs.sh 23 | 24 | # Lock this crontab 25 | cron_lock $(basename $0) 26 | 27 | ./state-mon 28 | ./fsck-mlkeys 29 | ./dbck-password 30 | 31 | # Unlock this crontab 32 | cron_unlock 33 | 34 | exit 35 | -------------------------------------------------------------------------------- /library/db_migrations/09-DEC-2013_2FA.psql: -------------------------------------------------------------------------------- 1 | ;prepare to add support for second factor auth. 2 | 3 | CREATE TABLE second_factor_types ( 4 | type TEXT NOT NULL 5 | UNIQUE 6 | ); 7 | GRANT SELECT ON TABLE second_factor_types to www; 8 | GRANT ALL ON TABLE second_factor_types TO sysadmin; 9 | 10 | CREATE TABLE second_factors ( 11 | uuid UUID NOT NULL UNIQUE PRIMARY KEY, 12 | member TEXT NOT NULL REFERENCES member(ident) 13 | ON UPDATE CASCADE 14 | ON DELETE CASCADE, 15 | type TEXT NOT NULL REFERENCES second_factor_types(type) 16 | ON UPDATE CASCADE 17 | ON DELETE CASCADE, 18 | entered TIMESTAMP NOT NULL DEFAULT NOW()::TIMESTAMP, 19 | active BOOLEAN NOT NULL DEFAULT false, 20 | counter INTEGER DEFAULT 0, 21 | key TEXT 22 | ); 23 | GRANT ALL ON TABLE second_factors TO www,sysadmin; 24 | 25 | INSERT INTO second_factor_types VALUES ('HOTP'); 26 | INSERT INTO second_factor_types VALUES ('TOTP'); 27 | -------------------------------------------------------------------------------- /library/remove-from-tg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -x 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | # $0 email tgname 21 | 22 | cd $(dirname $0) || exit 1 23 | 24 | . ./funcs.sh 25 | 26 | if [ $# -lt 2 ]; then 27 | echo "Usage: $0 " 28 | exit 1 29 | fi 30 | 31 | echo "DELETE FROM member_trustgroup WHERE trustgroup='$2' AND email='$1';" | portal_query 32 | 33 | exit 34 | -------------------------------------------------------------------------------- /library/rename-email.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -x 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | # $0 old new 21 | 22 | cd $(dirname $0) || exit 1 23 | 24 | . ./funcs.sh 25 | 26 | if [ "z$1" = "z" -o "z$2" = "z" ]; then 27 | echo "Usage: $0 " 28 | exit 1 29 | fi 30 | 31 | echo "UPDATE member_email SET email='$2' WHERE email='$1';" | portal_query 32 | 33 | exit 34 | -------------------------------------------------------------------------------- /library/db_migrations/DB_6.psql: -------------------------------------------------------------------------------- 1 | -- Starting Version 6 2 | BEGIN; 3 | 4 | --704 recovery password. 5 | ALTER TABLE member ADD recover_password TEXT; 6 | ALTER TABLE member ADD recover_password_set_at TIMESTAMP; 7 | 8 | --706 Notices display 9 | CREATE TABLE notices ( 10 | ID SERIAL UNIQUE NOT NULL, 11 | CREATED_AT TIMESTAMP NOT NULL DEFAULT NOW()::TIMESTAMP, 12 | EXPIRE_AT TIMESTAMP, -- can be null, if we don't know when to expire. 13 | MESSAGE TEXT NOT NULL 14 | ); 15 | ALTER TABLE member ADD last_notice INT REFERENCES notices(id); 16 | 17 | --707 Fix logging references 18 | 19 | ALTER TABLE audit_history ALTER member DROP NOT NULL; 20 | ALTER TABLE audit_history 21 | DROP CONSTRAINT audit_history_member_fkey, 22 | ADD CONSTRAINT audit_history_member_fkey 23 | FOREIGN KEY (member) 24 | REFERENCES member(ident) 25 | ON UPDATE CASCADE 26 | ON DELETE SET NULL; 27 | 28 | -- Set the db version properly. 29 | --Update Version. 30 | UPDATE schema_metadata 31 | SET value = 7 32 | WHERE value = 6 33 | AND key = 'portal_schema_version'; 34 | COMMIT; 35 | -------------------------------------------------------------------------------- /library/db_migrations/Makefile: -------------------------------------------------------------------------------- 1 | # This file is part of the Ops-T Portal. 2 | # 3 | # Copyright 2014 Operations Security Administration, Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | SHELL_SCRIPTS= update_db db_needs_update 19 | 20 | MADE=${SHELL_SCRIPTS} 21 | 22 | all: ${SHELL_SCRIPTS} 23 | 24 | clean:; rm -f ${MADE} 25 | .SUFFIXES: .sh 26 | 27 | .sh: 28 | @echo $< =\> $@; \ 29 | rm -f $@ && \ 30 | perl ../../mycat.pl ../../siteconfig < $< > $@ && \ 31 | chmod +x,-w $@ 32 | 33 | ${SHELL_SCRIPTS}: ../../siteconfig Makefile 34 | -------------------------------------------------------------------------------- /webroot/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #191950; color: #F8F8FF; 3 | font-family: arial, helvetica, sans-serif; 4 | } 5 | 6 | a { 7 | color: yellow; 8 | } 9 | 10 | .note { 11 | color: #F8A000; 12 | font-weight: bold; 13 | font-size: larger; 14 | } 15 | 16 | p.mh { 17 | background-color: #F8F8FF; color: #191950; 18 | text-align: center; 19 | font-variant: small-caps; 20 | font-size: 30px; 21 | } 22 | 23 | tr.b { 24 | background-color: #404060; 25 | } 26 | 27 | /*Start CSS to support HTML::Barcode::QRcode */ 28 | table.hbc { 29 | background-color: #fff; 30 | color: #000; 31 | border-width:0; 32 | border-spacing:0; 33 | } 34 | 35 | table.hbc tr, table.hbc td{ 36 | border:0; 37 | margin:0; 38 | padding:0; 39 | } 40 | 41 | table.hbc td{ 42 | text-align:left; 43 | } 44 | 45 | table.hbc td.hbc_on,table.hbc td.hbc_off { 46 | width:3px; 47 | height:3px; 48 | } 49 | 50 | table.hbc td.hbc_on { 51 | background-color:#000; 52 | color:inherit; 53 | } 54 | 55 | table.hbc td.hbc_off { 56 | background-color:#fff; 57 | color:inherit; 58 | } 59 | /*End CSS to support HTML::Barcode::QRcode */ 60 | -------------------------------------------------------------------------------- /webroot/site/Makefile: -------------------------------------------------------------------------------- 1 | FILES= chpw.html favicon.ico mailinglist.html index.html default_face.png \ 2 | index_private.mas nominate.html edit_contact.html robots.txt \ 3 | edit_second_factor.html leftside_private.mas emit_emerg.html \ 4 | edit_email.html leftside_public.mas show_member.html \ 5 | list_airports.html sitehandler.mas emit_mlkey.html \ 6 | list_languages.html style.css emit_pgp.html list_members.html \ 7 | vouch_cp.html emit_vouch.html 8 | 9 | MADE= decor.mas index_public.mas select_tg.html 10 | 11 | all: ${MADE} 12 | 13 | clean:; rm -f ${MADE} 14 | 15 | install: all 16 | @( echo mkdir -p !sitedir!; \ 17 | echo install -m 644 -o !wwwuid! -g !wwwgid! \ 18 | ${MADE} ${FILES} !sitedir!/; \ 19 | ) | \ 20 | perl ../../mycat.pl ../../siteconfig | \ 21 | sh -x 22 | 23 | decor.mas: decor.mas.template ../../siteconfig 24 | perl ../../mycat.pl ../../siteconfig < decor.mas.template > $@ 25 | 26 | index_public.mas: index_public.mas.template ../../siteconfig 27 | perl ../../mycat.pl ../../siteconfig < index_public.mas.template > $@ 28 | 29 | select_tg.html: select_tg.html.template ../../siteconfig 30 | perl ../../mycat.pl ../../siteconfig < select_tg.html.template > $@ 31 | -------------------------------------------------------------------------------- /webroot/site/emit_mlkey.html: -------------------------------------------------------------------------------- 1 | <%args> 2 | $lhs => '' 3 | 4 | 5 | <%perl> 6 | $m->clear_buffer(); 7 | $r->content_type('application/pgp-keys'); 8 | $r->header_out('Cache-Control' => 'no-cache'); 9 | $r->err_headers_out->add( 10 | 'Content-Disposition' => 'inline; filename="pgpkeys.asc"' 11 | ); 12 | $m->current_comp->call_method('do', lhs => $lhs); 13 | $m->abort(); 14 | 15 | 16 | <%method do> 17 | <%args> 18 | $lhs => '' 19 | 20 | <%perl> 21 | my $q = $Mech->{cgi}; 22 | my $dbh = $Site->{dbh}; 23 | my $tg = $Site->{tg}; 24 | my $db_tg = $tg->{db_ident}; 25 | my $db_lhs = $dbh->quote($lhs); 26 | my $db_and = (length $lhs) 27 | ? "AND ROW(ml.lhs, ml.trustgroup) = ROW($db_lhs, $db_tg)" 28 | : ''; 29 | #print STDERR "db_and='$db_and'\n"; 30 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 31 | SELECT ml.lhs, ml.trustgroup, ml.pubkey 32 | FROM mailinglist ml 33 | WHERE ml.trustgroup IN ( 34 | SELECT trustgroup 35 | FROM member_trustgroup 36 | WHERE member = $Site->{db_member} 37 | ) 38 | $db_and 39 | }, {Slice => {}})}) { 40 | print $row->{pubkey}; 41 | } 42 | 43 | 44 | 45 | <%shared> 46 | 47 | 48 | <%init> 49 | 50 | -------------------------------------------------------------------------------- /library/db_migrations/README_OPS_T_DB.txt: -------------------------------------------------------------------------------- 1 | Ops-T DB versioning. 2 | 3 | Each DB change will be codified in a migration. Migrations with assigned 4 | versions will be named DB_$version.psql. Where the version is the current 5 | version of the database at the time that the given file should be applied. 6 | 7 | For example if the current version is 3 and you would like to make a change to 8 | the schema, set the name of the file to DB_3.psql. Your update will set the 9 | database version to 4 as it's last action. 10 | 11 | The contents of the migration are in the form of a PSQL file. All activity 12 | should be within a transaction (BEGIN,COMMIT). This is so that if the update 13 | fails in any way, no update will occur. 14 | 15 | BEGIN; 16 | 17 | CREATE TABLE schema_version ( 18 | current_version INT NOT NULL DEFAULT 0 19 | ); 20 | 21 | GRANT ALL ON schema_version TO sysadmin; 22 | 23 | UDPATE schema_version set current_version = 1; 24 | COMMIT; 25 | 26 | If you need to include a fully manual step, skip a version number. DB starts 27 | at 1, first migration updates it to 2, next migration requires version 3 to run. 28 | The manual step should include the transition from version 2 to 3. 29 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | = Installation = 2 | 3 | == Debian == 4 | 5 | On Debian (7.0+) these are the packages you have to have installed for Ops-T: 6 | 7 | From Debian: 8 | ``` 9 | apt-get install perl perl-base libcgi-session-perl perl-modules \ 10 | libdatetime-format-mail-perl libdatetime-format-pg-perl \ 11 | libdbi-perl libgnupg-interface-perl libmail-sendeasy-perl \ 12 | libmime-tools-perl libhtml-mason-perl libdbd-pg-perl 13 | ``` 14 | 15 | From CPAN you will additionally need: 16 | ``` 17 | cpan install HTML::Barcode::QRCode 18 | ``` 19 | 20 | For compilation of the mail handler one also requires installation of 'make' and 'gcc'. 21 | Outside of compilation though these package are not needed on your production hosts. 22 | 23 | === Database === 24 | postgres can be installed on the same or another host. 25 | 26 | === Apache === 27 | For the portal webinterface, Apache2 is required to serve the website: 28 | ``` 29 | apt-get install apache2-mpm-prefork package libapache2-mod-perl2 libapache2-reload-perl 30 | a2enmod perl 31 | a2enmod ssl 32 | 33 | a2ensite http-real 34 | service apache2 reload 35 | ``` 36 | 37 | Copy the config file generated from /home/user/portal/library/http-real.inc to /etc/apache2/sites-available/http-real.conf 38 | 39 | -------------------------------------------------------------------------------- /library/cronrun-day.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | cd $(dirname $0) || exit 1 21 | 22 | . ./funcs.sh 23 | 24 | # Lock this crontab 25 | cron_lock $(basename $0) 26 | 27 | # Clean up pgp data files (for keys of users, etc). 28 | ./fsck-pgpkeys 29 | 30 | # Run notification bits for each trustgroup. 31 | for trustgroup in $($portal_query -A -t -c 'SELECT ident FROM trustgroup ORDER BY ident') 32 | do 33 | ./notify-unvetted $trustgroup 34 | ./report-unvetted $trustgroup 35 | done 36 | 37 | # Unlock this crontab 38 | cron_unlock 39 | 40 | exit 41 | -------------------------------------------------------------------------------- /library/set-no-mail.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | cd $(dirname $0) || exit 1 21 | 22 | . ./funcs.sh 23 | 24 | # 25 | # Disable mail delivery for an email destination. 26 | # 27 | # Args: 28 | # email, an email that is in need of disabling. 29 | if [ "X$1" = "X" ] ; then 30 | echo "$0 " 31 | exit 255 32 | fi 33 | 34 | portal_query << EOF 35 | BEGIN; 36 | 37 | UPDATE member m 38 | SET no_email = true 39 | FROM member_email me 40 | WHERE m.ident = me.member 41 | AND me.email = '$1'; 42 | 43 | COMMIT; 44 | EOF 45 | 46 | -------------------------------------------------------------------------------- /webroot/site/emit_vouch.html: -------------------------------------------------------------------------------- 1 | <%args> 2 | $member => '' 3 | 4 | 5 | <%perl> 6 | $m->clear_buffer(); 7 | $r->content_type('text/plain'); 8 | $r->header_out('Cache-Control' => 'no-cache'); 9 | my $filename = "${common::dbname}.$tg->{ident}.vouch.txt"; 10 | $r->err_headers_out->add( 11 | 'Content-Disposition' => qq{inline; filename="$filename"} 12 | ); 13 | 14 | printf "%s,%s,%s\n", 'Vouchor', 'Vouchee', 'Entered'; 15 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 16 | SELECT mv.vouchor, mv.vouchee, DATE(mv.entered) AS entered 17 | FROM member_vouch mv 18 | JOIN member m1 ON (mv.vouchor = m1.ident) 19 | JOIN member m2 ON (mv.vouchee = m2.ident) 20 | JOIN member_trustgroup mt1 ON 21 | ROW(mv.vouchor, mv.trustgroup) = 22 | ROW(mt1.member, mt1.trustgroup) 23 | JOIN member_trustgroup mt2 ON 24 | ROW(mv.vouchee, mv.trustgroup) = 25 | ROW(mt2.member, mt2.trustgroup) 26 | JOIN member_state ms1 ON (mt1.state = ms1.ident) 27 | JOIN member_state ms2 ON (mt2.state = ms2.ident) 28 | WHERE mv.trustgroup = $tg->{db_ident} 29 | AND ms1.can_login 30 | AND ms2.can_login 31 | AND mv.positive 32 | }, {Slice => {}})}) { 33 | printf "%s,%s,%s\n", $row->{vouchor}, $row->{vouchee}, $row->{entered}; 34 | } 35 | $m->abort(); 36 | 37 | 38 | <%shared> 39 | my $q = undef; 40 | my $dbh = undef; 41 | my $tg = undef; 42 | 43 | 44 | <%init> 45 | $q = $Mech->{cgi}; 46 | $dbh = $Site->{dbh}; 47 | $tg = $Site->{tg}; 48 | 49 | -------------------------------------------------------------------------------- /library/db_migrations/DB_7.psql: -------------------------------------------------------------------------------- 1 | -- Starting Version 7 2 | BEGIN; 3 | --GHI_6 Omnibus DB update #7 4 | 5 | --GHI_4 LockDown Member Name. 6 | ALTER TABLE member ADD ident_changed BOOLEAN NOT NULL DEFAULT false; 7 | --GHI_5 Sync passowrds and ssh keys for people accounts to the DB. 8 | ALTER TABLE member ADD ssh_key TEXT; 9 | 10 | --GHI_8 Replace UUID in second_factors table with SEQUENCE. 11 | ALTER TABLE second_factors RENAME TO second_factors_old; 12 | CREATE TABLE second_factors ( 13 | id SERIAL, 14 | member TEXT NOT NULL REFERENCES member(ident) 15 | ON UPDATE CASCADE 16 | ON DELETE CASCADE, 17 | type TEXT NOT NULL REFERENCES second_factor_types(type) 18 | ON UPDATE CASCADE 19 | ON DELETE CASCADE, 20 | entered TIMESTAMP NOT NULL DEFAULT NOW()::TIMESTAMP, 21 | active BOOLEAN NOT NULL DEFAULT false, 22 | counter INTEGER DEFAULT 0, 23 | key TEXT, 24 | descr TEXT 25 | ); 26 | GRANT ALL ON TABLE second_factors TO www,sysadmin, "www-data"; 27 | 28 | INSERT INTO second_factors (member,type,entered,active,counter,key,descr) 29 | SELECT member,type,entered,active,counter,key,descr FROM second_factors_old; 30 | 31 | DROP TABLE second_factors_old; 32 | 33 | -- Set the db version properly. 34 | --Update Version. 35 | UPDATE schema_metadata 36 | SET value = 8 37 | WHERE value = 7 38 | AND key = 'portal_schema_version'; 39 | COMMIT; 40 | -------------------------------------------------------------------------------- /mycat.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | use warnings; 21 | use strict; 22 | 23 | my $siteconfig = {}; 24 | my $file = undef; 25 | my $filename = shift @ARGV; 26 | open($file, "<$filename") || die "$filename: $!"; 27 | while (<$file>) { 28 | chomp; 29 | s/^\s+//go; # remove starting blanks 30 | s/\s+$//go; # remove ending blanks 31 | next if /^#/ || !length; 32 | die "siteconfig($_) syntax error" unless /=/o; 33 | $siteconfig->{$`} = eval $'; 34 | } 35 | close($file); 36 | 37 | while () { 38 | my $n = 100; 39 | while (--$n > 0 && /\!(\w+)\!/) { 40 | if (!defined($siteconfig->{$1})) { 41 | die "Undefined siteconfig variable: $1"; 42 | } 43 | $_ = $`.$siteconfig->{$1}.$'; 44 | } 45 | die "expansion loop" if $n == 0; 46 | print; 47 | } 48 | 49 | exit 0; 50 | -------------------------------------------------------------------------------- /webroot/site/leftside_private.mas: -------------------------------------------------------------------------------- 1 |

2 | % if (! $Site->{change_pw}) { 3 | % if (!defined $Site->{tg}) { 4 | Trustgroup not set 5 | (Select TG) 6 | % } else { 7 | <%$Site->{tg}->{descr}%> 8 | % } 9 | % if ($Mech->{sess}->param('ntg') > 1) { 10 | (change) 11 | % } 12 | <% $Site->{admin} ? 'Ω' : '' %>

13 | % if ($Site->{can_see}) { 14 |

Members

15 |

Airports

16 |

Languages

17 | % if ($Site->{admin} || $Site->{tg}->{nom_enabled}) { 18 |

Nominate

19 | % } 20 | % if ($Site->{admin} || !$Site->{tg}->{vouch_adminonly}) { 21 |

Vouch Ctl Panel

22 | % } 23 | % my $tgname = $Site->{tg}->{ident}; 24 | % my $wiki_url = sprintf $common::wiki, $tgname; 25 |

Mailing Lists

26 |

PGP keys

27 |

Emergency contacts

28 |

List Vouches (CSV)

29 | % if ($Site->{tg}->{has_wiki}) { 30 |

Wiki

31 | % } 32 |

Edit profile

33 |

Show profile

34 | % } 35 | % } 36 |

Change password

37 | -------------------------------------------------------------------------------- /library/merge-member.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | cd $(dirname $0) || exit 1 21 | 22 | . ./funcs.sh 23 | 24 | tg=$1 25 | old=$2 26 | new=$3 27 | 28 | cat <<:EOF: 29 | BEGIN; 30 | UPDATE member_email SET member='$new' 31 | WHERE member = '$old'; 32 | UPDATE member_trustgroup SET member='$new' 33 | WHERE ROW(trustgroup, member) = ROW('$tg', '$old'); 34 | UPDATE member_vouch SET vouchor='$new' 35 | WHERE ROW(trustgroup, vouchor) = ROW('$tg', '$old'); 36 | UPDATE member_vouch SET vouchee='$new' 37 | WHERE ROW(trustgroup, vouchee) = ROW('$tg', '$old'); 38 | UPDATE member_mailinglist SET member='$new' 39 | WHERE ROW(trustgroup, member) = ROW('$tg', '$old'); 40 | UPDATE audit_history SET member='$new' 41 | WHERE member = '$old'; 42 | DELETE FROM member 43 | WHERE ident = '$old'; 44 | COMMIT; 45 | :EOF: 46 | 47 | exit 48 | -------------------------------------------------------------------------------- /library/new-member.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | use strict; 21 | use warnings; 22 | use FindBin; 23 | use lib $FindBin::Bin; 24 | use common; 25 | 26 | $| = 1; 27 | my $dbh = &common::get_dbh(); 28 | 29 | my ($tgname, $vouchor, $email, $descr, $bio, $affiliation, $attestation) = 30 | @ARGV; 31 | die "usage: $0 tgname vouchor email descr bio affiliation attestation" unless 32 | defined $tgname && length $tgname && 33 | defined $vouchor && length $vouchor && 34 | defined $email && length $email && 35 | defined $descr && length $bio && 36 | defined $bio && length $bio && 37 | defined $affiliation && length $affiliation && 38 | defined $attestation && length $attestation; 39 | 40 | my $tg = &common::get_tg($dbh, $tgname); 41 | 42 | print &common::new_member($dbh, $tg, $vouchor, $email, $descr, $bio, 43 | $affiliation, $attestation), 44 | "\n"; 45 | 46 | exit 0; 47 | -------------------------------------------------------------------------------- /library/fix-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | cd $(dirname $0) || exit 1 21 | 22 | . ./funcs.sh 23 | 24 | # 25 | # Rsync'ing over a git clone is bobo... fix the permissions and add missing 26 | # directories/links. 27 | 28 | mkdir -p !portal!/webvar 29 | chown !wwwuid!:!wwwgid! !portal!/webvar 30 | for sub in badpgp ml_keys pgpkeys tmp; do 31 | mkdir -p !portal!/webvar/${sub} 32 | chown -R !wwwuid!:!wwwgid! !portal!/webvar/${sub} 33 | chmod 755 !portal!/webvar/${sub} 34 | done 35 | 36 | chmod -R 770 !portal!/webvar/ml_keys 37 | chmod -R 770 !portal!/webvar/pgpkeys 38 | chmod -R 775 !portal!/webvar 39 | chmod -R 775 !portal!/webroot 40 | 41 | for dir in logs webroot; do 42 | chown -R root:0 !portal!/${dir} 43 | done 44 | 45 | for masondir in \ 46 | /var/local/mason/ops-trust/cache \ 47 | /var/local/mason/ops-trust/obj; 48 | do 49 | mkdir -p $masondir 50 | chmod -R 770 $masondir 51 | chown -R !wwwuid!:!wwwgid! $masondir 52 | done 53 | 54 | exit 55 | -------------------------------------------------------------------------------- /library/gpgtest.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | use strict; 21 | use warnings; 22 | use FindBin; 23 | use lib $FindBin::Bin; 24 | use common; 25 | 26 | my $dbh = &common::get_dbh(); 27 | 28 | my ($uuid, $email) = @ARGV; 29 | &gpgtest($uuid, $email); 30 | exit 0; 31 | 32 | sub gpgtest { 33 | my ($uuid, $email) = @_; 34 | 35 | print "gpgtest($uuid):\n"; 36 | 37 | # don't do this next line until we know how to chgrp www-data non-root 38 | # &common::gpg_key_present($dbh, $uuid); 39 | 40 | print "gpg_key_path: ", &common::gpg_key_path($uuid), "\n"; 41 | print "gpgcmd = '", &common::gpgcmd_user($uuid), "'\n"; 42 | print "myfiles = (", join(' ', &common::gpgcmd_myfiles($uuid)), ")\n"; 43 | 44 | my $x = &common::gpgcmd_present($uuid); 45 | print "present: $x\n"; 46 | return unless $x; 47 | 48 | print "mykeys = (", join(' ', &common::gpgcmd_mykeys($uuid, $email)), 49 | ")\n"; 50 | print "allkeys:\n", &common::gpgcmd_allkeys($uuid), "---\n"; 51 | } 52 | -------------------------------------------------------------------------------- /siteconfig.template: -------------------------------------------------------------------------------- 1 | # 2 | # The name of the group that owns this installation. 3 | brand='Ops-T' 4 | # 5 | # A title which will appear on the portal's pages. 6 | title='Operations Security Trust' 7 | # 8 | # Copyright by... 9 | coname='OpSecAdmin' 10 | # 11 | # The portal host name. 12 | portalname='portal' 13 | # 14 | # The auth URL. 15 | authurl='https://openid.ops-trust.net/' 16 | # 17 | # The email domain to be used. 18 | domain='ops-trust.net' 19 | # 20 | # URL to the wiki for a trustgroup. %s is replaced by the TrustGroup name. 21 | wiki='https://wiki.ops-trust.net/%s/bin/view' 22 | # 23 | # Regular expressions to be used in locating the support website or wiki. 24 | supportre='https://ops.!domain!/rt' 25 | wikire='https://wiki.!domain!/([a-z]+)/' 26 | 27 | # E-mail for support requests 28 | supportemail='technical\@support.!domain!' 29 | # 30 | # The location of the postgres DB, either a local instance or a hostname. 31 | pghost='/var/run/postgresql' 32 | pgport=5433 33 | pguser='postgres' 34 | # 35 | # The database name to be used. 36 | pgname='ops-trust' 37 | # 38 | # Paths to the running code once installed. 39 | portal='/proj/ops-trust' 40 | library='/proj/ops-trust/library' 41 | webroot='/proj/ops-trust/webroot' 42 | sitedir='/proj/ops-trust/webroot/site' 43 | mechdir='/proj/ops-trust/webroot/mech' 44 | webvar='/proj/ops-trust/webvar' 45 | # 46 | # User id (integer) and group id (integer) to be used for file ownership 47 | # and apache process configuration. 48 | wwwuid=33 49 | wwwgid=33 50 | # 51 | # Paths to SSL Key, Cert, Chainfile. To be used in the Apache configuration. 52 | sslcertfile='/etc/ssl/certs/yourcert.pem' 53 | sslkeyfile='/etc/ssl/private/yourkeyhere' 54 | sslchainfile='/etc/ssl/certs/chainlovin' 55 | -------------------------------------------------------------------------------- /webroot/site/emit_emerg.html: -------------------------------------------------------------------------------- 1 | <%args> 2 | $member => '' 3 | 4 | 5 | <%perl> 6 | $m->clear_buffer(); 7 | $r->content_type('text/plain'); 8 | $r->header_out('Cache-Control' => 'no-cache'); 9 | my $filename = "${common::dbname}.$tg->{ident}.membership.txt"; 10 | $r->err_headers_out->add( 11 | 'Content-Disposition' => qq{inline; filename="$filename"} 12 | ); 13 | 14 | my $members = { }; 15 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 16 | SELECT m.ident, m.descr, m.affiliation, 17 | m.tel_info, m.sms_info, mt.state 18 | FROM member m 19 | JOIN member_trustgroup mt ON (ROW(mt.member, mt.trustgroup) = 20 | ROW(m.ident, $tg->{db_ident})) 21 | JOIN member_state ms ON (ms.ident = mt.state) 22 | WHERE mt.trustgroup = $tg->{db_ident} 23 | AND ms.can_login 24 | }, {Slice => {}})}) { 25 | $_ = $row->{descr}; s/^\s+//o; s/\s+$//o; 26 | my $sortdescr = /\s+([^\s]+)$/o ? "$1$`" : $_; 27 | $sortdescr =~ tr/A-Z/a-z/; 28 | $members->{$row->{ident}} = { 29 | affiliation => $row->{affiliation}, 30 | tel_info => $row->{tel_info}, 31 | sms_info => $row->{sms_info}, 32 | state => $row->{state}, 33 | sortdescr => $sortdescr 34 | }; 35 | } 36 | 37 | foreach my $ident (sort { $members->{$a}->{sortdescr} cmp 38 | $members->{$b}->{sortdescr} } 39 | keys %$members) 40 | { 41 | my $member = $members->{$ident}; 42 | printf "%-30s : tel %-15s : sms %-15s\n", 43 | sprintf("%s (%s)", $ident, $member->{affiliation}), 44 | $member->{tel_info}, $member->{sms_info}; 45 | } 46 | $m->abort(); 47 | 48 | 49 | <%shared> 50 | my $q = undef; 51 | my $dbh = undef; 52 | my $tg = undef; 53 | 54 | 55 | <%init> 56 | $q = $Mech->{cgi}; 57 | $dbh = $Site->{dbh}; 58 | $tg = $Site->{tg}; 59 | 60 | -------------------------------------------------------------------------------- /webroot/autohandler.template: -------------------------------------------------------------------------------- 1 | <%perl> 2 | $m->comp('/mech/header.mas'); 3 | if (!$m->request_comp->method_exists('is_public') && 4 | !defined $Mech->{sess}->param('~logged-in')) 5 | { 6 | print $m->cgi_object()->h3('You are not logged in.'); 7 | $m->redirect('!authurl!'); 8 | } else { 9 | my $ok_p = $m->scomp('/site/sitehandler.mas:request_ok', 10 | request => $m->request_comp); 11 | if ($ok_p !~ /^\s*OK\s*/) { 12 | print $m->cgi_object()->h3($ok_p); 13 | } else { 14 | $m->call_next; 15 | } 16 | } 17 | $m->comp('/mech/footer.mas'); 18 | 19 | 20 | <%once> 21 | use CGI; 22 | use CGI::Cookie; 23 | use CGI::Session; 24 | 25 | 26 | <%init> 27 | $r->header_out('Cache-Control' => 'no-cache'); 28 | $r->content_type(q{text/html; charset=utf-8}); 29 | #my $sh = $r->server->server_hostname || 'www'; 30 | my $sh = 'www'; 31 | $m->comp('/site/sitehandler.mas:init'); 32 | $Mech = { 33 | cgi => $m->cgi_object(), 34 | dsn => 'driver:PostgreSQL', 35 | args => {Handle=>$Site->{dbh}, TableName=>'web_sessions'} 36 | }; 37 | $Mech->{sess} = CGI::Session->load($Mech->{dsn}, $Mech->{cgi}, $Mech->{args}); 38 | if (defined $Mech->{sess}->param('test') && !defined $Mech->{sess}->param('member')){ 39 | $Mech->{sess}->param('member',$Mech->{sess}->param('test')); 40 | $Mech->{sess}->flush(); 41 | } 42 | $m->comp('/site/sitehandler.mas:session_setup'); 43 | if ($Mech->{sess}->is_expired() && 44 | !$m->request_comp->method_exists('is_logout')) 45 | { 46 | $m->comp('/site/sitehandler.mas:logout'); 47 | $m->redirect('!authurl!'); 48 | } 49 | $Mech->{username} = $Mech->{sess}->param('member'); 50 | 51 | 52 | <%cleanup> 53 | $Mech->{sess}->flush() unless $Mech->{sess}->is_empty(); 54 | $m->comp('/site/sitehandler.mas:fini'); 55 | 56 | -------------------------------------------------------------------------------- /library/db_migrations/db_needs_update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This file is part of the Ops-T Portal. 3 | # 4 | # Copyright 2014 Operations Security Administration, Inc. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | 20 | # This script checks the current DB rev and reports if updates are ready 21 | # to be applied. 22 | 23 | # exit value will be: 24 | # - 0 if we can find no updates ready for automatic application. 25 | # - 1 if we have run an update that we could run. 26 | 27 | if [ ! -d "./db_migrations" ]; then 28 | echo " - db_migrations directory not found in local dir." 29 | echo " This script must be run from the source pool library/ dir." 30 | echo " Not !library! " 31 | exit 1 32 | fi 33 | 34 | export CURRENT_VERSION=`psql -h !pghost! -d !pgname! -U !pguser! \ 35 | -t --no-align \ 36 | -c "SELECT value FROM schema_metadata WHERE key = 'portal_schema_version';"` 37 | echo "Current DB Version: ${CURRENT_VERSION}, looking for updates." 38 | 39 | export FILENAME="db_migrations/DB_${CURRENT_VERSION}.psql" 40 | if [ -f $FILENAME ]; then 41 | echo "Updates ready to apply" 42 | exit 1 43 | 44 | else 45 | echo "No Update to apply" 46 | exit 0 47 | fi 48 | 49 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # This file is part of the Ops-T Portal. 2 | # 3 | # Copyright 2014 Operations Security Administration, Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | SUBDIRS= library webroot 18 | OBJDIRS= logs webvar archival 19 | 20 | all clean: siteconfig 21 | @set -e; for subdir in ${SUBDIRS}; do \ 22 | ( set -x && cd $$subdir && ${MAKE} ${MARGS} $@ ); \ 23 | done 24 | 25 | makedb: siteconfig 26 | @echo createdb -h !pghost! -p !pgport! !pgname! | \ 27 | perl mycat.pl siteconfig | \ 28 | sh -x 29 | 30 | @echo psql -h !pghost! -p !pgport! -d !pgname! \ 31 | \< library/schema.psql |\ 32 | perl mycat.pl siteconfig | \ 33 | sh -x 34 | 35 | install: all 36 | @set -e; for subdir in ${SUBDIRS}; do \ 37 | echo mkdir -p !portal!/$$subdir | \ 38 | perl mycat.pl siteconfig | \ 39 | sh -x; \ 40 | set -x && ( cd $$subdir && ${MAKE} ${MARGS} install ); \ 41 | done 42 | @set -e; for objdir in ${OBJDIRS}; do \ 43 | echo mkdir -p !portal!/$$objdir | \ 44 | perl mycat.pl siteconfig | \ 45 | sh -x; \ 46 | done 47 | @echo !library!/fix-install | \ 48 | perl mycat.pl siteconfig | \ 49 | sh -x 50 | 51 | siteconfig: 52 | @echo copy siteconfig from siteconfig.template and localize it 53 | @exit 1 54 | -------------------------------------------------------------------------------- /library/extract-sysadmins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | cd $(dirname $0) || exit 1 21 | 22 | . ./funcs.sh 23 | 24 | # 25 | # Get all sysadmins into a table, created on the fly. 26 | # pgdump that into inserts for the destination/new database. 27 | # 28 | 29 | wt="extract_sysadmin_$$" 30 | et="extract_member_email_$$" 31 | 32 | # Create a working table for the members. 33 | portal_query << EOF 34 | SELECT * 35 | INTO ${wt} 36 | FROM member 37 | WHERE sysadmin; 38 | EOF 39 | 40 | # Create a working table for the member_emails 41 | portal_query << EOF 42 | SELECT me.member, me.email, me.verified 43 | INTO ${et} 44 | FROM member_email AS me, member AS m 45 | WHERE m.sysadmin 46 | AND me.member = m.ident; 47 | EOF 48 | 49 | # dump the member working table into stdout 50 | portal_dump -t ${wt} !pgname! | sed "s/${wt}/member/" 51 | 52 | # dump the email working table into stdout 53 | portal_dump -t ${et} !pgname! | sed "s/${et}/member_email/" 54 | 55 | # Remove the temporary table from the original location. 56 | portal_query << EOF 57 | DROP TABLE ${wt}; 58 | DROP TABLE ${et}; 59 | EOF 60 | 61 | exit 62 | -------------------------------------------------------------------------------- /library/db_migrations/DB_3.psql: -------------------------------------------------------------------------------- 1 | -- Starting Version 3 2 | -- Ticket 455 Correct some schema nits. 3 | BEGIN; 4 | 5 | --Change the data type to TEXT CHECK(LENGTH(iso_639_1) for iso_639_1 6 | 7 | ALTER TABLE member_language_skill DROP CONSTRAINT member_language_skill_language_fkey; 8 | ALTER TABLE languages RENAME iso_639_1 TO iso_639_1_tmp; 9 | ALTER TABLE languages ADD iso_639_1 TEXT CHECK(LENGTH(iso_639_1) = 2) UNIQUE; 10 | UPDATE languages SET iso_639_1 = iso_639_1_tmp; 11 | ALTER TABLE languages ALTER COLUMN iso_639_1 SET NOT NULL; 12 | ALTER TABLE languages DROP COLUMN iso_639_1_tmp; 13 | ALTER TABLE member_language_skill ADD CONSTRAINT member_language_skill_language_fkey 14 | FOREIGN KEY (language) REFERENCES languages(iso_639_1) 15 | ON UPDATE CASCADE 16 | ON DELETE CASCADE; 17 | 18 | -- It's not referenced anywhere but re-instantiate anyway incase that changes the name. 19 | 20 | ALTER TABLE member_language_skill RENAME TO member_language_skill_old; 21 | 22 | CREATE TABLE member_language_skill ( 23 | member TEXT NOT NULL REFERENCES member(ident) 24 | ON UPDATE CASCADE 25 | ON DELETE CASCADE, 26 | language TEXT NOT NULL REFERENCES languages(iso_639_1) 27 | ON UPDATE CASCADE 28 | ON DELETE CASCADE, 29 | skill TEXT NOT NULL REFERENCES language_skill(skill) 30 | ON UPDATE CASCADE 31 | ON DELETE CASCADE, 32 | entered TIMESTAMP NOT NULL DEFAULT NOW()::TIMESTAMP, 33 | UNIQUE(member,language) 34 | ); 35 | 36 | GRANT ALL ON TABLE member_language_skill TO sysadmin, www, "www-data"; 37 | 38 | INSERT INTO member_language_skill (member,language,skill,entered) 39 | SELECT member,language,skill,entered FROM member_language_skill_old; 40 | 41 | DROP TABLE member_language_skill_old; 42 | 43 | --Update Version. 44 | UPDATE schema_metadata 45 | SET value = 4 46 | WHERE value = 3 47 | AND key = 'portal_schema_version'; 48 | 49 | COMMIT; 50 | 51 | -------------------------------------------------------------------------------- /library/db_migrations/DB_2.psql: -------------------------------------------------------------------------------- 1 | -- Starting Version 2 2 | -- Ticket 378 Move PGP keys and faces into SQL 3 | BEGIN; 4 | 5 | --Images should be base64. 6 | ALTER TABLE member ADD image TEXT; 7 | 8 | --Store the PGP key in ASCII Armor 9 | ALTER TABLE member_email ADD keyring TEXT; 10 | ALTER TABLE member_email ADD keyring_update_at TIMESTAMP; 11 | ALTER TABLE mailinglist ADD pubkey TEXT; 12 | ALTER TABLE mailinglist ADD key_update_at TIMESTAMP; 13 | ALTER TABLE mailinglist ADD seckey TEXT; 14 | 15 | CREATE TABLE message_types ( 16 | ident TEXT PRIMARY KEY, 17 | descr TEXT 18 | ); 19 | 20 | GRANT ALL ON message_types TO www,sysadmin; 21 | 22 | INSERT INTO message_types (ident,descr) 23 | VALUES ('web_global_hello','Global public about Ops-t page'); 24 | INSERT INTO message_types (ident,descr) 25 | VALUES ('web_tg_hello','TG unique welcome message, after login'); 26 | INSERT INTO message_types (ident,descr) 27 | VALUES ('email_statechange_vetted','Message sent when state changed to vetted'); 28 | 29 | --Store the message templates. 30 | CREATE TABLE message_catalog ( 31 | trustgroup TEXT REFERENCES trustgroup(ident) 32 | ON UPDATE CASCADE 33 | ON DELETE CASCADE, 34 | message_type TEXT NOT NULL REFERENCES message_types(ident), 35 | message_template TEXT 36 | ); 37 | 38 | GRANT ALL ON message_catalog TO www,sysadmin; 39 | 40 | --Old table no-longer used. 41 | DROP TABLE freemail; 42 | 43 | --Prevent multiple rows in schema_version 44 | DROP TABLE schema_version; 45 | 46 | CREATE TABLE schema_metadata ( 47 | key TEXT PRIMARY KEY, 48 | value INT NOT NULL DEFAULT 0 49 | ); 50 | 51 | GRANT ALL ON schema_metadata TO sysadmin; 52 | 53 | INSERT INTO schema_metadata (key,value) 54 | VALUES ('portal_schema_version',1); 55 | 56 | --Update Version. 57 | UPDATE schema_metadata 58 | SET value = 3 59 | WHERE value = 2 60 | AND key = 'portal_schema_version'; 61 | 62 | COMMIT; 63 | -------------------------------------------------------------------------------- /library/mh-wrapper.c: -------------------------------------------------------------------------------- 1 | /* 2 | # This file is part of the Ops-T Portal. 3 | # 4 | # Copyright 2014 Operations Security Administration, Inc. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | */ 19 | 20 | #ifdef linux 21 | #define _GNU_SOURCE 22 | #endif 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #define STR_HELPER(x) #x 29 | #define STR(x) STR_HELPER(x) 30 | 31 | int 32 | main(int argc, char *argv[], char *envp[]) { 33 | const char wrapped[] = STR(WRAPPED); 34 | #ifdef linux 35 | uid_t new_gid; 36 | uid_t new_uid, ruid, euid, suid; 37 | 38 | new_gid = getegid(); 39 | new_uid = geteuid(); 40 | 41 | if (setresgid(new_gid, new_gid, new_gid) < 0) { 42 | perror("setresgid()"); 43 | return (1); 44 | } 45 | 46 | if (setresuid(new_uid, new_uid, new_uid) < 0) { 47 | perror("setresuid()"); 48 | return (1); 49 | } 50 | 51 | if (getresuid(&ruid, &euid, &suid) < 0) { 52 | perror("getresuid()"); 53 | return (1); 54 | } 55 | #else 56 | if (setgid(getegid()) != 0) { 57 | perror("setgid()"); 58 | return (1); 59 | } 60 | 61 | if (setuid(geteuid()) != 0) { 62 | perror("setuid()"); 63 | return (1); 64 | } 65 | #endif 66 | 67 | execve(wrapped, argv, envp); 68 | 69 | /* Should not be reached */ 70 | perror("execve()"); 71 | 72 | return (1); 73 | } 74 | 75 | -------------------------------------------------------------------------------- /webroot/site/emit_pgp.html: -------------------------------------------------------------------------------- 1 | <%args> 2 | $member => '' 3 | 4 | 5 | <%perl> 6 | $m->clear_buffer(); 7 | $r->content_type('application/pgp-keys'); 8 | $r->header_out('Cache-Control' => 'no-cache'); 9 | my $db_and = ''; 10 | my $filename = 'pgpkeys.asc'; 11 | if (length $member) { 12 | my $db_member = $dbh->quote($member); 13 | $db_and = qq{AND (m.ident = $db_member)}; 14 | $filename = "$member.asc"; 15 | } 16 | $r->err_headers_out->add( 17 | 'Content-Disposition' => qq{inline; filename="$filename"} 18 | ); 19 | 20 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 21 | SELECT DISTINCT m.uuid AS uuid, mt.email AS email 22 | FROM member_trustgroup mt 23 | JOIN member m ON (mt.member = m.ident) 24 | JOIN member_email me ON ROW(me.member, me.email) = 25 | ROW(mt.member, mt.email) 26 | WHERE mt.trustgroup IN ( 27 | SELECT trustgroup 28 | FROM member_trustgroup 29 | WHERE member = $Site->{db_member} 30 | ) 31 | AND me.pgpkey_id IS NOT NULL 32 | $db_and 33 | }, {Slice => {}})}) { 34 | my $ring = sprintf '%s/%s.gpg', &common::gpg_key_path($row->{uuid}), $row->{uuid}; 35 | my $secring = sprintf '%s/%s.secring', &common::gpg_key_path($row->{uuid}), $row->{uuid}; 36 | &common::gpg_key_present($dbh, $row->{uuid}); 37 | next unless -r $ring; 38 | my $my_gpgcmd = join(' ', 39 | &common::gpgcmd_user($row->{uuid}), 40 | "--primary-keyring $ring", 41 | "--secret-keyring $secring", 42 | '--armor', '--export', $row->{email}); 43 | if (!open(IN, "${my_gpgcmd} |")) { 44 | print STDERR "${my_gpgcmd}: $!"; 45 | return; 46 | } 47 | while (read(IN, my $buffer, 4096)) { 48 | print $buffer; 49 | } 50 | close(IN); 51 | } 52 | $m->comp('/site/emit_mlkey.html:do') unless length $member; 53 | $m->abort(); 54 | 55 | 56 | <%shared> 57 | my $q = undef; 58 | my $dbh = undef; 59 | my $tg = undef; 60 | 61 | 62 | <%init> 63 | $q = $Mech->{cgi}; 64 | $dbh = $Site->{dbh}; 65 | $tg = $Site->{tg}; 66 | 67 | -------------------------------------------------------------------------------- /library/http-real.inc.template: -------------------------------------------------------------------------------- 1 | 2 | ServerName !portalname!.!domain! 3 | ServerAdmin hostmaster@!domain! 4 | Redirect 301 / https://!portalname!.!domain!/ 5 | 6 | 7 | 8 | 9 | 10 | ServerName !portalname!.!domain! 11 | ServerAdmin hostmaster@!domain! 12 | DocumentRoot !portal!/webroot 13 | DirectoryIndex index.html 14 | ErrorLog !portal!/logs/error_log 15 | CustomLog !portal!/logs/access_log combined 16 | 17 | PerlAddVar MasonCompRoot "private => !portal!/webroot" 18 | PerlSetVar MasonAllowGlobals $Mech 19 | PerlAddVar MasonAllowGlobals $Site 20 | PerlSetVar MasonDataDir /var/local/mason/ops-trust 21 | PerlSetVar MasonErrorFormat brief 22 | PerlSetVar TGName main 23 | PerlModule HTML::Mason::ApacheHandler 24 | PerlModule CGI 25 | PerlPostConfigRequire !library!/common.pm 26 | 27 | SetHandler perl-script 28 | PerlHandler HTML::Mason::ApacheHandler 29 | 30 | 31 | DefaultType text/html 32 | 33 | # SSL Configuration 34 | SSLEngine on 35 | SSLCertificateFile !sslcertfile! 36 | SSLCertificateKeyFile !sslkeyfile! 37 | SSLCertificateChainFile !sslchainfile! 38 | 39 | # Restrict the CipherSuite to things which we believe to be 'secure' 40 | # We can get PFS by making TLSv1+HIGH+EDH: this will drop all MS IE users. 41 | # From: https://cipherli.st/ (note we are damning the IE users to hell here) 42 | SSLCipherSuite AES256+EECDH:AES256+EDH 43 | SSLProtocol All -SSLv2 -SSLv3 44 | SSLHonorCipherOrder On 45 | 46 | # Handle MS-IE issues with SSL services. 47 | SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown 48 | 49 | CustomLog !portal!/logs/ssl_request_log "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b" 50 | 51 | 52 | 53 | 54 | Options Indexes FollowSymLinks 55 | AllowOverride All 56 | Order allow,deny 57 | Allow from all 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /library/approve.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | use strict; 21 | use warnings; 22 | use FindBin; 23 | use lib $FindBin::Bin; 24 | use common; 25 | 26 | $| = 1; 27 | my $dbh = &common::get_dbh(); 28 | 29 | my ($ident) = @ARGV; 30 | die "usage: $0 ident" unless defined $ident && length $ident; 31 | my $db_ident = $dbh->quote($ident); 32 | 33 | my $row = $dbh->selectrow_hashref(qq{ 34 | SELECT m.descr 35 | FROM member m 36 | WHERE m.ident = $db_ident 37 | }); 38 | die "no such member $db_ident" unless defined $row; 39 | print "Found member: $db_ident ($row->{descr})\n", "\n"; 40 | 41 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 42 | SELECT mt.trustgroup, mt.email 43 | FROM member_trustgroup mt 44 | WHERE mt.member = $db_ident 45 | AND mt.state = 'vetted' 46 | }, {Slice => {}} )}) { 47 | my $tg_ident = $row->{trustgroup}; 48 | my $email = $row->{email}; 49 | my $tg = &common::get_tg($dbh, $tg_ident); 50 | print "Approve in trustgroup $$tg->{db_ident} (y/n)? "; 51 | my $yesno = ; 52 | if ($yesno) { 53 | my $stmt = qq{ 54 | UPDATE member_trustgroup 55 | SET state = 'approved' 56 | WHERE ROW(member, trustgroup) = 57 | ROW($db_ident, $tg->{db_ident}) 58 | }; 59 | my $rc = $dbh->do($stmt); 60 | print "{$stmt} ==> $rc\n"; 61 | if ($rc == 1) { 62 | &common::notify_newlyapproved($ident, $email, $tg); 63 | } 64 | } 65 | } 66 | 67 | exit 0; 68 | -------------------------------------------------------------------------------- /webroot/site/index_public.mas.template: -------------------------------------------------------------------------------- 1 |

Mission

2 | 3 |

!title! (or "!brand!") forum is a highly vetted community of security 4 | professionals focused on the operational robustness, integrity, and security 5 | of the Internet. The community promotes responsible action against malicious 6 | behavior beyond just observation, analysis and research. !brand! carefully 7 | expands membership pulling talent from many other security forums looking 8 | for strong vetting with in three areas: 9 |

    10 |
  1. sphere of trust; 11 |
  2. sphere of action; 12 |
  3. the ability to maintain a "need to know" confidentiality. 13 |
14 | 15 |

!title! (or "!brand!") members are in a position to directly affect 16 | Internet security operations in some meaningful way. The community's members 17 | span the breadth of the industry including service providers, equipment 18 | vendors, financial institutions, mail admins, DNS admins, DNS registrars, 19 | content hosting providers, law enforcement organizations/agencies, CSIRT 20 | Teams, and third party organizations that provide security-related services 21 | for public benefit (e.g. monitoring or filtering service providers). The 22 | breadth of membership, along with an action plus trust vetting approach 23 | creates a community which would be in a position to apply focused attention on 24 | the malfeasant behaviors which threaten the Internet. 25 | 26 |

Members: 27 |

    28 |
  • will be privy to lists of infected IP addresses, compromised 29 | accounts, bot c&c lists and other data that should be acted upon. 30 |
  • are expected to take appropriate action within their domain of control. 31 |
  • are expected to contribute data as appropriate and in a fashion that 32 | does not violate any laws or corporate policies. 33 |
34 | 35 |

!brand! does not accept applications for membership. New candidates are 36 | nominated by their peers who are actively working with them on improving the 37 | operational robustness, integrity, and security of the Internet. 38 | 39 | <%method is_public> 40 | # no-op, just here to tell autohandler that this page can be 41 | # accessed without logging in. 42 | 43 | -------------------------------------------------------------------------------- /library/funcs.sh: -------------------------------------------------------------------------------- 1 | # Helpful Shell Functions 2 | # 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | # Force our path to contain /usr/local/bin, just in case 21 | PATH=/usr/local/bin:$PATH 22 | export PATH 23 | 24 | # Always include our Portal configuration 25 | . ./portal.conf 26 | 27 | # Stores details in LOCKFILE + LOCKTOUCHPID 28 | cron_lock() 29 | { 30 | LOCKNAME=$1 31 | LOCKFILE=/tmp/portal-${LOCKNAME}.lock 32 | 33 | # Avoid running more than one at a time 34 | if [ -x /usr/bin/lockfile-create ] ; then 35 | lockfile-create $LOCKFILE 36 | if [ $? -ne 0 ]; 37 | then 38 | DATE=$(date) 39 | cat < 2 | <%perl> 3 | my $dbh = &common::get_dbh(); 4 | $Site = { 5 | dbh => $dbh 6 | }; 7 | 8 | 9 | 10 | <%method session_setup> 11 | <%perl> 12 | if (defined $Mech->{sess}->param('~logged-in')) { 13 | my $db_username = $Site->{dbh}->quote($Mech->{sess}->param('member')); 14 | 15 | my $tgname = $Mech->{sess}->param('trustgroup'); 16 | if (defined $tgname) { 17 | $Site->{tg} = &common::get_tg($Site->{dbh}, $tgname); 18 | $Site->{can_see} = $Mech->{sess}->param('can_see'); 19 | $Site->{admin} = $Mech->{sess}->param('admin'); 20 | } 21 | if (!defined $Mech->{sess}->param('member') || 22 | !defined $Mech->{sess}->param('change_pw') || 23 | !defined $Mech->{sess}->param('uuid')) 24 | { 25 | my ($ident, $change_pw, $uuid, $sysadmin) = 26 | $Site->{dbh}->selectrow_array(qq{ 27 | SELECT m.ident, m.change_pw, m.uuid, m.sysadmin 28 | FROM member m 29 | WHERE ident = $db_username 30 | }); 31 | $Mech->{sess}->param('member', $ident); 32 | $Mech->{sess}->param('change_pw', $change_pw); 33 | $Mech->{sess}->param('uuid', $uuid); 34 | $Mech->{sess}->param('sysadmin', $sysadmin); 35 | $Mech->{sess}->flush(); 36 | } 37 | $Site->{member} = $Mech->{sess}->param('member'); 38 | $Site->{db_member} = $Site->{dbh}->quote($Site->{member});; 39 | $Site->{change_pw} = $Mech->{sess}->param('change_pw'); 40 | $Site->{uuid} = $Mech->{sess}->param('uuid'); 41 | } 42 | 43 | 44 | 45 | <%method fini> 46 | <%perl> 47 | $Mech->{sess}->flush() if defined $Mech && defined $Mech->{sess}; 48 | &common::drop_dbh($Site->{dbh}); 49 | 50 | 51 | 52 | <%method logout> 53 | <%perl> 54 | $r->header_out('Set-Cookie' => 55 | $Mech->{cgi}->cookie(-name => $Mech->{sess}->name, 56 | -value => $Mech->{sess}->id(), 57 | -expires => '-1h', 58 | -httponly => 1, 59 | -secure => 1)); 60 | $Mech->{sess}->delete(); 61 | $Mech->{sess}->flush(); 62 | 63 | 64 | 65 | <%method request_ok> 66 | <%args> 67 | $request 68 | 69 | <%perl> 70 | if (!$request->method_exists('tg_not_needed') && 71 | !defined $Site->{tg}) 72 | { 73 | print 'Must first select trustgroup.'; 74 | } elsif (!$request->method_exists('vetting_not_needed') && 75 | !$Site->{can_see}) 76 | { 77 | print 'Must first be vetted.'; 78 | } else { 79 | print 'OK'; 80 | } 81 | 82 | 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ops-Trust Platform - Portal 2 | 3 | This is the code that runs [Ops-Trust](https://www.ops-trust.net). 4 | 5 | It is placed under the [Apache Version 2.0 License](http://www.apache.org/licenses/). 6 | 7 | There is a short [installation text](INSTALL.md) which describes prerequisites. 8 | Proper packaging for both FreeBSD and Debian are following. 9 | 10 | ## Installation Requirements 11 | The Ops-T Database permission architecture has the following invariants: 12 | 13 | * we use PostGreSQL 9.1 or later, which need not run on the same host w/ us 14 | * our users and apps do not specify a username or password in PQconnect() 15 | * www (freebsd) and www-data (linux) and all sysadmins need to be in the ACL 16 | * the "sysadmin" group has to have all the sysadmins in it as group members 17 | * your pg_hba.conf file should permit the portal and mail hosts to connect 18 | * Software: Apache2, FastCGI, mod_perl 19 | 20 | Dev cycle is: 21 | 22 | * cd ~root/portal, edit, 'make' 23 | * sudo make install; test 24 | * consider restarting apache if you've changed a file it may have cached 25 | * consider installing some database updates 26 | * commit and push; consider going to other portals and doing 'git pull' etc 27 | 28 | Install cycle is: 29 | 30 | * cd ~root/portal, clone, cp siteconfig.template siteconfig, edit, 'make' 31 | * sudo make install 32 | * cd /proj/ops-trust/library, and: 33 | * 'psql -h ... ops-trust < sysadmins.psql' 34 | * 'psql -h ... ops-trust < main-tg.psql' 35 | * do something about postfix 36 | * do something about apache 37 | * test 38 | 39 | One then needs to add to /etc/aliases: 40 | ``` 41 | opstrust-mail-handler: "|/proj/ops-trust/library/mh-wrapper" 42 | ``` 43 | 44 | and to /etc/postfix/virtual something similar to: 45 | ``` 46 | mail-handler@ops-trust.net opstrust-mail-handler 47 | @ops-trust.net opstrust-mail-handler 48 | ``` 49 | 50 | Of course configuring postfix properly and setting up Apache. 51 | 52 | So, please note: 53 | 54 | ~root/portal is "the" checked out copy of the portal code right now. We are 55 | not yet managing this with 'puppet' because of the 'siteconfig' file. Don't 56 | freak out, just please don't make your own on-host clone and 'make install' 57 | from it without also (a) warning the other sysadmins, (b) committing your 58 | changes and pushing them, (c) pulling your changes into ~root/portal, making, 59 | and 'sudo make install' from there, to ensure that it's really what's running. 60 | 61 | -------------------------------------------------------------------------------- /library/chpw.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | use strict; 21 | use warnings; 22 | use FindBin; 23 | use lib $FindBin::Bin; 24 | use common; 25 | 26 | $| = 1; 27 | my $dbh = &common::get_dbh(); 28 | 29 | my ($ident, $which) = @ARGV; 30 | die "usage: $0 ident" unless defined $ident && length $ident; 31 | my $db_ident = $dbh->quote($ident); 32 | $which = 'portal' unless $which; 33 | die "usage: $0 $ident [portal|chat]" 34 | unless $which eq 'portal' || $which eq 'chat'; 35 | 36 | my $row = $dbh->selectrow_hashref(qq{ 37 | SELECT m.descr 38 | FROM member m 39 | WHERE m.ident = $db_ident 40 | }); 41 | die "no such member $db_ident" unless defined $row; 42 | print "Found member: $db_ident ($row->{descr})\n\n"; 43 | 44 | print "Enter new password: "; 45 | my $pass1 = ; 46 | die unless $pass1; 47 | chomp $pass1; 48 | 49 | print "Re-enter new password: "; 50 | my $pass2 = ; 51 | die unless $pass2; 52 | chomp $pass2; 53 | 54 | die "password mismatch" unless $pass1 eq $pass2; 55 | 56 | my $dbh_newpw; 57 | if ($pass1 eq '') { 58 | $dbh_newpw = 'NULL'; 59 | } else { 60 | my $newpw; 61 | $newpw = &common::mkpw_portal($pass1) if $which eq 'portal'; 62 | $newpw = &common::mkpw_portal($pass1) if $which eq 'chat'; 63 | die "bad which ($which)" unless $newpw; 64 | $dbh_newpw = $dbh->quote($newpw); 65 | } 66 | 67 | my $db_pwfield; 68 | $db_pwfield = 'password' if $which eq 'portal'; 69 | $db_pwfield = 'passwd_chat' if $which eq 'chat'; 70 | die "bad which ($which)" unless $db_pwfield; 71 | # Update the member's passwd, and set login_attempts to zero. 72 | my $stmt = qq{ 73 | UPDATE member 74 | SET $db_pwfield = $dbh_newpw, login_attempts = 0 75 | WHERE ident = $db_ident 76 | }; 77 | my $rc = $dbh->do($stmt); 78 | print "{$stmt} ==> $rc\n"; 79 | &common::password_synch($ident); 80 | 81 | exit 0; 82 | -------------------------------------------------------------------------------- /library/notify-idle.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | 21 | # notify-idle -- send e-mail to members who are idle 22 | 23 | use strict; 24 | use warnings; 25 | use FindBin; 26 | use lib $FindBin::Bin; 27 | use common; 28 | 29 | $ENV{PATH} .= ':/usr/local/sbin'; 30 | 31 | my $dbh = &common::get_dbh(); 32 | my $tgs = { }; 33 | 34 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 35 | SELECT mt.email, m.descr, m.ident, m.affiliation, mt.trustgroup 36 | FROM member m 37 | JOIN member_trustgroup mt ON (mt.member = m.ident) 38 | WHERE mt.state = 'idle' 39 | AND NOT m.furlough 40 | AND NOT m.no_email 41 | }, {Slice => {}})}) { 42 | my $tgname = $row->{trustgroup}; 43 | $tgs->{$tgname} = &common::get_tg($dbh, $tgname) 44 | unless defined $tgs->{$tgname}; 45 | ¬ify($tgs->{$tgname}, $dbh, 46 | @$row{qw[email descr ident affiliation]}); 47 | } 48 | 49 | exit 0; 50 | 51 | sub notify { 52 | my ($tg, $dbh, $email, $descr, $ident, $affiliation) = @_; 53 | 54 | $descr =~ s/"//go; 55 | my $db_email = $dbh->quote($email); 56 | print "notify-idle: [$tg->{ident}] $ident ($affiliation)\n"; 57 | $_ = &common::email_send($tg, $common::hostmaster, # from 58 | $email, # to 59 | undef, # cc 60 | undef, # reply-to 61 | "[ops-t] you are idle in '$tg->{descr}'", # subject 62 | \qq{Your membership in the opsec trust group: 63 | 64 | $tg->{descr} 65 | 66 | ...has reached the 'idle' state, which means you're not receiving 67 | any group e-mails. You have the following options: 68 | 69 | 1. do nothing, and continue to receive mail like this periodically. 70 | 2. log into the portal or send some group e-mail, you'll become non-idle. 71 | 3. log into the portal and select 'furlough' in your profile. 72 | 73 | Questions about this can be addressed to . 74 | }); 75 | print "$_\n" if defined $_; 76 | } 77 | -------------------------------------------------------------------------------- /library/notify-unvetted.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | # This file is part of the Ops-T Portal. 4 | # 5 | # Copyright 2014 Operations Security Administration, Inc. 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | # 19 | 20 | # notify-unvetted -- send e-mail to vettors for each nominee who is 21 | # still unvetted and/or have no pgp key 22 | 23 | use strict; 24 | use warnings; 25 | use FindBin; 26 | use lib $FindBin::Bin; 27 | use common; 28 | use Text::Wrap; 29 | 30 | $ENV{PATH} .= ':/usr/local/sbin'; 31 | 32 | my $tgname = $ARGV[0] || 'main'; 33 | 34 | my $dbh = &common::get_dbh(); 35 | my $tg = &common::get_tg($dbh, $tgname); 36 | my $db_tgname = $dbh->quote($tgname); 37 | 38 | foreach my $row (@{$dbh->selectall_arrayref(qq{ 39 | SELECT m.ident, m.descr 40 | FROM member m 41 | JOIN member_trustgroup mt ON (mt.member = m.ident) 42 | WHERE mt.state = 'nominated' 43 | AND mt.trustgroup = $db_tgname 44 | }, {Slice => {}})}) { 45 | ¬ify($dbh, $tg, @$row{qw[ident descr]}); 46 | } 47 | 48 | exit 0; 49 | 50 | sub notify { 51 | my ($dbh, $tg, $ident, $descr) = @_; 52 | 53 | $descr =~ s/"//go; 54 | my $db_ident = $dbh->quote($ident); 55 | my ($vouchor, $vouchor_descr, $vouchor_email, $howlong) = 56 | $dbh->selectrow_array(qq{ 57 | SELECT mv.vouchor, m.descr, mt.email, 58 | DATE_TRUNC('days', AGE(mv.entered)) 59 | FROM member_vouch mv 60 | JOIN member m ON (mv.vouchor = m.ident) 61 | JOIN member_trustgroup mt ON 62 | ROW(mt.member, mt.trustgroup) = 63 | ROW(m.ident, $tg->{db_ident}) 64 | WHERE ROW(mv.vouchee, mv.trustgroup) = 65 | ROW($db_ident, $db_tgname) 66 | AND mv.positive 67 | ORDER BY mv.entered 68 | LIMIT 1 69 | }); 70 | return unless defined $vouchor; 71 | 72 | $_ = &common::email_send($tg, $common::hostmaster, # tg, from 73 | $vouchor_email, # to 74 | undef, # cc 75 | undef, # reply-to 76 | "nomination status for $ident ($descr)", # subject 77 | Text::Wrap::fill('', '', ( 78 | qq{$ident ($descr) was nominated by $vouchor ($vouchor_descr) $howlong ago 79 | but the membership process is incomplete. 80 | 81 | Not enough members have vouched yet. $vouchor should try to get other 82 | members vouch for this candidate. 83 | } )) # body 84 | ); 85 | print "$_\n" if defined $_; 86 | } 87 | -------------------------------------------------------------------------------- /webroot/site/list_airports.html: -------------------------------------------------------------------------------- 1 | <%args> 2 | $iata => undef 3 | 4 | 5 | <%perl> 6 | 7 | print $q->h4("$n_airports Airports"); 8 | 9 | # transpose 10 | my $nr = int(sqrt($n_airports)); 11 | my $nc = int($n_airports / $nr) + 1; 12 | my @t = ( ); 13 | my $i = 0; 14 | foreach my $airport (sort keys %$airports) { 15 | my $c = int($i++ / $nr); 16 | push @t, [] unless defined $t[$c]; 17 | push @{$t[$c]}, $airport; 18 | } 19 | 20 | # render 21 | print $q->start_table({-cellspacing=>5, -cellpadding=>5}); 22 | for (my $r = 0; $r < $nr; $r++) { 23 | print '

'; 26 | my $airport = $t[$c]->[$r]; 27 | if (defined $airport) { 28 | my $number = 1 + $#{$airports->{$airport}->{members}}; 29 | my $disp_ap = (length $airport) 30 | ? $q->code($airport) 31 | : '∅'; 32 | print $q->a({-href=>$q->url()."?iata=$airport"}, 33 | $disp_ap)." ($number)"; 34 | } 35 | print '
%s %s (%s)%s