Merge branch '090-encrypted-recording' into HEAD
Conflicts: bigbluebutton-web/src/java/org/bigbluebutton/api/MeetingService.java record-and-playback/core/Gemfile record-and-playback/deploy.sh
This commit is contained in:
commit
8e12c909c5
@ -329,12 +329,12 @@ if [ $REPUBLISH ]; then
|
||||
fi
|
||||
|
||||
if [ $CLEAN ]; then
|
||||
sudo /etc/init.d/bbb-record stop
|
||||
sudo /etc/init.d/bbb-record-core stop
|
||||
for type in $TYPES; do
|
||||
echo " clearning logs in /var/log/bigbluebutton/$type"
|
||||
find /var/log/bigbluebutton/$type -name "*.log" -exec sudo rm '{}' \;
|
||||
done
|
||||
sudo /etc/init.d/bbb-record start
|
||||
sudo /etc/init.d/bbb-record-core start
|
||||
fi
|
||||
|
||||
if [ $DELETE ]; then
|
||||
|
@ -1516,6 +1516,17 @@ class ApiController {
|
||||
mkp.yield(item.getExtensions())
|
||||
}
|
||||
}
|
||||
}
|
||||
download() {
|
||||
r.getDownloads().each { item ->
|
||||
format{
|
||||
type(item.getFormat())
|
||||
url(item.getUrl())
|
||||
md5(item.getMd5())
|
||||
key(item.getKey())
|
||||
length(item.getLength())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -70,6 +70,12 @@ public class RecordingServiceHelperImp implements RecordingServiceHelper {
|
||||
builder.duration(info.getPlaybackDuration())
|
||||
builder.extension(info.getPlaybackExtensions())
|
||||
}
|
||||
builder.download {
|
||||
builder.format(info.getDownloadFormat())
|
||||
builder.link(info.getDownloadLink())
|
||||
builder.md5(info.getDownloadMd5())
|
||||
builder.key(info.getDownloadKey())
|
||||
}
|
||||
Map<String,String> metainfo = info.getMetadata();
|
||||
builder.meta{
|
||||
metainfo.keySet().each { key ->
|
||||
@ -104,7 +110,10 @@ public class RecordingServiceHelperImp implements RecordingServiceHelper {
|
||||
r.setPlaybackLink(rec.playback.link.text());
|
||||
r.setPlaybackDuration(rec.playback.duration.text());
|
||||
r.setPlaybackExtensions(rec.playback.extension.children());
|
||||
|
||||
r.setDownloadFormat(rec.download.format.text());
|
||||
r.setDownloadLink(rec.download.link.text());
|
||||
r.setDownloadMd5(rec.download.md5.text());
|
||||
r.setDownloadKey(rec.download.key.text());
|
||||
Map<String, String> meta = new HashMap<String, String>();
|
||||
rec.meta.children().each { anode ->
|
||||
log.debug("metadata: "+anode.name()+" "+anode.text())
|
||||
|
@ -29,6 +29,7 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.*;
|
||||
import org.bigbluebutton.api.domain.Download;
|
||||
import org.bigbluebutton.api.domain.Meeting;
|
||||
import org.bigbluebutton.api.domain.Playback;
|
||||
import org.bigbluebutton.api.domain.Recording;
|
||||
@ -364,24 +365,46 @@ public class MeetingService implements MessageListener {
|
||||
|
||||
ArrayList<Playback> plays = new ArrayList<Playback>();
|
||||
|
||||
if (!r.getPlaybackFormat().isEmpty()) {
|
||||
plays.add(new Playback(r.getPlaybackFormat(), r.getPlaybackLink(),
|
||||
getDurationRecording(r.getPlaybackDuration(),
|
||||
r.getEndTime(), r.getStartTime()),
|
||||
r.getPlaybackExtensions()));
|
||||
}
|
||||
r.setPlaybacks(plays);
|
||||
|
||||
ArrayList<Download> downloads = new ArrayList<Download>();
|
||||
if (!r.getDownloadFormat().isEmpty()) {
|
||||
downloads.add(new Download(r.getDownloadFormat(), r.getDownloadLink(),
|
||||
r.getDownloadMd5(), r.getDownloadKey(),
|
||||
getDurationRecording(r.getEndTime(), r.getStartTime())));
|
||||
}
|
||||
r.setDownloads(downloads);
|
||||
|
||||
map.put(r.getId(), r);
|
||||
} else {
|
||||
Recording rec = map.get(r.getId());
|
||||
if (!r.getPlaybackFormat().isEmpty()) {
|
||||
rec.getPlaybacks().add(new Playback(r.getPlaybackFormat(), r.getPlaybackLink(),
|
||||
getDurationRecording(r.getPlaybackDuration(),
|
||||
r.getEndTime(), r.getStartTime()),
|
||||
r.getPlaybackExtensions()));
|
||||
}
|
||||
if (!r.getDownloadFormat().isEmpty()) {
|
||||
rec.getDownloads().add(new Download(r.getDownloadFormat(), r.getDownloadLink(),
|
||||
r.getDownloadMd5(), r.getDownloadKey(),
|
||||
getDurationRecording(r.getEndTime(), r.getStartTime())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private int getDurationRecording(String end, String start) {
|
||||
return getDurationRecording("", end, start);
|
||||
}
|
||||
|
||||
private int getDurationRecording(String playbackDuration, String end, String start) {
|
||||
int duration;
|
||||
try {
|
||||
|
47
bigbluebutton-web/src/java/org/bigbluebutton/api/domain/Download.java
Executable file
47
bigbluebutton-web/src/java/org/bigbluebutton/api/domain/Download.java
Executable file
@ -0,0 +1,47 @@
|
||||
package org.bigbluebutton.api.domain;
|
||||
|
||||
public class Download {
|
||||
private String format;
|
||||
private String url;
|
||||
private int length;
|
||||
private String md5;
|
||||
private String key;
|
||||
public Download(String format, String url, String md5, String key, int length) {
|
||||
this.format = format;
|
||||
this.url = url;
|
||||
this.length = length;
|
||||
this.md5 = md5;
|
||||
this.key = key;
|
||||
}
|
||||
public String getFormat() {
|
||||
return format;
|
||||
}
|
||||
public void setFormat(String format) {
|
||||
this.format = format;
|
||||
}
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
public void setLength(int length) {
|
||||
this.length = length;
|
||||
}
|
||||
public void setMd5(String md5) {
|
||||
this.md5 = md5;
|
||||
}
|
||||
public String getMd5() {
|
||||
return md5;
|
||||
}
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
}
|
@ -36,6 +36,7 @@ public class Recording {
|
||||
private String endTime;
|
||||
private Map<String, String> metadata = new HashMap<String, String>();
|
||||
private ArrayList<Playback> playbacks=new ArrayList<Playback>();
|
||||
private ArrayList<Download> downloads=new ArrayList<Download>();
|
||||
|
||||
//TODO:
|
||||
private String state;
|
||||
@ -44,6 +45,10 @@ public class Recording {
|
||||
private String playbackDuration;
|
||||
private GPathResult playbackExtensions;
|
||||
|
||||
private String downloadLink;
|
||||
private String downloadFormat;
|
||||
private String downloadMd5;
|
||||
private String downloadKey;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
@ -84,7 +89,37 @@ public class Recording {
|
||||
public void setEndTime(String endTime) {
|
||||
this.endTime = convertOldDateFormat(endTime);
|
||||
}
|
||||
public String getDownloadLink() {
|
||||
return downloadLink;
|
||||
}
|
||||
|
||||
public void setDownloadLink(String downloadLink) {
|
||||
this.downloadLink = downloadLink;
|
||||
}
|
||||
|
||||
public String getDownloadFormat() {
|
||||
return downloadFormat;
|
||||
}
|
||||
|
||||
public void setDownloadFormat(String downloadFormat) {
|
||||
this.downloadFormat = downloadFormat;
|
||||
}
|
||||
|
||||
public String getDownloadMd5() {
|
||||
return downloadMd5;
|
||||
}
|
||||
|
||||
public void setDownloadMd5(String downloadMd5) {
|
||||
this.downloadMd5 = downloadMd5;
|
||||
}
|
||||
|
||||
public String getDownloadKey() {
|
||||
return downloadKey;
|
||||
}
|
||||
|
||||
public void setDownloadKey(String downloadKey) {
|
||||
this.downloadKey = downloadKey;
|
||||
}
|
||||
public String getPlaybackLink() {
|
||||
return playbackLink;
|
||||
}
|
||||
@ -141,6 +176,14 @@ public class Recording {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public ArrayList<Download> getDownloads() {
|
||||
return downloads;
|
||||
}
|
||||
|
||||
public void setDownloads(ArrayList<Download> downloads) {
|
||||
this.downloads = downloads;
|
||||
}
|
||||
|
||||
public ArrayList<Playback> getPlaybacks() {
|
||||
return playbacks;
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ require 'recordandplayback/deskshare_archiver'
|
||||
require 'recordandplayback/generators/events'
|
||||
require 'recordandplayback/generators/audio'
|
||||
require 'recordandplayback/generators/video'
|
||||
require 'recordandplayback/generators/mconf_processor'
|
||||
require 'recordandplayback/generators/matterhorn_processor'
|
||||
require 'recordandplayback/generators/audio_processor'
|
||||
require 'recordandplayback/generators/presentation'
|
||||
|
54
record-and-playback/core/lib/recordandplayback/generators/mconf_processor.rb
Executable file
54
record-and-playback/core/lib/recordandplayback/generators/mconf_processor.rb
Executable file
@ -0,0 +1,54 @@
|
||||
# Set encoding to utf-8
|
||||
# encoding: UTF-8
|
||||
|
||||
#
|
||||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
#
|
||||
# Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free Software
|
||||
# Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License along
|
||||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
|
||||
require 'rubygems'
|
||||
require 'fileutils'
|
||||
require 'builder'
|
||||
require 'mime/types'
|
||||
require 'digest/md5'
|
||||
require 'zip'
|
||||
|
||||
module BigBlueButton
|
||||
|
||||
class MconfProcessor
|
||||
def self.zip_directory (directory, zipped_file)
|
||||
BigBlueButton.logger.info("Task: Zipping directory... #{zipped_file} #{directory}")
|
||||
#files = [webcam, deskshare, dublincore, manifest]
|
||||
Zip::File.open(zipped_file, Zip::File::CREATE) do |zipfile|
|
||||
Dir["#{directory}/**/**"].reject{|f|f==zipped_file}.each do |file|
|
||||
zipfile.add(file.sub(directory+'/', ''), file)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.unzip(unzip_dir, zipfile)
|
||||
Zip::File.open(zipfile) do |zip_file|
|
||||
zip_file.each do |f|
|
||||
f_path=File.join(unzip_dir, f.name)
|
||||
FileUtils.mkdir_p(File.dirname(f_path))
|
||||
zip_file.extract(f, f_path) unless File.exist?(f_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
@ -38,7 +38,15 @@ function deploy_format() {
|
||||
done
|
||||
}
|
||||
|
||||
RECORDING_SERVER=false
|
||||
if $RECORDING_SERVER ; then
|
||||
deploy_format "presentation"
|
||||
deploy_format "mconf_decrypter"
|
||||
sudo mv /usr/local/bigbluebutton/core/scripts/mconf-recording-decrypter.initd /etc/init.d/mconf-recording-decrypter
|
||||
sudo mv /usr/local/bigbluebutton/core/scripts/mconf-recording-decrypter.monit /etc/monit/conf.d/mconf-recording-decrypter
|
||||
else
|
||||
deploy_format "mconf_encrypted"
|
||||
fi
|
||||
|
||||
sudo mkdir -p /var/bigbluebutton/playback/
|
||||
sudo mkdir -p /var/bigbluebutton/recording/raw/
|
||||
|
166
record-and-playback/mconf_decrypter/scripts/mconf-decrypter.rb
Executable file
166
record-and-playback/mconf_decrypter/scripts/mconf-decrypter.rb
Executable file
@ -0,0 +1,166 @@
|
||||
#!/usr/bin/ruby
|
||||
# Set encoding to utf-8
|
||||
# encoding: UTF-8
|
||||
#
|
||||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
#
|
||||
# Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free Software
|
||||
# Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License along
|
||||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
require '../../core/lib/recordandplayback'
|
||||
require 'rubygems'
|
||||
require 'yaml'
|
||||
require 'net/http'
|
||||
require 'rexml/document'
|
||||
require 'open-uri'
|
||||
require 'digest/md5'
|
||||
|
||||
BigBlueButton.logger = Logger.new("/var/log/bigbluebutton/mconf_decrypter.log",'daily' )
|
||||
#BigBlueButton.logger = Logger.new(STDOUT)
|
||||
|
||||
bbb_props = YAML::load(File.open('../../core/scripts/bigbluebutton.yml'))
|
||||
mconf_props = YAML::load(File.open('mconf-decrypter.yml'))
|
||||
|
||||
# these properties must be global variables (starting with $)
|
||||
$private_key = mconf_props['private_key']
|
||||
$get_recordings_url = mconf_props['get_recordings_url']
|
||||
$recording_dir = bbb_props['recording_dir']
|
||||
$raw_dir = "#{$recording_dir}/raw"
|
||||
$archived_dir = "#{$recording_dir}/status/archived"
|
||||
|
||||
def fetchRecordings(url)
|
||||
#BigBlueButton.logger.debug("Fetching #{url}")
|
||||
doc = nil
|
||||
begin
|
||||
doc = Nokogiri::XML(Net::HTTP.get_response(URI.parse(url)).body)
|
||||
returncode = doc.xpath("//returncode")
|
||||
if returncode.empty? or returncode.text != "SUCCESS"
|
||||
BigBlueButton.logger.error "getRecordings didn't return success:\n#{doc.to_xml(:indent => 2)}"
|
||||
return false
|
||||
end
|
||||
rescue
|
||||
BigBlueButton.logger.error("Exception occurred: #{$!}")
|
||||
return false
|
||||
end
|
||||
|
||||
doc.xpath("//recording").each do |recording|
|
||||
record_id = recording.xpath(".//recordID").text
|
||||
recording.xpath(".//download/format").each do |format|
|
||||
type = format.xpath(".//type").text
|
||||
if type == "encrypted"
|
||||
meeting_id = record_id
|
||||
file_url = format.xpath(".//url").text
|
||||
key_file_url = format.xpath(".//key").text
|
||||
md5_value = format.xpath(".//md5").text
|
||||
|
||||
encrypted_file = file_url.split("/").last
|
||||
decrypted_file = File.basename(encrypted_file, '.*') + ".zip"
|
||||
if not File.exist?("#{$archived_dir}/#{meeting_id}.done") then
|
||||
Dir.chdir($raw_dir) do
|
||||
BigBlueButton.logger.info("Next recording to be processed is #{meeting_id}")
|
||||
|
||||
BigBlueButton.logger.debug("Removing any file previously downloaded related to this recording")
|
||||
FileUtils.rm_r Dir.glob("#{$raw_dir}/#{record_id}*"), :force => true
|
||||
|
||||
BigBlueButton.logger.debug("recordID = #{record_id}")
|
||||
BigBlueButton.logger.debug("file_url = #{file_url}")
|
||||
BigBlueButton.logger.debug("key_file_url = #{key_file_url}")
|
||||
BigBlueButton.logger.debug("md5_value = #{md5_value}")
|
||||
|
||||
BigBlueButton.logger.info("Downloading the encrypted file to #{encrypted_file}")
|
||||
|
||||
begin
|
||||
writeOut = open(encrypted_file, "wb")
|
||||
writeOut.write(open(file_url).read)
|
||||
writeOut.close
|
||||
rescue Exception => e
|
||||
BigBlueButton.logger.error "Failed to download the encrypted file: #{e.to_s}"
|
||||
next
|
||||
end
|
||||
|
||||
md5_calculated = Digest::MD5.file(encrypted_file)
|
||||
|
||||
if md5_calculated == md5_value
|
||||
BigBlueButton.logger.info("The calculated MD5 matches the expected value")
|
||||
key_file = key_file_url.split("/").last
|
||||
decrypted_key_file = File.basename(key_file, '.*') + ".txt"
|
||||
|
||||
BigBlueButton.logger.info("Downloading the key file to #{key_file}")
|
||||
writeOut = open(key_file, "wb")
|
||||
writeOut.write(open(key_file_url).read)
|
||||
writeOut.close
|
||||
|
||||
if key_file != decrypted_key_file
|
||||
BigBlueButton.logger.debug("Locating private key")
|
||||
if not File.exists?("#{$private_key}")
|
||||
BigBlueButton.logger.error "Couldn't find the private key on #{$private_key}"
|
||||
next
|
||||
end
|
||||
BigBlueButton.logger.debug("Decrypting recording key")
|
||||
command = "openssl rsautl -decrypt -inkey #{$private_key} < #{key_file} > #{decrypted_key_file}"
|
||||
status = BigBlueButton.execute(command, false)
|
||||
if not status.success?
|
||||
BigBlueButton.logger.error "Couldn't decrypt the random key with the server private key"
|
||||
next
|
||||
end
|
||||
FileUtils.rm_r "#{key_file}"
|
||||
else
|
||||
BigBlueButton.logger.info("No public key was used to encrypt the random key")
|
||||
end
|
||||
|
||||
BigBlueButton.logger.debug("Decrypting the recording file")
|
||||
command = "openssl enc -aes-256-cbc -d -pass file:#{decrypted_key_file} < #{encrypted_file} > #{decrypted_file}"
|
||||
status = BigBlueButton.execute(command, false)
|
||||
if not status.success?
|
||||
BigBlueButton.logger.error "Couldn't decrypt the recording file using the random key"
|
||||
next
|
||||
end
|
||||
|
||||
BigBlueButton::MconfProcessor.unzip("#{$raw_dir}/#{meeting_id}", decrypted_file)
|
||||
|
||||
archived_done = File.new("#{$archived_dir}/#{meeting_id}.done", "w")
|
||||
archived_done.write("Archived #{meeting_id}")
|
||||
archived_done.close
|
||||
|
||||
[ "#{encrypted_file}", "#{decrypted_file}", "#{decrypted_key_file}" ].each { |file|
|
||||
BigBlueButton.logger.info("Removing #{file}")
|
||||
FileUtils.rm_r "#{file}"
|
||||
}
|
||||
|
||||
BigBlueButton.logger.info("Recording #{record_id} decrypted successfully")
|
||||
|
||||
else
|
||||
BigBlueButton.logger.error("The calculated MD5 doesn't match the expected value")
|
||||
FileUtils.rm_f(encrypted_file)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
def processGetRecordingsUrlInput(input)
|
||||
if input.respond_to? "each"
|
||||
input.each do |url|
|
||||
processGetRecordingsUrlInput url
|
||||
end
|
||||
else
|
||||
fetchRecordings input
|
||||
end
|
||||
end
|
||||
|
||||
processGetRecordingsUrlInput $get_recordings_url
|
@ -0,0 +1,2 @@
|
||||
get_recordings_url: http://localhost:8080/bigbluebutton/api/getRecordings?checksum=1bc39cd242bb8f9b331af0d2323dc1ba3cabe2fe
|
||||
private_key: /usr/local/bigbluebutton/core/scripts/private.pem
|
@ -0,0 +1,44 @@
|
||||
#!/bin/sh
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: mconf-recording-decrypter
|
||||
# Required-Start: $remote_fs $syslog
|
||||
# Required-Stop: $remote_fs $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Description: Starts the foo service
|
||||
### END INIT INFO
|
||||
|
||||
NAME=mconf-recording-decrypter
|
||||
PID_FILE=/var/run/mconf-recording-decrypter.pid
|
||||
DIR=/usr/local/bigbluebutton/core/scripts
|
||||
EXEC=mconf-decrypter.rb
|
||||
RUN_AS=tomcat7
|
||||
|
||||
if [ ! -f $DIR/$EXEC ]; then
|
||||
echo "$DIR/$EXEC not found."
|
||||
exit
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
echo "Starting $NAME"
|
||||
cd $DIR
|
||||
start-stop-daemon -d $DIR --start --background --pidfile $PID_FILE --chuid $RUN_AS:$RUN_AS --make-pidfile --exec $EXEC --quiet
|
||||
;;
|
||||
stop)
|
||||
echo "Stopping $NAME"
|
||||
start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE --exec $EXEC
|
||||
;;
|
||||
force-reload|restart)
|
||||
$0 stop
|
||||
$0 start
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Use: /etc/init.d/$NAME {start|stop|restart|force-reload}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
@ -0,0 +1,3 @@
|
||||
check process mconf-recording-decrypter with pidfile /var/run/mconf-recording-decrypter.pid
|
||||
start program = "/etc/init.d/mconf-recording-decrypter start" with timeout 60 seconds
|
||||
stop program = "/etc/init.d/mconf-recording-decrypter stop"
|
@ -0,0 +1,22 @@
|
||||
|
||||
#
|
||||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
#
|
||||
# Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free Software
|
||||
# Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License along
|
||||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
location /mconf_encrypted {
|
||||
root /var/bigbluebutton/published;
|
||||
index index.html index.htm;
|
||||
}
|
62
record-and-playback/mconf_encrypted/scripts/process/mconf_encrypted.rb
Executable file
62
record-and-playback/mconf_encrypted/scripts/process/mconf_encrypted.rb
Executable file
@ -0,0 +1,62 @@
|
||||
# Set encoding to utf-8
|
||||
# encoding: UTF-8
|
||||
#
|
||||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
#
|
||||
# Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free Software
|
||||
# Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License along
|
||||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
|
||||
require '../../core/lib/recordandplayback'
|
||||
require 'rubygems'
|
||||
require 'trollop'
|
||||
require 'yaml'
|
||||
|
||||
opts = Trollop::options do
|
||||
opt :meeting_id, "Meeting id to archive", :default => '58f4a6b3-cd07-444d-8564-59116cb53974', :type => String
|
||||
end
|
||||
|
||||
meeting_id = opts[:meeting_id]
|
||||
|
||||
#Mconf process log file
|
||||
logger = Logger.new("/var/log/bigbluebutton/mconf_encrypted/process-#{meeting_id}.log", 'daily' )
|
||||
BigBlueButton.logger = logger
|
||||
|
||||
# This script lives in scripts/archive/steps while bigbluebutton.yml lives in scripts/
|
||||
props = YAML::load(File.open('../../core/scripts/bigbluebutton.yml'))
|
||||
|
||||
recording_dir = props['recording_dir']
|
||||
raw_presentation_src = props['raw_presentation_src']
|
||||
|
||||
meeting_raw_dir = "#{recording_dir}/raw/#{meeting_id}"
|
||||
meeting_raw_presentation_dir = "#{raw_presentation_src}/#{meeting_id}"
|
||||
meeting_process_dir = "#{recording_dir}/process/mconf_encrypted/#{meeting_id}"
|
||||
|
||||
if not FileTest.directory?(meeting_process_dir)
|
||||
FileUtils.mkdir_p "#{meeting_process_dir}"
|
||||
# Create a copy of the raw archives
|
||||
BigBlueButton.logger.info("Copying the recording raw files from #{meeting_raw_dir} to #{meeting_process_dir}")
|
||||
FileUtils.cp_r Dir.glob("#{meeting_raw_dir}/*"), meeting_process_dir
|
||||
|
||||
# There's no need to backup the presentation raw folder now
|
||||
# FileUtils.mkdir_p "#{meeting_process_dir}/presentation_raw"
|
||||
# BigBlueButton.logger.info("Copying the recording presentation from #{meeting_raw_presentation_dir}/#{meeting_id} to #{meeting_process_dir}/presentation_raw")
|
||||
# FileUtils.cp_r Dir.glob("#{meeting_raw_presentation_dir}/#{meeting_id}/*"), "#{meeting_process_dir}/presentation_raw"
|
||||
|
||||
process_done = File.new("#{recording_dir}/status/processed/#{meeting_id}-mconf_encrypted.done", "w")
|
||||
process_done.write("Processed #{meeting_id}")
|
||||
process_done.close
|
||||
end
|
||||
|
171
record-and-playback/mconf_encrypted/scripts/publish/mconf_encrypted.rb
Executable file
171
record-and-playback/mconf_encrypted/scripts/publish/mconf_encrypted.rb
Executable file
@ -0,0 +1,171 @@
|
||||
# Set encoding to utf-8
|
||||
# encoding: UTF-8
|
||||
#
|
||||
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
#
|
||||
# Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU Lesser General Public License as published by the Free Software
|
||||
# Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
# version.
|
||||
#
|
||||
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License along
|
||||
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
require '../../core/lib/recordandplayback'
|
||||
require 'rubygems'
|
||||
require 'yaml'
|
||||
require 'cgi'
|
||||
require 'digest/md5'
|
||||
|
||||
bbb_props = YAML::load(File.open('../../core/scripts/bigbluebutton.yml'))
|
||||
|
||||
recording_dir = bbb_props['recording_dir']
|
||||
playback_host = bbb_props['playback_host']
|
||||
published_dir = bbb_props['published_dir']
|
||||
raw_presentation_src = bbb_props['raw_presentation_src']
|
||||
|
||||
done_files = Dir.glob("#{recording_dir}/status/processed/*.done")
|
||||
done_files.each do |df|
|
||||
match = /(.*)-(.*).done/.match df.sub(/.+\//, "")
|
||||
meeting_id = match[1]
|
||||
if (match[2] == "mconf_encrypted")
|
||||
BigBlueButton.logger = Logger.new("/var/log/bigbluebutton/mconf_encrypted/publish-#{meeting_id}.log", 'daily' )
|
||||
|
||||
meeting_process_dir = "#{recording_dir}/process/mconf_encrypted/#{meeting_id}"
|
||||
meeting_publish_dir = "#{recording_dir}/publish/mconf_encrypted/#{meeting_id}"
|
||||
meeting_published_dir = "#{recording_dir}/published/mconf_encrypted/#{meeting_id}"
|
||||
meeting_raw_dir = "#{recording_dir}/raw/#{meeting_id}"
|
||||
meeting_raw_presentation_dir = "#{raw_presentation_src}/#{meeting_id}"
|
||||
|
||||
if not FileTest.directory?(meeting_publish_dir)
|
||||
FileUtils.mkdir_p meeting_publish_dir
|
||||
|
||||
Dir.chdir(meeting_publish_dir) do
|
||||
BigBlueButton::MconfProcessor.zip_directory(meeting_process_dir, "#{meeting_id}.zip")
|
||||
|
||||
metadata = BigBlueButton::Events.get_meeting_metadata("#{meeting_process_dir}/events.xml")
|
||||
|
||||
length = 16
|
||||
chars = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'
|
||||
password = ''
|
||||
length.times { password << chars[rand(chars.size)] }
|
||||
|
||||
passfile = File.new("#{meeting_id}.txt", "w")
|
||||
passfile.write "#{password}"
|
||||
passfile.close
|
||||
|
||||
# encrypt files
|
||||
command = "openssl enc -aes-256-cbc -pass file:#{meeting_id}.txt < #{meeting_id}.zip > #{meeting_id}.dat"
|
||||
status = BigBlueButton.execute(command)
|
||||
if not status.success?
|
||||
raise "Couldn't encrypt the recording file using the random key"
|
||||
end
|
||||
|
||||
FileUtils.rm_f "#{meeting_id}.zip"
|
||||
|
||||
key_filename = ""
|
||||
if metadata.has_key?('mconflb-rec-server-key') and not metadata['mconflb-rec-server-key'].to_s.empty?
|
||||
key_filename = "#{meeting_id}.enc"
|
||||
# The key is already unescaped in the metadata!!
|
||||
#BigBlueButton.logger.info("Unescaping public key")
|
||||
#public_key_decoded = CGI::unescape("#{metadata['public-key'].to_s}")
|
||||
public_key_decoded = "#{metadata['mconflb-rec-server-key'].to_s}"
|
||||
public_key_filename = "public-key.pem"
|
||||
public_key = File.new("#{public_key_filename}", "w")
|
||||
public_key.write "#{public_key_decoded}"
|
||||
public_key.close
|
||||
|
||||
=begin
|
||||
# Test: print the public key
|
||||
public_key = File.new("#{public_key_filename}", "r")
|
||||
counter = 0
|
||||
while (line = public_key.gets)
|
||||
BigBlueButton.logger.info "#{counter}: #{line}"
|
||||
counter = counter + 1
|
||||
end
|
||||
public_key.close
|
||||
=end
|
||||
|
||||
command = "openssl rsautl -encrypt -pubin -inkey #{public_key_filename} < #{meeting_id}.txt > #{meeting_id}.enc"
|
||||
status = BigBlueButton.execute(command)
|
||||
if not status.success?
|
||||
raise "Couldn't encrypt the random key using the server public key passed as metadata"
|
||||
end
|
||||
|
||||
# Comment it for testing
|
||||
FileUtils.rm_f ["#{meeting_id}.txt", "#{public_key_filename}"]
|
||||
else
|
||||
key_filename = "#{meeting_id}.txt"
|
||||
BigBlueButton.logger.warn "No public key was found in the meeting's metadata"
|
||||
end
|
||||
|
||||
# generate md5 checksum
|
||||
md5sum = Digest::MD5.file("#{meeting_id}.dat")
|
||||
|
||||
BigBlueButton.logger.info("Creating metadata.xml")
|
||||
|
||||
# Create metadata.xml
|
||||
b = Builder::XmlMarkup.new(:indent => 2)
|
||||
metaxml = b.recording {
|
||||
b.id(meeting_id)
|
||||
b.state("available")
|
||||
b.published(true)
|
||||
# Date Format for recordings: Thu Mar 04 14:05:56 UTC 2010
|
||||
b.start_time(BigBlueButton::Events.first_event_timestamp("#{meeting_process_dir}/events.xml"))
|
||||
b.end_time(BigBlueButton::Events.last_event_timestamp("#{meeting_process_dir}/events.xml"))
|
||||
b.download {
|
||||
b.format("encrypted")
|
||||
b.link("http://#{playback_host}/mconf_encrypted/#{meeting_id}/#{meeting_id}.dat")
|
||||
b.md5(md5sum)
|
||||
b.key("http://#{playback_host}/mconf_encrypted/#{meeting_id}/#{key_filename}")
|
||||
}
|
||||
b.meta {
|
||||
BigBlueButton::Events.get_meeting_metadata("#{meeting_process_dir}/events.xml").each { |k,v| b.method_missing(k,v) }
|
||||
}
|
||||
}
|
||||
|
||||
metadata_xml = File.new("metadata.xml","w")
|
||||
metadata_xml.write(metaxml)
|
||||
metadata_xml.close
|
||||
|
||||
BigBlueButton.logger.info("Publishing mconf_encrypted")
|
||||
|
||||
# Now publish this recording
|
||||
if not FileTest.directory?("#{published_dir}/mconf_encrypted")
|
||||
FileUtils.mkdir_p "#{published_dir}/mconf_encrypted"
|
||||
end
|
||||
BigBlueButton.logger.info("Publishing files")
|
||||
FileUtils.cp_r(meeting_publish_dir, "#{published_dir}/mconf_encrypted")
|
||||
|
||||
# it doesn't work since video and deskshare files are owned by red5,
|
||||
# freeswitch files are owned by freeswitch, and this script is ran by
|
||||
# tomcat6, so it can just remove files owned by tomcat6
|
||||
FileUtils.rm_r [ "/usr/share/red5/webapps/video/streams/#{meeting_id}",
|
||||
"/usr/share/red5/webapps/deskshare/streams/#{meeting_id}",
|
||||
Dir.glob("/var/freeswitch/meetings/#{meeting_id}*.wav") ], :force => true
|
||||
|
||||
# Remove all the recording flags
|
||||
FileUtils.rm_f [ "#{recording_dir}/status/sanity/#{meeting_id}.done",
|
||||
"#{recording_dir}/status/recorded/#{meeting_id}.done",
|
||||
"#{recording_dir}/status/archived/#{meeting_id}.done" ]
|
||||
|
||||
# Comment it for testing
|
||||
BigBlueButton.logger.info("Removing the recording raw files: #{meeting_raw_dir}")
|
||||
FileUtils.rm_r meeting_raw_dir, :force => true
|
||||
BigBlueButton.logger.info("Removing the recording presentation: #{meeting_raw_presentation_dir}")
|
||||
FileUtils.rm_r meeting_raw_presentation_dir, :force => true
|
||||
|
||||
publish_done = File.new("#{recording_dir}/status/published/#{meeting_id}-mconf_encrypted.done", "w")
|
||||
publish_done.write("Published #{meeting_id}")
|
||||
publish_done.close
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user