author | Michael Krelin <hacker@klever.net> | 2009-03-08 20:09:21 (UTC) |
---|---|---|
committer | Michael Krelin <hacker@klever.net> | 2009-03-08 20:09:21 (UTC) |
commit | aadaa8b5d7eda23e72dbded9d6437b40358353f3 (patch) (side-by-side diff) | |
tree | 0bff6fdde1e2b9be02b48aaf7d03f095604718e1 | |
download | iii-0.0.zip iii-0.0.tar.gz iii-0.0.tar.bz2 |
Inital commit to public repository0.0
39 files changed, 1094 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0e9c1c2 --- a/dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +Makefile +Makefile.in +/aclocal.m4 +/autom4te.cache +/config.h +/config.h.in +/config.log +/config.status +/configure +/stamp-h1 +/INSTALL +/depcomp +/install-sh +/missing @@ -0,0 +1,12 @@ +Klever dissected: + Michael 'hacker' Krelin <hacker@klever.net> + Leonid Ivanov <kamel@klever.net> + + +Thanks to: + +Brian Muller <bmuller AT butterfat DOT net> of mod_auth_openid project for +suggestions, bug reports, testing and actually making use of the library. + +Joseph Smarr <joseph AT plaxo DOT com> of plaxo.com for robustness enhancements +and making use of my work. @@ -0,0 +1,19 @@ +Copyright (c) 2009 Klever Group (http://www.klever.net/) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 --- a/dev/null +++ b/ChangeLog diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..fb3b5f3 --- a/dev/null +++ b/Makefile.am @@ -0,0 +1,16 @@ +SUBDIRS=doc src + +all-local: NEWS + +NEWS: NEWS.xsl NEWS.xml + ${XSLTPROC} -o $@ $^ + +EXTRA_DIST = NEWS.xml NEWS.xsl + +ISSUEFILES = $$(find ${top_srcdir} -type f '(' \ + -name '*.cc' -or -name '*.h' \ + ')' ) \ + ${top_srcdir}/configure.ac +issues: todo fixme xxx +todo fixme xxx: + @grep --color=auto -in '$@:' ${ISSUEFILES} || true @@ -0,0 +1,2 @@ +0.0 (March 8th, 2009) + - Initial release diff --git a/NEWS.xml b/NEWS.xml new file mode 100644 index 0000000..03e1c42 --- a/dev/null +++ b/NEWS.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="us-ascii"?> +<news> + <version version="0.0" date="March 8th, 2009"> + <ni>Initial release</ni> + </version> +</news> diff --git a/NEWS.xsl b/NEWS.xsl new file mode 100644 index 0000000..7c71307 --- a/dev/null +++ b/NEWS.xsl @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="us-ascii"?> +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + > + <xsl:output + method="text" + encoding="us-ascii" + media-type="text/plain" /> + + <xsl:template match="news"> + <xsl:apply-templates/> + </xsl:template> + <xsl:template match="version"> + <xsl:value-of select="concat(@version,' (',@date,')
')"/> + <xsl:apply-templates/> + </xsl:template> + <xsl:template match="ni"> + <xsl:text> - </xsl:text> + <xsl:apply-templates mode="text"/> + <xsl:text>
</xsl:text> + </xsl:template> + <xsl:template match="*|text()"/> + +</xsl:stylesheet> diff --git a/autogen.sh b/autogen.sh new file mode 100644 index 0000000..86d2d88 --- a/dev/null +++ b/autogen.sh @@ -0,0 +1,8 @@ +#!/bin/sh +WANT_AUTOMAKE=1.8 +export WANT_AUTOMAKE + aclocal \ +&& autoheader \ +&& automake -a \ +&& autoconf \ +&& ./configure "$@" diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..7b1db54 --- a/dev/null +++ b/configure.ac @@ -0,0 +1,71 @@ +AC_INIT([iii], [0.0], [iii-bugs@klever.net]) +AC_CONFIG_SRCDIR([configure.ac]) +AC_CONFIG_HEADERS([config.h]) +AM_INIT_AUTOMAKE([dist-bzip2]) + +AC_PROG_INSTALL +AC_PROG_CXX +AC_PROG_CC +PKG_PROG_PKG_CONFIG + +AC_HEADER_STDC + +AC_PATH_PROG([XSLTPROC],[xsltproc],[true]) + +PKG_CHECK_MODULES([MODULES],[gsoap++ openssl libconfuse],,[ + AC_MSG_ERROR([one of the build dependencies isn't satisfied]) +]) + +AC_PATH_PROG([SOAPCPP2],[soapcpp2],[false]) +test "$SOAPCPP2" = "false" && AC_MSG_ERROR([no soapcpp2 tool, part of gsoap package, found.]) + +notfound=false +AC_CHECK_HEADERS([archive.h],[ + AC_CHECK_LIB([archive],[archive_read_new],,[notfound=true]) +],[notfound=true]) +$notfound && AC_MSG_ERROR([no required libarchive library found. get one from http://people.freebsd.org/~kientzle/libarchive/]) + +notfound=false +AC_LANG_PUSH([C++]) +AC_CHECK_HEADERS([autosprintf.h],[ + AC_CHECK_LIB([asprintf],[main],,[notfound=true]) +],[notfound=true]) +$notfound && AC_MSG_ERROR([no autosprintf, part of gettext, found]) +AC_LANG_POP([C++]) + +nitpick=false +AC_ARG_ENABLE([nitpicking], + AC_HELP_STRING([--enable-nitpicking],[make compiler somewhat overly fastidious about the code it deals with]), + [ test "$enableval" = "no" || nitpick=true ] +) +if $nitpick ; then + CPP_NITPICK="-pedantic -Wall -Wextra -Wundef -Wshadow \ + -Wunsafe-loop-optimizations -Wconversion -Wmissing-format-attribute \ + -Wredundant-decls -ansi -Wlogical-op -Wmissing-noreturn" + C_NITPICK="$CPP_NITPICK" + CXX_NITPICK="$C_NITPICK" + + CPPFLAGS="$CPPFLAGS $CPP_NITPICK" + CFLAGS="$CFLAGS $C_NITPICK" + CXXFLAGS="$CXXFLAGS $CXX_NITPICK" +fi + +ndebug=true +AC_ARG_ENABLE([debug], + AC_HELP_STRING([--enable-debug],[enable debugginc code]), + [ test "$enableval" = "no" || ndebug=false ] +) +if $ndebug ; then + CPPFLAGS_DEBUG="-DNDEBUG" +else + CPPFLAGS_DEBUG="-DDEBUG" +fi +AC_SUBST([CPPFLAGS_DEBUG]) + +AC_CONFIG_FILES([ + Makefile + src/Makefile + doc/Makefile + doc/iiid.8 +]) +AC_OUTPUT diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..3603e7e --- a/dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +iii (0.0) unstable; urgency=low + + * Initial release + + -- Michael Krelin <hacker@klever.net> Sun, 08 Mar 2009 19:39:38 +0100 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..b8626c4 --- a/dev/null +++ b/debian/compat @@ -0,0 +1 @@ +4 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..02a6138 --- a/dev/null +++ b/debian/control @@ -0,0 +1,13 @@ +Source: iii +Section: graphics +Priority: optional +Maintainer: Michael Krelin <hacker@klever.net> +Build-Depends: debhelper (>=7), gsoap, libssl-dev, libconfuse-dev, libarchive-dev, gettext +Standards-Version: 3.8.0 + +Package: iii +Architecture: any +Depends: adduser, ${shlibs:Depends} +Description: Eye-Fi Manager implementation + Implementation of Eye-Fi manager service for Linux + diff --git a/debian/copyright b/debian/copyright new file mode 120000 index 0000000..012065c --- a/dev/null +++ b/debian/copyright @@ -0,0 +1 @@ +../COPYING
\ No newline at end of file diff --git a/debian/dirs b/debian/dirs new file mode 100644 index 0000000..04efab3 --- a/dev/null +++ b/debian/dirs @@ -0,0 +1,3 @@ +/etc/iii +/usr/sbin +/usr/share/doc/iii diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..3cc350b --- a/dev/null +++ b/debian/docs @@ -0,0 +1,2 @@ +AUTHORS +NEWS diff --git a/debian/examples b/debian/examples new file mode 100644 index 0000000..c1afd06 --- a/dev/null +++ b/debian/examples @@ -0,0 +1 @@ +doc/000000000000.conf diff --git a/debian/iii.init b/debian/iii.init new file mode 100755 index 0000000..c60c25e --- a/dev/null +++ b/debian/iii.init @@ -0,0 +1,57 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: iii +# Required-Start: $remote_fs $network +# Required-Stop: $remote_fs $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Start iii eye-fi card manager daemon. +### END INIT INFO + +set -e + +PATH=/sbin:/bin:/usr/sbin:/usr/bin +NAME=iii +DAEMON=/usr/sbin/${NAME}d +DESC="Eye-Fi card manager daemon" +PIDFILE=/var/run/${NAME}d.pid +SCRIPTNAME=/etc/init.d/$NAME +USER=eyefi +SSD="/sbin/start-stop-daemon" + +test -x $DAEMON || exit 0 + +if [ -f /etc/default/$NAME ] ; then + . /etc/default/$NAME + fi + +test -z "$NO_IIID" || exit 0 + +. /lib/lsb/init-functions + +case "$1" in + start) + log_daemon_msg "Starting $DESC" $NAME + $SSD --start --pidfile $PIDFILE --chuid $USER --background --make-pidfile --exec $DAEMON -- $DAEMON_OPTS + log_end_msg $? + ;; + stop) + log_daemon_msg "Stopping $DESC" $NAME + if $SSD --stop --oknodo --retry 30 --pidfile $PIDFILE --exec $DAEMON ; then + rm -f $PIDFILE + log_end_msg 0 + else + log_end_msg 1 + fi + ;; + restart|force-reload) + $SCRIPTNAME stop + $SCRIPTNAME start + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/debian/iii.postinst b/debian/iii.postinst new file mode 100755 index 0000000..20463a1 --- a/dev/null +++ b/debian/iii.postinst @@ -0,0 +1,18 @@ +#!/bin/sh +set -e +UG=eyefi +P=eyekindo +H=/var/lib/$P + +if [ "$1" = configure ] ; then + getent group $UG >/dev/null 2>&1 || addgroup --system $UG + getent passwd $UG >/dev/null 2>&1 || adduser --system --home $H \ + --no-create-home --disabled-password --ingroup $UG $UG + if ! test -d $H ; then + mkdir -p $H + chown $UG:$UG $H + chmod 2770 $H + fi +fi + +#DEBHELPER# diff --git a/debian/iii.postrm b/debian/iii.postrm new file mode 100755 index 0000000..a2c66fa --- a/dev/null +++ b/debian/iii.postrm @@ -0,0 +1,3 @@ +#!/bin/sh + +#DEBHELPER# diff --git a/debian/iii.prerm b/debian/iii.prerm new file mode 100755 index 0000000..6060c4f --- a/dev/null +++ b/debian/iii.prerm @@ -0,0 +1,4 @@ +#!/bin/sh +set -e + +#DEBHELPER# diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..86549d3 --- a/dev/null +++ b/debian/rules @@ -0,0 +1,11 @@ +#!/usr/bin/make -f +%: + dh $@ + +build: build-stamp +build-stamp: + sh autogen.sh --version + dh build --before configure + dh_auto_configure -- --enable-debug + dh build --after configure + touch $@ diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000..b6c3d7c --- a/dev/null +++ b/doc/.gitignore @@ -0,0 +1 @@ +iiid.8 diff --git a/doc/000000000000.conf b/doc/000000000000.conf new file mode 100644 index 0000000..d7b365f --- a/dev/null +++ b/doc/000000000000.conf @@ -0,0 +1,17 @@ +# target directory for uploaded files, if the name contains %s, it is replaced +# with eyefi card mac address. +targetdir = "/var/lib/eyekindo/%s/" +# the upload key as seen in Mac and (perhaps) windows Settings.xml file +uploadkey = "e3e2c4a305cee6bce0ebb38a3259ac08" + +# commands to be executed at certain events, the commands also receive certain +# values of interested passed via environment. +# for on-start-session: EYEFI_MACADDRESS, EYEFI_TRANSFER_MODE, EYEFI_TRANSFERMODETIMESTAMP +on-start-session = "bash /usr/local/lib/iii/on-start-session.bash" +# for on-upload-photo: EYEFI_MACADDRESS, EYEFI_UPLOADED (uploaded file name) +on-upload-photo = "bash /usr/local/lib/iii/on-upload-photo.bash" +# for on-mark-last-photo-in-roll: EYEFI_MACADDRESS, EYEFI_MERGEDELTA +on-mark-last-photo-in-roll = "bash /usr/local/lib/iii/on-mark-last-photo-in-roll.bash" + +# file mode creation mask +umask = 002 diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..4a22498 --- a/dev/null +++ b/doc/Makefile.am @@ -0,0 +1,6 @@ +man_MANS=iiid.8 + +EXTRA_DIST = 000000000000.conf + +clean-local: + rm -f iiid.8 diff --git a/doc/iiid.8.in b/doc/iiid.8.in new file mode 100644 index 0000000..63a941e --- a/dev/null +++ b/doc/iiid.8.in @@ -0,0 +1,106 @@ +.TH iiid 8 "March 8th, 2009" "iiid(8)" "Klever Group (http://www.klever.net/)" +.hla en + +.SH NAME + +iiid \- An eye-fi card management daemon + +.SH SYNOPSYS + +\fBiiid\fR +[\fB-h\fR] [\fB--help\fR] [\fB--usage\fR] +[\fB-V\fR] [\fB--version\fR] +[\fB-L\fR] [\fB--license\fR] +[\fB-p\fR \fIport\fR] [\fB--port=\fR\fIport\fR] + +.SH DESCRIPTION + +iiid daemon is a minimalistic open source eye-fi management daemon +implementation. It is listening for incoming connections and accepts files from +eye-fi card, optionally invoking scripts to sort out files or send out +notifications. + +.SH OPTIONS + +.TP +\fB-p\fR \fIport\fR, \fB--port=\fR\fIport\fR +Set the port to listen to. You're not likely to ever need to change this, +perhaps for debugging purpose or if you want to proxy connections. +.TP +\fB-h\fR, \fB--help\fR, \fB--usage\fR +Display short usage instructions and exit. +.TP +\fB-V\fR, \fB--version\fR +Report version and exit. +.TP +\fB-L\fR, \fB--license\fR +Show licensing terms. + +.SH EXIT STATUS + +The daemon isn't supposed to exit at this point, but, like most executables, it returns zero in case of success, non-zero otherwise. + +.SH FILES + +.TP +@sysconfdir@/iii/\fBXXXXXXXXXXXX.conf/fR +Upon successfull connection, the daemon reads the configuration from this file, +where XXXXXXXXXXXX is the macaddress of your eyefi card. You can lookup the mac +address of your card in your Mac/Windows Settings.xml file. Be sure to remove +dashes from it. + +.SH CONFIGURATION OPTIONS + +.TP +\fBtargetdir\fR = "\fI/var/lib/iii/%s/\fR" +Sets the target directory for uploaded files. If the \fI%s\fR placeholder is +present in the string, it is replaced with card mac address. +.TP +\fBuploadkey\fR = "\fIxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\fR" +The upload key as seen in Mac/Windows Settings.xml file. +.TP +\fBon-start-session\fR = "\fIlogger iii-StartSession ${EYEFI_MACADDRESS} ${EYEFI_TRANSFER_MODE} ${EYEFI_TRANSFERMODETIMESTAMP}\fR" +The command to execute on \fBStartSession\fR request. The command receives some +information via environment variables. +.TP +\fBon-upload-photo\fR = "\fIlogger iii-UploadPhoto ${EYEFI_MACADDRESS} ${EYEFI_UPLOADED}\fR" +The command to execute after successfull photo upload. The information about +the card mac address and uploaded file name is passed via environment +variables. +.TP +\fBon-mark-last-photo-in-roll\fR = "\fIlogger iii-MarkLastPhotoInRoll ${EYEFI_MACADDRESS} ${EYEFI_MERGEDELTA}\fR" +The command to execute on \fBMarkLastPhotoInRoll\fR request. The information +about request is passed via environment variables. +.TP +\fBumask\fR = \fI002\fR +The file mode creation mask. + +.SH AUTHOR + +Written by Michael Krelin <hacker@klever.net> + +.SH COPYRIGHT + +Copyright (c) 2009 Klever Group (http://www.klever.net/) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +.SH BUGS + +You tell me. Send reports to <iii-bugs@klever.net> diff --git a/scripts/xml2sh.xsl b/scripts/xml2sh.xsl new file mode 100644 index 0000000..736bc21 --- a/dev/null +++ b/scripts/xml2sh.xsl @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="us-ascii"?> +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + > + <xsl:output + method="text" + encoding="us-ascii" + media-type="text/plain" /> + + <xsl:template match="/"> + <xsl:apply-templates select="/Config/Cards/Card"/> + </xsl:template> + + <xsl:template match="/Config/Cards/Card"> + <xsl:text>cat ></xsl:text> + <xsl:value-of select="translate(@MacAddress,'-','')"/> + <xsl:text>.conf <<__EOF__
</xsl:text> + <xsl:text>uploadkey="</xsl:text> + <xsl:value-of select="UploadKey"/> + <xsl:text>"
</xsl:text> + <xsl:text>targetdir="/var/lib/iii/%s"
</xsl:text> + <xsl:text>umask=022
</xsl:text> + <xsl:text>__EOF__
</xsl:text> + </xsl:template> + + <xsl:template match="*|text()"/> + +</xsl:stylesheet> 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; +} + |