-rw-r--r-- | src/.gitignore | 10 | ||||
-rw-r--r-- | src/Makefile.am | 32 | ||||
-rw-r--r-- | src/eyefi.h | 47 | ||||
-rw-r--r-- | src/eyefiservice.cc | 186 | ||||
-rw-r--r-- | src/eyefiworker.cc | 26 | ||||
-rw-r--r-- | src/eyefiworker.h | 15 | ||||
-rw-r--r-- | src/eyekinfig.cc | 67 | ||||
-rw-r--r-- | src/eyekinfig.h | 25 | ||||
-rw-r--r-- | src/eyetil.cc | 103 | ||||
-rw-r--r-- | src/eyetil.h | 48 | ||||
-rw-r--r-- | src/iiid.cc | 86 |
11 files changed, 645 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..425033a --- a/dev/null +++ b/src/.gitignore @@ -0,0 +1,10 @@ +/.deps +eyefi.nsmap +soapC.cpp +soapH.h +soapStub.h +soapeyefiService.cpp +soapeyefiService.h +*.o +/iiid +/COPYING.cc diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..09f698e --- a/dev/null +++ b/src/Makefile.am @@ -0,0 +1,32 @@ +sbin_PROGRAMS=iiid +noinst_HEADERS = \ + eyefi.h \ + eyekinfig.h eyetil.h \ + eyefiworker.h + +AM_CPPFLAGS = ${CPPFLAGS_DEBUG} \ + -DEYEKIN_CONF_DIR=\"${sysconfdir}/${PACKAGE}\" +DEFAULT_INCLUDES = -I${top_builddir} -I${builddir} -I${srcdir} +INCLUDES = ${MODULES_CFLAGS} + +iiid_SOURCES = iiid.cc \ + eyekinfig.cc eyetil.cc \ + eyefiservice.cc eyefiworker.cc +nodist_iiid_SOURCES = \ + ${builddir}/soapC.cpp ${builddir}/soapeyefiService.cpp \ + COPYING.cc +iiid_LDADD = ${MODULES_LIBS} + +COPYING.cc: ${top_srcdir}/COPYING + echo "const char * COPYING = " >$@ || (rm $@;exit 1) + sed -e 's/"/\\"/g' -e 's/^/\"/' -e 's/$$/\\n\"/' $< >>$@ || (rm $@;exit 1) + echo ';' >>$@ || (rm $@;exit 1) + +${srcdir}/eyefiservice.cc: ${builddir}/soapeyefiService.h +${srcdir}/iiid.cc: ${builddir}/eyefi.nsmap + +${builddir}soapC.cpp ${builddir}/soapeyefiService.cpp ${builddir}/eyefi.nsmap ${builddir}/soapeyefiService.h: ${srcdir}/eyefi.h + ${SOAPCPP2} -d${builddir} -S -L -a -i -w -x $< + +clean-local: + rm -f soap{{H,Stub,eyefiService}.h,{C,eyefiService}.cpp} eyefi.nsmap COPYING.cc diff --git a/src/eyefi.h b/src/eyefi.h new file mode 100644 index 0000000..70e918d --- a/dev/null +++ b/src/eyefi.h @@ -0,0 +1,47 @@ +//gsoap efs service name: eyefi +//gsoap efs service location: http://api.eye.fi/api/soap/eyefilm/v1 +//gsoap efs service namespace: EyeFi/SOAP/EyeFilm +//gsoap efs service method-action: StartSession "urn:StartSession" +//gsoap efs service method-action: GetPhotoStatus "urn:GetPhotoStatus" +//gsoap efs service method-action: MarkLastPhotoInRoll "urn:MarkLastPhotoInRoll" +//gsoap rns service namespace: http://localhost/api/soap/eyefilm + +struct rns__StartSessionResponse { + std::string credential; + std::string snonce; + int transfermode; + unsigned int transfermodetimestamp; + bool upsyncallowed; +}; + +int efs__StartSession( + std::string macaddress,std::string cnonce, + int transfermode,long transfermodetimestamp, + struct rns__StartSessionResponse &r ); + +struct rns__GetPhotoStatusResponse { + int fileid; + long offset; +}; + +int efs__GetPhotoStatus( + std::string credential, std::string macaddress, + std::string filename, long filesize, std::string filesignature, + struct rns__GetPhotoStatusResponse &r ); + +struct rns__MarkLastPhotoInRollResponse { +}; + +int efs__MarkLastPhotoInRoll( + std::string macaddress, int mergedelta, + struct rns__MarkLastPhotoInRollResponse &r ); + +struct rns__UploadPhotoResponse { + bool success; +}; + +int efs__UploadPhoto( + int fileid, std::string macaddress, + std::string filename, long filesize, std::string filesignature, + std::string encryption, int flags, + struct rns__UploadPhotoResponse& r ); diff --git a/src/eyefiservice.cc b/src/eyefiservice.cc new file mode 100644 index 0000000..30c06fa --- a/dev/null +++ b/src/eyefiservice.cc @@ -0,0 +1,186 @@ +#include <cassert> +#include <iostream> +#include <fstream> +#include <stdexcept> +#include <iterator> +#include <syslog.h> +#include <sys/wait.h> +#include <autosprintf.h> +#include "eyekinfig.h" +#include "eyetil.h" +#include "soapeyefiService.h" + +static bool detached_child() { + pid_t p = fork(); + if(p<0) throw std::runtime_error("failed to fork()"); + if(!p) { + p = fork(); + if(p<0) { + syslog(LOG_ERR,"Failed to re-fork child process"); + _exit(-1); + } + if(!p) { + setsid(); + for(int i=getdtablesize();i>=0;--i) close(i); + int i=open("/dev/null",O_RDWR); assert(i==0); + i = dup(i); assert(i==1); + i = dup(i); assert(i==2); + return true; + } + _exit(0); + } + int rc; + if(waitpid(p,&rc,0)<0) throw std::runtime_error("failed to waitpid()"); + if(!WIFEXITED(rc)) throw std::runtime_error("error in forked process"); + if(WEXITSTATUS(rc)) throw std::runtime_error("forked process signalled error"); + return false; +} + +int eyefiService::StartSession( + std::string macaddress,std::string cnonce, + int transfermode,long transfermodetimestamp, + struct rns__StartSessionResponse &r ) { +#ifndef NDEBUG + syslog(LOG_DEBUG, + "StartSession request from %s with cnonce=%s, transfermode=%d, transfermodetimestamp=%ld", + macaddress.c_str(), cnonce.c_str(), transfermode, transfermodetimestamp ); +#endif + r.credential = binary_t(macaddress+cnonce+eyekinfig_t(macaddress).get_upload_key()).md5().hex(); + /* TODO: better nonce generator */ + time_t t = time(0); + r.snonce = binary_t(&t,sizeof(t)).md5().hex(); + r.transfermode=2; + r.transfermodetimestamp=t; + r.upsyncallowed=false; + + std::string cmd = eyekinfig_t(macaddress).get_on_start_session(); + if(!cmd.empty()) { + if(detached_child()) { + putenv( gnu::autosprintf("EYEFI_MACADDRESS=%s",macaddress.c_str()) ); + putenv( gnu::autosprintf("EYEFI_TRANSFERMODE=%d",transfermode) ); + putenv( gnu::autosprintf("EYEFI_TRANSFERMODETIMESTAMP=%ld",transfermodetimestamp) ); + char *argv[] = { (char*)"/bin/sh", (char*)"-c", (char*)cmd.c_str(), 0 }; + execv("/bin/sh",argv); + syslog(LOG_ERR,"Failed to execute '%s'",cmd.c_str()); + _exit(-1); + } + } + return SOAP_OK; +} + +int eyefiService::GetPhotoStatus( + std::string credential, std::string macaddress, + std::string filename, long filesize, std::string filesignature, + struct rns__GetPhotoStatusResponse &r ) { +#ifndef NDEBUG + syslog(LOG_DEBUG, + "GetPhotoStatus request from %s with credential=%s, filename=%s, filesize=%ld, filesignature=%s", + macaddress.c_str(), credential.c_str(), filename.c_str(), filesize, filesignature.c_str() ); +#endif + r.fileid = 1; r.offset = 0; + return SOAP_OK; +} + +int eyefiService::MarkLastPhotoInRoll( + std::string macaddress, int mergedelta, + struct rns__MarkLastPhotoInRollResponse &r ) { +#ifndef NDEBUG + syslog(LOG_DEBUG, + "MarkLastPhotoInRoll request from %s with mergedelta=%d", + macaddress.c_str(), mergedelta ); +#endif + std::string cmd = eyekinfig_t(macaddress).get_on_mark_last_photo_in_roll(); + if(!cmd.empty()) { + if(detached_child()) { + putenv( gnu::autosprintf("EYEFI_MACADDRESS=%s",macaddress.c_str()) ); + putenv( gnu::autosprintf("EYEFI_MERGEDELTA=%d",mergedelta) ); + char *argv[] = { (char*)"/bin/sh", (char*)"-c", (char*)cmd.c_str(), 0 }; + execv("/bin/sh",argv); + syslog(LOG_ERR,"Failed to execute '%s'",cmd.c_str()); + _exit(-1); + } + } + return SOAP_OK; +} + +int eyefiService::UploadPhoto( + int fileid, std::string macaddress, + std::string filename, long filesize, std::string filesignature, + std::string encryption, int flags, + struct rns__UploadPhotoResponse& r ) { +#ifndef NDEBUG + syslog(LOG_DEBUG, + "UploadPhoto request from %s with fileid=%d, filename=%s, filesize=%ld," + " filesignature=%s, encryption=%s, flags=%04X", + macaddress.c_str(), fileid, filename.c_str(), filesize, + filesignature.c_str(), encryption.c_str(), flags ); +#endif + eyekinfig_t eyekinfig(macaddress); + + umask(eyekinfig.get_umask()); + + std::string td = eyekinfig.get_targetdir(); + /* TODO: try to create, if needed */ + tmpdir_t indir(td+"/.incoming.XXXXXX"); + + for(soap_multipart::iterator i=mime.begin(),ie=mime.end();i!=ie;++i) { +#ifndef NDEBUG + syslog(LOG_DEBUG, + " MIME attachment with id=%s, type=%s, size=%ld", + (*i).id, (*i).type, (long)(*i).size ); +#endif + +#ifndef NDEBUG + if((*i).id && !strcmp((*i).id,"INTEGRITYDIGEST")) { + std::string idigest((*i).ptr,(*i).size); + syslog(LOG_DEBUG, " INTEGRITYDIGEST=%s", idigest.c_str()); + } +#endif + if( (*i).id && !strcmp((*i).id,"FILENAME") ) { + assert( (*i).type && !strcmp((*i).type,"application/x-tar") ); +#ifdef III_SAVE_TARS + std::string tarfile = indir.get_file(filename); + { + std::ofstream(tarfile.c_str(),std::ios::out|std::ios::binary).write((*i).ptr,(*i).size); + } +#endif + tarchive_t a((*i).ptr,(*i).size); + if(!a.read_next_header()) + throw std::runtime_error("failed to tarchive_t::read_next_header())"); + std::string jf = indir.get_file(a.entry_pathname()); + std::string::size_type ls = jf.rfind('/'); + std::string jbn = (ls==std::string::npos)?jf:jf.substr(ls+1); + int fd=open(jf.c_str(),O_CREAT|O_WRONLY,0666); + assert(fd>0); + a.read_data_into_fd(fd); + close(fd); + std::string tf = td+'/'+jbn; + bool success = false; + if(!link(jf.c_str(), tf.c_str())) { + unlink(jf.c_str()); success = true; + }else{ + for(int i=1;i<32767;++i) { + tf = (const char*)gnu::autosprintf( "%s/(%05d)%s", + td.c_str(), i, jbn.c_str() ); + if(!link(jf.c_str(), tf.c_str())) { + unlink(jf.c_str()); success = true; + break; + } + } + } + std::string cmd = eyekinfig.get_on_upload_photo(); + if(success && !cmd.empty()) { + if(detached_child()) { + putenv( gnu::autosprintf("EYEFI_MACADDRESS=%s",macaddress.c_str()) ); + putenv( gnu::autosprintf("EYEFI_UPLOADED=%s",tf.c_str()) ); + char *argv[] = { (char*)"/bin/sh", (char*)"-c", (char*)cmd.c_str(), 0 }; + execv("/bin/sh",argv); + syslog(LOG_ERR,"Failed to execute '%s'",cmd.c_str()); + _exit(-1); + } + } + } + } + r.success = true; + return SOAP_OK; +} diff --git a/src/eyefiworker.cc b/src/eyefiworker.cc new file mode 100644 index 0000000..d87c36e --- a/dev/null +++ b/src/eyefiworker.cc @@ -0,0 +1,26 @@ +#include <sys/wait.h> +#include <stdexcept> +#include "eyefiworker.h" + +eyefiworker::eyefiworker() + : eyefiService(SOAP_IO_STORE|SOAP_IO_KEEPALIVE) { + bind_flags = SO_REUSEADDR; max_keep_alive = 0; + } + +int eyefiworker::run(int port) { + if(!soap_valid_socket(bind(0,port,5))) + throw std::runtime_error("failed to bind()"); + while(true) { + while(waitpid(-1,0,WNOHANG)>0); + if(!soap_valid_socket(accept())) + throw std::runtime_error("failed to accept()"); + pid_t p = fork(); + if(p<0) throw std::runtime_error("failed to fork()"); + if(!p) { + (void)serve(); + soap_destroy(this); soap_end(this); soap_done(this); + _exit(0); + } + close(socket); socket = SOAP_INVALID_SOCKET; + } +} diff --git a/src/eyefiworker.h b/src/eyefiworker.h new file mode 100644 index 0000000..c08ec8b --- a/dev/null +++ b/src/eyefiworker.h @@ -0,0 +1,15 @@ +#ifndef __EYEFIWORKER_H +#define __EYEFIWORKER_H + +#include "soapeyefiService.h" + +class eyefiworker : public eyefiService { + public: + + eyefiworker(); + + int run(int port); + +}; + +#endif /* __EYEFIWORKER_H */ diff --git a/src/eyekinfig.cc b/src/eyekinfig.cc new file mode 100644 index 0000000..27a5a56 --- a/dev/null +++ b/src/eyekinfig.cc @@ -0,0 +1,67 @@ +#include <cassert> +#include <stdexcept> +#include <autosprintf.h> +#include "eyekinfig.h" + +#include "config.h" + +eyekinfig_t::eyekinfig_t(const std::string& ma) + : macaddress(ma) { + static cfg_opt_t opts[] = { + CFG_STR((char*)"targetdir",(char*)"/var/lib/" PACKAGE "/%s",CFGF_NONE), + CFG_STR((char*)"uploadkey",(char*)"",CFGF_NONE), + CFG_STR((char*)"on-start-session",(char*)"",CFGF_NONE), + CFG_STR((char*)"on-upload-photo",(char*)"",CFGF_NONE), + CFG_STR((char*)"on-mark-last-photo-in-roll",(char*)"",CFGF_NONE), + CFG_INT((char*)"umask",022,CFGF_NONE), + CFG_END() + }; + cfg = cfg_init(opts,CFGF_NONE); + if(!cfg) + throw std::runtime_error("failed to cfg_init()"); + std::string::size_type ls = macaddress.rfind('/'); + if(cfg_parse(cfg,gnu::autosprintf( + EYEKIN_CONF_DIR "/%s.conf", + (ls==std::string::npos) + ? macaddress.c_str() + : macaddress.substr(ls+1).c_str() + )) ==CFG_PARSE_ERROR) { + if(cfg) cfg_free(cfg); + cfg=0; + throw std::runtime_error("failed to cfg_parse()"); + } + } + +eyekinfig_t::~eyekinfig_t() { + if(cfg) cfg_free(cfg); +} + +std::string eyekinfig_t::get_targetdir() { + assert(cfg); + return gnu::autosprintf(cfg_getstr(cfg,"targetdir"),macaddress.c_str()); +} + +std::string eyekinfig_t::get_upload_key() { + assert(cfg); + return cfg_getstr(cfg,"uploadkey"); +} + +std::string eyekinfig_t::get_on_start_session() { + assert(cfg); + return cfg_getstr(cfg,"on-start-session"); +} +std::string eyekinfig_t::get_on_upload_photo() { + assert(cfg); + return cfg_getstr(cfg,"on-upload-photo"); +} + +std::string eyekinfig_t::get_on_mark_last_photo_in_roll() { + assert(cfg); + return cfg_getstr(cfg,"on-mark-last-photo-in-roll"); +} + + +int eyekinfig_t::get_umask() { + assert(cfg); + return cfg_getint(cfg,"umask"); +} diff --git a/src/eyekinfig.h b/src/eyekinfig.h new file mode 100644 index 0000000..34f8d49 --- a/dev/null +++ b/src/eyekinfig.h @@ -0,0 +1,25 @@ +#ifndef __EYEKINFIG_H +#define __EYEKINFIG_H + +#include <confuse.h> +#include <string> + +class eyekinfig_t { + public: + std::string macaddress; + cfg_t *cfg; + + eyekinfig_t(const std::string& ma); + ~eyekinfig_t(); + + std::string get_targetdir(); + std::string get_upload_key(); + + std::string get_on_start_session(); + std::string get_on_upload_photo(); + std::string get_on_mark_last_photo_in_roll(); + + int get_umask(); +}; + +#endif /* __EYEKINFIG_H */ diff --git a/src/eyetil.cc b/src/eyetil.cc new file mode 100644 index 0000000..d00c2ee --- a/dev/null +++ b/src/eyetil.cc @@ -0,0 +1,103 @@ +#include <stdlib.h> +#include <syslog.h> +#include <iostream> +#include <cassert> +#include <stdexcept> +#include <openssl/md5.h> +#include "eyetil.h" + +binary_t& binary_t::from_hex(const std::string& h) { + /* TODO: algorithmize */ + std::string::size_type hs = h.length(); + if(hs&1) + throw std::runtime_error("odd number of characters in hexadecimal number"); + int rvs = hs>>1; + resize(rvs); + const unsigned char *hp = (const unsigned char*)h.data(); + iterator oi=begin(); + char t[3] = { 0,0,0 }; + for(int i=0;i<rvs;++i) { + t[0]=*(hp++); t[1]=*(hp++); + *(oi++) = strtol(t,0,16); + } + return *this; +} + +binary_t& binary_t::from_data(const void *d,size_t s) { + resize(s); + std::copy((const unsigned char*)d,(const unsigned char *)d+s, + begin() ); + return *this; +} + +std::string binary_t::hex() const { + std::string rv; + rv.reserve((size()<<1)+1); + char t[3] = {0,0,0}; + for(const_iterator i=begin(),ie=end();i!=ie;++i) { + int rc = snprintf(t,sizeof(t),"%02x",*i); + assert(rc<sizeof(t)); + rv += t; + } + return rv; +} + +binary_t binary_t::md5() const { + binary_t rv(MD5_DIGEST_LENGTH); + if(!MD5( + (const unsigned char*)&(front()),size(), + (unsigned char*)&(rv.front()) )) + throw std::runtime_error("failed to md5()"); + return rv; +} + +tmpdir_t::tmpdir_t(const std::string& dt) : dir(dt) { + if(!mkdtemp((char*)dir.data())) + throw std::runtime_error("failed to mkdtmp()"); +} +tmpdir_t::~tmpdir_t() { + assert(!dir.empty()); + if(rmdir(dir.c_str())) { + syslog(LOG_WARNING,"Failed to remove '%s' directory",dir.c_str()); + } +} + +std::string tmpdir_t::get_file(const std::string& f) { + std::string::size_type ls = f.rfind('/'); + return dir+'/'+( + (ls==std::string::npos) + ? f + : f.substr(ls+1) + ); +} + +tarchive_t::tarchive_t(void *p,size_t s) : a(archive_read_new()), e(0) { + if(!a) throw std::runtime_error("failed to archive_read_new()"); + if(archive_read_support_format_tar(a)) { + archive_read_finish(a); + throw std::runtime_error("failed to archive_read_support_format_tar()"); + } + if(archive_read_open_memory(a,p,s)) { + archive_read_finish(a); + throw std::runtime_error("failed to archive_read_open_memory()"); + } +} +tarchive_t::~tarchive_t() { + assert(a); + archive_read_finish(a); +} + +bool tarchive_t::read_next_header() { + assert(a); + return archive_read_next_header(a,&e)==ARCHIVE_OK; +} + +std::string tarchive_t::entry_pathname() { + assert(a); assert(e); + return archive_entry_pathname(e); +} + +bool tarchive_t::read_data_into_fd(int fd) { + assert(a); + return archive_read_data_into_fd(a,fd)==ARCHIVE_OK; +} diff --git a/src/eyetil.h b/src/eyetil.h new file mode 100644 index 0000000..195d24f --- a/dev/null +++ b/src/eyetil.h @@ -0,0 +1,48 @@ +#ifndef __EYETIL_H +#define __EYETIL_H + +#include <vector> +#include <string> +#include <archive.h> +#include <archive_entry.h> + +class binary_t : public std::vector<unsigned char> { + public: + binary_t() { } + binary_t(size_type n) : std::vector<unsigned char>(n) { } + binary_t(const std::string& h) { from_hex(h); } + binary_t(const void *d,size_t s) { from_data(d,s); } + + binary_t& from_hex(const std::string& h); + binary_t& from_data(const void *d,size_t s); + + std::string hex() const; + binary_t md5() const; +}; + +class tmpdir_t { + public: + std::string dir; + + tmpdir_t(const std::string& dt); + ~tmpdir_t(); + + std::string get_file(const std::string& f); +}; + +class tarchive_t { + public: + struct archive *a; + struct archive_entry *e; + + tarchive_t(void *p,size_t s); + ~tarchive_t(); + + bool read_next_header(); + + std::string entry_pathname(); + + bool read_data_into_fd(int fd); +}; + +#endif /* __EYETIL_H */ diff --git a/src/iiid.cc b/src/iiid.cc new file mode 100644 index 0000000..6c23790 --- a/dev/null +++ b/src/iiid.cc @@ -0,0 +1,86 @@ +#include <syslog.h> +#include <getopt.h> +#include <iostream> +#include <cassert> +#include <stdexcept> +#include "eyetil.h" +#include "eyefiworker.h" + +#include "config.h" + +#include "eyefi.nsmap" + +#define PHEADER \ + PACKAGE " Version " VERSION "\n" \ + "Copyright (c) 2009 Klever Group" + +int main(int argc,char **argv) try { + + int port = 59278; + + while(true) { + static struct option opts[] = { + { "help", no_argument, 0, 'h' }, + { "usage", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'V' }, + { "license", no_argument, 0, 'L' }, + { "port", required_argument, 0, 'p' }, + { NULL, 0, 0, 0 } + }; + int c = getopt_long(argc,argv,"hVLp:",opts,NULL); + if(c==-1) break; + switch(c) { + case 'h': + std::cerr << PHEADER << std::endl << std::endl + << " " << argv[0] << " [options]" << std::endl + << std::endl << + " -h, --help,\n" + " --usage display this text\n" + " -V, --version display version information\n" + " -L, --license show license\n" + " -p <port>, --port=<port> port to listen to\n" + " (you're not likely to ever need it)\n" + << std::endl << std::endl; + exit(0); + break; + case 'V': + std::cerr << VERSION << std::endl; + exit(0); + break; + case 'L': + extern const char *COPYING; + std::cerr << COPYING << std::endl; + exit(0); + break; + case 'p': + port = strtol(optarg,0,0); + if(errno) { + std::cerr << "Failed to parse port number" << std::endl; + exit(1); + } + break; + default: + std::cerr << "Huh?" << std::endl; + exit(1); + break; + } + } + + const char *ident = rindex(*argv,'/'); + if(ident) + ++ident; + else + ident = *argv; + openlog(ident,LOG_PERROR|LOG_PID,LOG_DAEMON); + syslog(LOG_INFO,"Starting iii eye-fi manager"); + + eyefiworker().run(port); + + closelog(); + return 0; +} catch(std::exception& e) { + syslog(LOG_CRIT,"Exiting iii daemon, because of error condition"); + syslog(LOG_CRIT,"Exception: %s",e.what()); + return 1; +} + |