--- /dev/null
+Maintainer, developer, documenter, etc.:
+Fredrik Tolf <fredrik@dolda2000.com>
--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+\f
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 19yy <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
--- /dev/null
+/AUTHORS/1.1.1.1/Tue May 11 15:45:57 2004//
+/COPYING/1.1.1.1/Tue May 11 15:46:34 2004//
+/ChangeLog/1.4/Sat Jul 9 03:42:47 2005//
+/INSTALL/1.1.1.1/Tue May 11 15:46:34 2004//
+/Makefile.am/1.4/Sat Jul 9 03:38:28 2005//
+/NEWS/1.1.1.1/Tue May 11 15:46:45 2004//
+/README/1.1.1.1/Tue May 11 15:46:45 2004//
+/bootstrap/1.2/Sat Jul 9 03:17:49 2005//
+/configure.in/1.19/Sun Oct 9 15:28:57 2005//
+D/autopackage////
+D/clients////
+D/config////
+D/daemon////
+D/include////
+D/lib////
+D/po////
--- /dev/null
+doldaconnect
--- /dev/null
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
--- /dev/null
+2005-07-09 Fredrik Tolf <fredrik@dolda2000.com>
+
+ * Cleaned up CVS heavily.
+
+2004-08-13 gettextize <bug-gnu-gettext@gnu.org>
+
+ * Makefile.am (SUBDIRS): Add m4.
+ (ACLOCAL_AMFLAGS): New variable.
+ (EXTRA_DIST): New variable.
+ * configure.in (AC_OUTPUT): Add po/Makefile.in,
+
+2004-05-11 dolda2000 <dolda2000@pc7.dolda2000.com>
+
+ * utils.h: Removed the format warnings, since they didn't work with %N.
+
+ * transfer.h, transfer.c, fnet-dc.c: Transfer system rewrite.
+
+ * net.c, net.h, sysevents.h, transfer.c, transfer.h, utils.c:
+ Initial import.
+
+ * net.c, net.h, sysevents.h, transfer.c, transfer.h, utils.c: New file.
+
+ * filenet.h, log.c, module.h: Initial import.
+
+ * filenet.h, log.c, module.h: New file.
+
+ * conf.h, fnet-dc.c, log.h: Initial import.
+
+ * conf.h, fnet-dc.c, log.h: New file.
+
+ * client.c, client.h, conf.c, filenet.c: Initial import.
+
+ * client.c, client.h, conf.c, filenet.c: New file.
+
+ * auth-pam.c: Initial import.
+
+ * auth-pam.c: New file.
+
+ * auth.c, auth.h: Initial import.
+
+ * auth.c, auth.h: New file.
+
+ * Makefile: Initial import.
+
+ * Makefile: New file.
+
+ * Makefile.in: Initial import.
+
+ * Makefile.in: New file.
+
+ * ui.c, uiretref, utils.h: Initial import.
+
+ * ui.c, uiretref, utils.h: New file.
+
+ * main.c: Initial import.
+
+ * main.c: New file.
+
+ * Makefile.am, emacs-local: Initial import.
+
+ * Makefile.am, emacs-local: New file.
+
--- /dev/null
+Basic Installation
+==================
+
+ These are generic installation instructions.
+
+ The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation. It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions. Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, a file
+`config.cache' that saves the results of its tests to speed up
+reconfiguring, and a file `config.log' containing compiler output
+(useful mainly for debugging `configure').
+
+ If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release. If at some point `config.cache'
+contains results you don't want to keep, you may remove or edit it.
+
+ The file `configure.in' is used to create `configure' by a program
+called `autoconf'. You only need `configure.in' if you want to change
+it or regenerate `configure' using a newer version of `autoconf'.
+
+The simplest way to compile this package is:
+
+ 1. `cd' to the directory containing the package's source code and type
+ `./configure' to configure the package for your system. If you're
+ using `csh' on an old version of System V, you might need to type
+ `sh ./configure' instead to prevent `csh' from trying to execute
+ `configure' itself.
+
+ Running `configure' takes awhile. While running, it prints some
+ messages telling which features it is checking for.
+
+ 2. Type `make' to compile the package.
+
+ 3. Optionally, type `make check' to run any self-tests that come with
+ the package.
+
+ 4. Type `make install' to install the programs and any data files and
+ documentation.
+
+ 5. You can remove the program binaries and object files from the
+ source code directory by typing `make clean'. To also remove the
+ files that `configure' created (so you can compile the package for
+ a different kind of computer), type `make distclean'. There is
+ also a `make maintainer-clean' target, but that is intended mainly
+ for the package's developers. If you use it, you may have to get
+ all sorts of other programs in order to regenerate files that came
+ with the distribution.
+
+Compilers and Options
+=====================
+
+ Some systems require unusual options for compilation or linking that
+the `configure' script does not know about. You can give `configure'
+initial values for variables by setting them in the environment. Using
+a Bourne-compatible shell, you can do that on the command line like
+this:
+ CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure
+
+Or on systems that have the `env' program, you can do it like this:
+ env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure
+
+Compiling For Multiple Architectures
+====================================
+
+ You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory. To do this, you must use a version of `make' that
+supports the `VPATH' variable, such as GNU `make'. `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script. `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+ If you have to use a `make' that does not supports the `VPATH'
+variable, you have to compile the package for one architecture at a time
+in the source code directory. After you have installed the package for
+one architecture, use `make distclean' before reconfiguring for another
+architecture.
+
+Installation Names
+==================
+
+ By default, `make install' will install the package's files in
+`/usr/local/bin', `/usr/local/man', etc. You can specify an
+installation prefix other than `/usr/local' by giving `configure' the
+option `--prefix=PATH'.
+
+ You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files. If you
+give `configure' the option `--exec-prefix=PATH', the package will use
+PATH as the prefix for installing programs and libraries.
+Documentation and other data files will still use the regular prefix.
+
+ In addition, if you use an unusual directory layout you can give
+options like `--bindir=PATH' to specify different values for particular
+kinds of files. Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them.
+
+ If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+ Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System). The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+ For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+ There may be some features `configure' can not figure out
+automatically, but needs to determine by the type of host the package
+will run on. Usually `configure' can figure that out, but if it prints
+a message saying it can not guess the host type, give it the
+`--host=TYPE' option. TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name with three fields:
+ CPU-COMPANY-SYSTEM
+
+See the file `config.sub' for the possible values of each field. If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the host type.
+
+ If you are building compiler tools for cross-compiling, you can also
+use the `--target=TYPE' option to select the type of system they will
+produce code for and the `--build=TYPE' option to select the type of
+system on which you are compiling the package.
+
+Sharing Defaults
+================
+
+ If you want to set default values for `configure' scripts to share,
+you can create a site shell script called `config.site' that gives
+default values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists. Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Operation Controls
+==================
+
+ `configure' recognizes the following options to control how it
+operates.
+
+`--cache-file=FILE'
+ Use and save the results of the tests in FILE instead of
+ `./config.cache'. Set FILE to `/dev/null' to disable caching, for
+ debugging `configure'.
+
+`--help'
+ Print a summary of the options to `configure', and exit.
+
+`--quiet'
+`--silent'
+`-q'
+ Do not print messages saying which checks are being made. To
+ suppress all normal output, redirect it to `/dev/null' (any error
+ messages will still be shown).
+
+`--srcdir=DIR'
+ Look for the package's source code in directory DIR. Usually
+ `configure' can determine that directory automatically.
+
+`--version'
+ Print the version of Autoconf used to generate the `configure'
+ script, and exit.
+
+`configure' also accepts some other, not widely useful, options.
--- /dev/null
+SUBDIRS= daemon lib include clients po config
+
+ACLOCAL_AMFLAGS = -I m4
+
+EXTRA_DIST = config.rpath
--- /dev/null
+/dolcon.apspec.in/1.2/Tue Jul 12 02:05:28 2005//
+D
--- /dev/null
+doldaconnect/autopackage
--- /dev/null
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
--- /dev/null
+# -*-shell-script-*-
+
+[Meta]
+RootName: @dolda2000.com/~fredrik/doldaconnect/dolcon:$SOFTWAREVERSION
+DisplayName: Dolda Connect GTK 2 user interface
+ShortName: doldaconnect-gtk
+Maintainer: Fredrik Tolf <fredrik@dolda2000.com>
+Packager: Fredrik Tolf <fredrik@dolda2000.com>
+Summary: A user interface module for Dolda Connect using GTK 2.
+URL: http://www.dolda2000.com/~fredrik/doldaconnect/
+License: GNU General Public License, Version 2
+SoftwareVersion: @VERSION@
+AutopackageTarget: 1.0
+
+# Only uncomment InterfaceVersion if your package exposes interfaces to other software,
+# for instance if it includes DSOs or python/perl modules. See the developer guide for more info,
+# or ask on autopackage-dev if you don't understand interface versioning in autopackage.
+#
+# InterfaceVersion: 0.0
+
+[Description]
+This is a user interface program for Dolda Connect. It connects to the
+Dolda Connect daemon and lets a user control it. This user interface
+is written with GTK 2.
+
+Note that this program does not share files or anything of the sort --
+it is the daemon that does that. This program only controls the
+daemon.
+
+[BuildPrepare]
+prepareBuild --enable-gtk2ui --disable-gnomeapplet
+
+[BuildUnprepare]
+unprepareBuild
+
+[Imports]
+echo '*' | import
+
+[Prepare]
+require @gtk.org/gtk 2.0
+require @dolda2000.com/~fredrik/doldaconnect/dcuilib
+
+[Install]
+installExe bin/dolcon
+installLocale share/locale
+
+[Uninstall]
+uninstallFromLog
--- /dev/null
+#! /bin/sh
+
+autopoint && aclocal$AUTOMAKE_VERSION -I m4 && autoheader && libtoolize --copy --force && automake$AUTOMAKE_VERSION --add-missing --copy && autoconf
--- /dev/null
+/Makefile.am/1.6/Thu Dec 30 02:47:05 2004//
+/hellodolda.jpg/1.1/Sun Dec 26 23:48:43 2004/-ko/
+/test.c/1.1.1.1/Tue May 11 15:46:45 2004//
+D/gnome-trans-applet////
+D/gtk2////
--- /dev/null
+doldaconnect/clients
--- /dev/null
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
--- /dev/null
+SUBDIRS=@clients@
+DIST_SUBDIRS=gtk2 gnome-trans-applet
+EXTRA_DIST=hellodolda.jpg
+
+iconsdir = $(datadir)/pixmaps
+icons_DATA = hellodolda.jpg
+
+noinst_PROGRAMS=test
+
+test_SOURCES=test.c
+
+test_LDADD=$(top_srcdir)/lib/libdcui.la
+AM_CPPFLAGS=-I$(top_srcdir)/include
--- /dev/null
+/Dolcon_Transferapplet_Factory.server.in/1.2/Thu Dec 30 01:11:03 2004//
+/Makefile.am/1.5/Sat Jan 1 17:39:45 2005//
+/conduit-dclib.c/1.4/Tue May 10 00:20:16 2005//
+/conduit-pipe.c/1.4/Fri Dec 31 12:35:38 2004//
+/conduit.c/1.3/Thu Dec 30 02:30:49 2004//
+/conduit.h/1.4/Sat Jan 1 17:39:11 2005//
+/dctrmon/1.2/Mon Jan 24 12:07:16 2005//
+/dolcon-trans-applet.c/1.5/Tue Oct 11 20:23:48 2005//
+D
--- /dev/null
+doldaconnect/clients/gnome-trans-applet
--- /dev/null
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
--- /dev/null
+<oaf_info>
+ <oaf_server iid="OAFIID:Dolcon_Transferapplet_Factory" type="exe" location="@LIBEXECDIR@/dolcon-trans-applet">
+ <oaf_attribute name="repo_ids" type="stringv">
+ <item value="IDL:Bonobo/GenericFactory:1.0" />
+ <item value="IDL:Bonobo/Unknown:1.0" />
+ </oaf_attribute>
+ <oaf_attribute name="name" type="string" value="Doldaconnect Transfer Viewer Factory" />
+ <oaf_attribute name="description" type="string" value="Factory for creating Doldaconnect Transfer Viewer applets" />
+ </oaf_server>
+
+ <oaf_server iid="OAFIID:Dolcon_Transferapplet" type="factory" location="OAFIID:Dolcon_Transferapplet_Factory">
+ <oaf_attribute name="repo_ids" type="stringv">
+ <item value="IDL:GNOME/Vertigo/PanelAppletShell:1.0" />
+ <item value="IDL:Bonobo/Control:1.0" />
+ <item value="IDL:Bonobo/Unknown:1.0" />
+ </oaf_attribute>
+ <oaf_attribute name="name" type="string" value="Doldaconnect Transfer Viewer" />
+ <oaf_attribute name="description" type="string" value="Easily grasp the current status of your Doldaconnect transfers" />
+ <oaf_attribute name="panel:icon" type="string" value="hellodolda.jpg" />
+ </oaf_server>
+</oaf_info>
--- /dev/null
+libexec_PROGRAMS=dolcon-trans-applet
+
+dolcon_trans_applet_SOURCES= dolcon-trans-applet.c \
+ conduit-pipe.c \
+ conduit-dclib.c \
+ conduit.c \
+ conduit.h
+
+localedir=$(datadir)/locale
+dolcon_trans_applet_LDFLAGS=$(shell pkg-config --libs libpanelapplet-2.0)
+dolcon_trans_applet_LDADD=$(top_srcdir)/lib/libdcui.la
+dolcon_trans_applet_CPPFLAGS=$(shell pkg-config --cflags libpanelapplet-2.0) -DLOCALEDIR=\"$(localedir)\"
+
+BUILT_SOURCES=Dolcon_Transferapplet_Factory.server
+
+serverdir=$(libdir)/bonobo/servers
+server_DATA=Dolcon_Transferapplet_Factory.server
+
+EXTRA_DIST=Dolcon_Transferapplet_Factory.server.in
+
+%.server: %.server.in
+ sed -e "s|\@LIBEXECDIR\@|$(libexecdir)|" $< > $@
--- /dev/null
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <panel-applet.h>
+#include <doldaconnect/uilib.h>
+#include <doldaconnect/uimisc.h>
+#include <doldaconnect/utils.h>
+
+#include "conduit.h"
+
+struct data
+{
+ int fd;
+ int gdkread, gdkwrite;
+};
+
+struct dtdata
+{
+ struct conduit *conduit;
+ struct transfer *ct;
+ char *tag;
+ int realtag;
+};
+
+static struct conduit *inuse = NULL;
+
+static void dcfdcb(struct conduit *conduit, int fd, GdkInputCondition condition);
+
+static void updatewrite(struct conduit *conduit)
+{
+ struct data *data;
+
+ data = conduit->cdata;
+ if(data->fd < 0)
+ return;
+ if(dc_wantwrite())
+ {
+ if(data->gdkwrite == -1)
+ data->gdkwrite = gdk_input_add(data->fd, GDK_INPUT_WRITE, (void (*)(gpointer, int, GdkInputCondition))dcfdcb, conduit);
+ } else {
+ if(data->gdkwrite != -1)
+ {
+ gdk_input_remove(data->gdkwrite);
+ data->gdkwrite = -1;
+ }
+ }
+}
+
+static void disconnected(struct conduit *conduit)
+{
+ struct data *data;
+
+ data = conduit->cdata;
+ if(inuse == conduit)
+ inuse = NULL;
+ if(data->gdkread != -1)
+ {
+ gdk_input_remove(data->gdkread);
+ data->gdkread = -1;
+ }
+ if(data->gdkwrite != -1)
+ {
+ gdk_input_remove(data->gdkwrite);
+ data->gdkwrite = -1;
+ }
+ data->fd = -1;
+ conddisconn(conduit);
+}
+
+static int noconv(int type, wchar_t *text, char **resp, void *data)
+{
+ return(1);
+}
+
+static char *gettag(struct dc_transfer *dt)
+{
+ char *mbspath, *p, *buf;
+
+ if(dt->path == NULL)
+ return(NULL);
+ if((mbspath = icwcstombs(dt->path, "UTF-8")) == NULL)
+ return(NULL);
+ /* XXX: Achtung! Too DC-specific! */
+ if((p = strrchr(mbspath, '\\')) == NULL)
+ p = mbspath;
+ else
+ p++;
+ buf = sstrdup(p);
+ free(mbspath);
+ return(buf);
+}
+
+static void dtfreecb(struct dc_transfer *dt)
+{
+ struct dtdata *dtd;
+
+ if((dtd = dt->udata) == NULL)
+ return;
+ if(dtd->ct != NULL)
+ freetransfer(dtd->ct);
+ if(dtd->tag != NULL)
+ free(dtd->tag);
+ free(dtd);
+}
+
+static int lstrargcb(struct dc_response *resp)
+{
+ struct dc_transfer *dt;
+ struct dtdata *dtd;
+ struct dc_intresp *ires;
+
+ dt = resp->data;
+ dtd = dt->udata;
+ if(resp->code == 200)
+ {
+ while((dtd->tag == NULL) && ((ires = dc_interpret(resp)) != NULL))
+ {
+ if(!wcscmp(ires->argv[0].val.str, L"tag"))
+ {
+ dtd->realtag = 1;
+ dtd->tag = icwcstombs(ires->argv[1].val.str, "UTF-8");
+ }
+ dc_freeires(ires);
+ }
+ }
+ if(dtd->tag == NULL)
+ dtd->tag = gettag(dt);
+ dtd->ct = newtransfer(dtd->conduit, dtd->tag, dt->size, dt->curpos);
+ return(1);
+}
+
+static void inittrans(struct conduit *conduit, struct dc_transfer *dt)
+{
+ struct dtdata *dtd;
+
+ dtd = smalloc(sizeof(*dtd));
+ memset(dtd, 0, sizeof(*dtd));
+ dtd->conduit = conduit;
+ dt->udata = dtd;
+ dt->destroycb = dtfreecb;
+ dc_queuecmd(lstrargcb, dt, L"lstrarg", L"%%i", dt->id, NULL);
+}
+
+static void trlistcb(int resp, struct conduit *conduit)
+{
+ struct data *data;
+ struct dc_transfer *dt;
+
+ data = conduit->cdata;
+ if(resp != 200)
+ return;
+ for(dt = dc_transfers; dt != NULL; dt = dt->next)
+ {
+ if(dt->dir != DC_TRNSD_DOWN)
+ continue;
+ inittrans(conduit, dt);
+ }
+}
+
+static void logincb(int err, wchar_t *reason, struct conduit *conduit)
+{
+ struct data *data;
+
+ data = conduit->cdata;
+ if(err != DC_LOGIN_ERR_SUCCESS)
+ {
+ dc_disconnect();
+ disconnected(conduit);
+ return;
+ }
+ condconnected(conduit);
+ dc_gettrlistasync((void (*)(int, void *))trlistcb, conduit);
+ dc_queuecmd(NULL, NULL, L"notify", L"trans:act", L"on", L"trans:prog", L"on", NULL);
+}
+
+static void dcfdcb(struct conduit *conduit, int fd, GdkInputCondition condition)
+{
+ struct data *data;
+ struct dc_response *resp;
+ struct dc_intresp *ires;
+ struct dc_transfer *dt;
+ struct dtdata *dtd;
+
+ data = conduit->cdata;
+ if(((condition & GDK_INPUT_READ) && dc_handleread()) || ((condition & GDK_INPUT_WRITE) && dc_handlewrite()))
+ {
+ disconnected(conduit);
+ return;
+ }
+ while((resp = dc_getresp()) != NULL)
+ {
+ if(!wcscmp(resp->cmdname, L".connect"))
+ {
+ if(resp->code == 200)
+ {
+ dc_loginasync(NULL, 1, noconv, (void (*)(int, wchar_t *, void *))logincb, conduit);
+ } else {
+ dc_disconnect();
+ disconnected(conduit);
+ }
+ } else if(!wcscmp(resp->cmdname, L".notify")) {
+ dc_uimisc_handlenotify(resp);
+ switch(resp->code)
+ {
+ case 610:
+ if((ires = dc_interpret(resp)) != NULL)
+ {
+ if((dt = dc_findtransfer(ires->argv[0].val.num)) != NULL)
+ {
+ if(dt->dir == DC_TRNSD_DOWN)
+ inittrans(conduit, dt);
+ }
+ dc_freeires(ires);
+ }
+ break;
+ case 613:
+ if((ires = dc_interpret(resp)) != NULL)
+ {
+ if((dt = dc_findtransfer(ires->argv[0].val.num)) != NULL)
+ {
+ if(((dtd = dt->udata) != NULL) && (dtd->ct != NULL))
+ {
+ if(dtd->ct->size != dt->size)
+ transfersetsize(dtd->ct, dt->size);
+ }
+ }
+ dc_freeires(ires);
+ }
+ break;
+ case 615:
+ if((ires = dc_interpret(resp)) != NULL)
+ {
+ if((dt = dc_findtransfer(ires->argv[0].val.num)) != NULL)
+ {
+ if(((dtd = dt->udata) != NULL) && (dtd->ct != NULL))
+ {
+ if(dtd->ct->pos != dt->curpos)
+ transfersetpos(dtd->ct, dt->curpos);
+ }
+ }
+ dc_freeires(ires);
+ }
+ break;
+ }
+ }
+ dc_freeresp(resp);
+ }
+ updatewrite(conduit);
+}
+
+static int init(struct conduit *conduit)
+{
+ static int inited = 0;
+ struct data *data;
+
+ if(!inited)
+ {
+ dc_init();
+ inited = 1;
+ }
+ data = smalloc(sizeof(*data));
+ memset(data, 0, sizeof(*data));
+ data->fd = -1;
+ data->gdkread = data->gdkwrite = -1;
+ conduit->cdata = data;
+ return(0);
+}
+
+static int connect(struct conduit *conduit)
+{
+ struct data *data;
+ char *host;
+
+ data = conduit->cdata;
+ if(inuse != NULL)
+ return(-1);
+ if((host = getenv("DCSERVER")) == NULL)
+ host = "localhost";
+ if((data->fd = dc_connect(host, -1)) < 0)
+ return(-1);
+ data->gdkread = gdk_input_add(data->fd, GDK_INPUT_READ, (void (*)(gpointer, int, GdkInputCondition))dcfdcb, conduit);
+ updatewrite(conduit);
+ inuse = conduit;
+ return(0);
+}
+
+static void destroy(struct conduit *conduit)
+{
+ struct data *data;
+
+ data = conduit->cdata;
+ if(data->gdkread != -1)
+ gdk_input_remove(data->gdkread);
+ if(data->gdkwrite != -1)
+ gdk_input_remove(data->gdkwrite);
+ if(data->fd >= 0)
+ dc_disconnect();
+ if(inuse == conduit)
+ inuse = NULL;
+ free(data);
+}
+
+static struct conduitiface st_conduit_dclib =
+{
+ .init = init,
+ .connect = connect,
+ .destroy = destroy,
+};
+
+struct conduitiface *conduit_dclib = &st_conduit_dclib;
--- /dev/null
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <panel-applet.h>
+#include <doldaconnect/utils.h>
+
+#include "conduit.h"
+
+#define SUBPROCCMD "ksu fredrik -q -e /home/fredrik/bin/dctrmon"
+
+struct data
+{
+ pid_t subproc;
+ int fd;
+ int gdktag;
+ char *inbuf;
+ size_t inbufsize, inbufdata;
+};
+
+static void pipefdcb(struct conduit *conduit, int fd, GdkInputCondition condition)
+{
+ struct data *data;
+ int ret;
+ char *p, *p2, *cmd;
+ char **args;
+ size_t argssize, argsdata;
+ struct transfer *transfer;
+
+ data = (struct data *)conduit->cdata;
+ if(conduit->state == CNDS_SYN)
+ condconnected(conduit);
+ sizebuf2(data->inbuf, data->inbufdata + 80, 1);
+ ret = read(data->fd, data->inbuf + data->inbufdata, data->inbufsize - data->inbufdata);
+ if(ret < 0)
+ {
+ if((errno == EINTR) || (errno == EAGAIN)) /* Shouldn't happen, but, oh well... */
+ return;
+ perror("conduit-pipe: read");
+ gdk_input_remove(data->gdktag);
+ close(data->fd);
+ kill(-data->subproc, SIGHUP);
+ data->gdktag = -1;
+ data->fd = -1;
+ data->subproc = 0;
+ conddisconn(conduit);
+ }
+ if(ret == 0)
+ {
+ gdk_input_remove(data->gdktag);
+ close(data->fd);
+ kill(-data->subproc, SIGHUP);
+ data->gdktag = -1;
+ data->fd = -1;
+ data->subproc = 0;
+ conddisconn(conduit);
+ }
+ data->inbufdata += ret;
+ while((p = memchr(data->inbuf, '\n', data->inbufdata)) != NULL)
+ {
+ *p = 0;
+ cmd = sstrdup(data->inbuf);
+ memmove(data->inbuf, p + 1, data->inbufdata -= (p - data->inbuf) + 1);
+ args = NULL;
+ argssize = argsdata = 0;
+ p = cmd;
+ do
+ {
+ if((p2 = strchr(p, '\t')) != NULL)
+ *(p2++) = 0;
+ addtobuf(args, p);
+ p = p2;
+ } while(p2 != NULL);
+ if(!strcmp(args[0], "N") && (argsdata >= 4))
+ {
+ transfer = newtransfer(conduit, args[1], atoi(args[2]), atoi(args[3]));
+ }
+ if(!strcmp(args[0], "D") && (argsdata >= 2))
+ {
+ if((transfer = findtransferbytag(conduit, args[1])) != NULL)
+ freetransfer(transfer);
+ }
+ if(!strcmp(args[0], "S") && (argsdata >= 3))
+ {
+ if((transfer = findtransferbytag(conduit, args[1])) != NULL)
+ transfersetsize(transfer, atoi(args[2]));
+ }
+ if(!strcmp(args[0], "P") && (argsdata >= 3))
+ {
+ if((transfer = findtransferbytag(conduit, args[1])) != NULL)
+ transfersetpos(transfer, atoi(args[2]));
+ }
+ free(args);
+ free(cmd);
+ }
+}
+
+static int init(struct conduit *conduit)
+{
+ static int inited = 0;
+ struct data *data;
+
+ if(!inited)
+ {
+ signal(SIGCHLD, SIG_IGN);
+ inited = 1;
+ }
+ data = smalloc(sizeof(*data));
+ memset(data, 0, sizeof(*data));
+ data->fd = -1;
+ data->inbuf = NULL;
+ data->inbufsize = data->inbufdata = 0;
+ data->gdktag = -1;
+ conduit->cdata = data;
+ return(0);
+}
+
+static int connect(struct conduit *conduit)
+{
+ struct data *data;
+ pid_t pid;
+ int pfd[2];
+
+ data = conduit->cdata;
+ if(pipe(pfd))
+ return(-1);
+ if((pid = fork()) < 0)
+ {
+ close(pfd[0]);
+ close(pfd[1]);
+ return(-1);
+ }
+ if(!pid)
+ {
+ int devnull;
+
+ setpgrp();
+ if((devnull = open("/dev/null", O_RDWR)) < 0)
+ exit(127);
+ close(pfd[0]);
+ dup2(pfd[1], 1);
+ close(pfd[1]);
+ dup2(devnull, 0);
+ close(devnull);
+ /* Leave stderr as is */
+ execl("/bin/sh", "sh", "-c", SUBPROCCMD, NULL);
+ exit(127);
+ }
+ close(pfd[1]);
+ fcntl(pfd[0], F_SETFL, fcntl(pfd[0], F_GETFL) | O_NONBLOCK);
+ data->subproc = pid;
+ data->fd = pfd[0];
+ data->gdktag = gdk_input_add(pfd[0], GDK_INPUT_READ, (void (*)(gpointer, gint, GdkInputCondition))pipefdcb, conduit);
+ data->inbufdata = 0;
+ return(0);
+}
+
+static void destroy(struct conduit *conduit)
+{
+ struct data *data;
+
+ data = conduit->cdata;
+ if(data == NULL)
+ return;
+ if(data->gdktag >= 0)
+ gdk_input_remove(data->gdktag);
+ if(data->subproc > 0)
+ kill(-data->subproc, SIGHUP);
+ if(data->fd >= 0)
+ close(data->fd);
+ if(data->inbuf != NULL)
+ free(data->inbuf);
+ free(data);
+ conduit->cdata = NULL;
+}
+
+static struct conduitiface st_conduit_pipe =
+{
+ .init = init,
+ .connect = connect,
+ .destroy = destroy,
+};
+
+struct conduitiface *conduit_pipe = &st_conduit_pipe;
--- /dev/null
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <doldaconnect/utils.h>
+#include <panel-applet.h>
+#include <time.h>
+
+#include "conduit.h"
+
+void (*cb_condstate)(struct conduit *conduit, void *data) = NULL;
+void (*cb_trsize)(struct transfer *transfer, void *data) = NULL;
+void (*cb_trpos)(struct transfer *transfer, void *data) = NULL;
+void (*cb_trnew)(struct transfer *transfer, void *data) = NULL;
+void (*cb_trfree)(struct transfer *transfer, void *data) = NULL;
+
+struct transfer *findtransferbytag(struct conduit *conduit, char *tag)
+{
+ struct transfer *transfer;
+
+ for(transfer = conduit->transfers; transfer != NULL; transfer = transfer->next)
+ {
+ if((transfer->tag != NULL) && !strcmp(transfer->tag, tag))
+ break;
+ }
+ return(transfer);
+}
+
+void transfersetsize(struct transfer *transfer, int size)
+{
+ transfer->size = size;
+ cb_trsize(transfer, transfer->conduit->udata);
+}
+
+void transfersetpos(struct transfer *transfer, int pos)
+{
+ transfer->pos = pos;
+ cb_trpos(transfer, transfer->conduit->udata);
+}
+
+static gboolean trupdatetime(struct transfer *transfer)
+{
+ time_t now;
+
+ if((transfer->size == -1) || (transfer->pos == -1))
+ return(TRUE);
+ now = time(NULL);
+ if(now - transfer->ckptime >= 10)
+ {
+ transfer->cmptime = transfer->ckptime;
+ transfer->cmpsize = transfer->ckpsize;
+ transfer->ckptime = 0;
+ }
+ if(transfer->ckptime == 0)
+ {
+ transfer->ckptime = now;
+ transfer->ckpsize = transfer->pos;
+ }
+ return(TRUE);
+}
+
+struct transfer *newtransfer(struct conduit *conduit, char *tag, int size, int pos)
+{
+ struct transfer *transfer;
+
+ transfer = smalloc(sizeof(*transfer));
+ memset(transfer, 0, sizeof(*transfer));
+ if(tag != NULL)
+ transfer->tag = sstrdup(tag);
+ transfer->size = size;
+ transfer->pos = pos;
+ transfer->timeout = g_timeout_add(1000, (gboolean (*)(gpointer))trupdatetime, transfer);
+ transfer->next = conduit->transfers;
+ transfer->conduit = conduit;
+ if(conduit->transfers != NULL)
+ conduit->transfers->prev = transfer;
+ conduit->transfers = transfer;
+ cb_trnew(transfer, conduit->udata);
+ return(transfer);
+}
+
+void freetransfer(struct transfer *transfer)
+{
+ if(transfer->next != NULL)
+ transfer->next->prev = transfer->prev;
+ if(transfer->prev != NULL)
+ transfer->prev->next = transfer->next;
+ if(transfer->conduit->transfers == transfer)
+ transfer->conduit->transfers = transfer->next;
+ cb_trfree(transfer, transfer->conduit->udata);
+ g_source_remove(transfer->timeout);
+ if(transfer->tag != NULL)
+ free(transfer->tag);
+ free(transfer);
+}
+
+struct conduit *newconduit(struct conduitiface *iface, void *udata)
+{
+ struct conduit *conduit;
+
+ conduit = smalloc(sizeof(*conduit));
+ memset(conduit, 0, sizeof(*conduit));
+ conduit->iface = iface;
+ conduit->udata = udata;
+ if(iface->init(conduit))
+ {
+ free(conduit);
+ return(NULL);
+ }
+ return(conduit);
+}
+
+void freeconduit(struct conduit *conduit)
+{
+ conduit->iface->destroy(conduit);
+ while(conduit->transfers != NULL)
+ freetransfer(conduit->transfers);
+ free(conduit);
+}
+
+int condtryconn(struct conduit *conduit)
+{
+ if(conduit->state != CNDS_IDLE)
+ return(-1);
+ if(conduit->iface->connect(conduit))
+ return(-1);
+ conduit->state = CNDS_SYN;
+ return(0);
+}
+
+void conddisconn(struct conduit *conduit)
+{
+ while(conduit->transfers != NULL)
+ freetransfer(conduit->transfers);
+ conduit->state = CNDS_IDLE;
+ cb_condstate(conduit, conduit->udata);
+}
+
+void condconnected(struct conduit *conduit)
+{
+ conduit->state = CNDS_EST;
+ cb_condstate(conduit, conduit->udata);
+}
--- /dev/null
+#ifndef _CONDUIT_H
+#define _CONDUIT_H
+
+#include <sys/types.h>
+
+#define CNDS_IDLE 0
+#define CNDS_SYN 1
+#define CNDS_EST 2
+
+struct transfer
+{
+ struct transfer *next, *prev;
+ struct conduit *conduit;
+ char *tag; /* UTF8 */
+ int pos, size;
+ time_t cmptime, ckptime;
+ size_t cmpsize, ckpsize;
+ int timeout;
+};
+
+struct conduit
+{
+ struct transfer *transfers;
+ struct conduitiface *iface;
+ void *cdata, *udata;
+ int state;
+};
+
+struct conduitiface
+{
+ int (*init)(struct conduit *conduit);
+ int (*connect)(struct conduit *conduit);
+ void (*destroy)(struct conduit *conduit);
+};
+
+struct transfer *findtransferbytag(struct conduit *conduit, char *tag);
+void transfersetsize(struct transfer *transfer, int size);
+void transfersetpos(struct transfer *transfer, int pos);
+struct transfer *newtransfer(struct conduit *conduit, char *tag, int size, int pos);
+void freetransfer(struct transfer *transfer);
+struct conduit *newconduit(struct conduitiface *iface, void *udata);
+void freeconduit(struct conduit *conduit);
+int condtryconn(struct conduit *conduit);
+void conddisconn(struct conduit *conduit);
+void condconnected(struct conduit *conduit);
+
+extern void (*cb_condstate)(struct conduit *conduit, void *data);
+extern void (*cb_trsize)(struct transfer *transfer, void *data);
+extern void (*cb_trpos)(struct transfer *transfer, void *data);
+extern void (*cb_trnew)(struct transfer *transfer, void *data);
+extern void (*cb_trfree)(struct transfer *transfer, void *data);
+extern struct conduitiface *conduit_pipe;
+extern struct conduitiface *conduit_dclib;
+
+#endif
--- /dev/null
+#!/usr/bin/guile \
+--debug -s
+!#
+
+(use-modules (dolcon ui))
+(use-modules (ice-9 popen))
+(use-modules (ice-9 pretty-print))
+
+(define (flush port)
+ (force-output port))
+
+(define idlist '())
+(define filter '())
+(define (filtered tag filter)
+ (and (pair? filter)
+ (or (equal? (car filter) (substring tag 0 (min (string-length (car filter)) (string-length tag))))
+ (filtered tag (cdr filter)))))
+(catch 'system-error
+ (lambda ()
+ (let ((port (open-input-file (string-append (getenv "HOME") "/.dctrmon-defines"))) (form #f))
+ (while (begin (set! form (read port)) (not (eof-object? form)))
+ (primitive-eval form))))
+ (lambda args
+ #f))
+
+
+(define krbcc (string-append "/tmp/krb5cc_dcmon_" (number->string (getuid)) "_XXXXXX"))
+(close-port (mkstemp! krbcc))
+(setenv "KRB5CCNAME" (string-append "FILE:" krbcc))
+(sigaction SIGCHLD SIG_DFL)
+(define pid (primitive-fork))
+(if (= pid 0)
+ (begin (execlp "kinit" "kinit" "-f" "-r" "10d" "-k" "-t" (string-append (getenv "HOME") "/.myprinc.keytab") (string-append (passwd:name (getpwuid (getuid))) "/dcview"))
+ (exit 1))
+ (if (not (= (cdr (waitpid pid)) 0))
+ (exit 1)))
+(dc-c&l #f (getenv "DCSERVER") #t)
+(delete-file krbcc)
+
+(dc-ecmd-assert 200 "notify" "all" "on")
+
+(display "C\n")
+
+(let ((resp (dc-ecmd-assert '(200 201) "lstrans")))
+ (if (and resp (= (cdr (assoc 'code (dc-extract resp))) 200))
+ (for-each (lambda (o)
+ (if (= (cadr o) 2)
+ (catch 'bad-return
+ (lambda ()
+ (for-each (lambda (a)
+ (if (and (equal? (car a) "tag") (filtered (cadr a) filter))
+ (begin
+ (display (string-append "N\t" (cadr a) "\t" (number->string (list-ref o 6)) "\t" (number->string (list-ref o 7)) "\n"))
+ (set! idlist (append idlist (list (cons (car o) (cadr a))))))))
+ (dc-intall (dc-ecmd-assert 200 "lstrarg" (car o)))))
+ (lambda args #f))))
+ (dc-intall resp))))
+
+(flush (current-output-port))
+
+(while #t
+ (dc-select 10000)
+ (while (let ((resp (dc-getresp)))
+ (if resp
+ (begin
+ (let* ((er (dc-extract resp)) (code (cdr (assoc 'code er))) (cmd (cdr (assoc 'cmd er))) (ir (dc-intresp resp)))
+ (if (equal? cmd ".notify")
+ (case code
+ ((610)
+ (let* ((id (car ir)) (ir2 (dc-intall (dc-ecmd-assert '(200 201) "lstrarg" id))) (tag (if (eq? (car ir2) '()) #f (assoc "tag" ir2))))
+ (if (and tag (filtered (cadr tag) filter))
+ (begin (display (string-append "N\t" (cadr tag) "\t-1\t-1\n"))
+ (flush (current-output-port))
+ (set! idlist (append idlist (list (cons (car ir) (cadr tag)))))))))
+ ((613)
+ (let ((id (car ir)))
+ (if (assoc id idlist)
+ (begin (display (string-append "S\t" (cdr (assoc id idlist)) "\t" (number->string (cadr ir)) "\n"))
+ (flush (current-output-port))))))
+ ((615)
+ (let ((id (car ir)))
+ (if (assoc id idlist)
+ (begin (display (string-append "P\t" (cdr (assoc id idlist)) "\t" (number->string (cadr ir)) "\n"))
+ (flush (current-output-port))))))
+ ((617)
+ (let ((id (car ir)))
+ (if (assoc id idlist)
+ (begin (display (string-append "D\t" (cdr (assoc id idlist)) "\n"))
+ (flush (current-output-port)))))))))
+ #t)
+ #f)) #f))
+
+(dc-disconnect)
--- /dev/null
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <string.h>
+#include <doldaconnect/uilib.h>
+#include <doldaconnect/utils.h>
+#include <panel-applet.h>
+#include <gtk/gtk.h>
+#include <time.h>
+
+#include "conduit.h"
+
+struct appletdata
+{
+ PanelApplet *applet;
+ GtkLabel *label;
+ GtkProgressBar *pbar;
+ GtkTooltips *tips;
+ gint tiptimeout;
+ struct conduit *conduit;
+ struct transfer *curdisplay;
+};
+
+static char *ctxtmenu =
+"<popup name='button3'>"
+" <menuitem name='Preferences' verb='dca_pref' _label='Preferences' pixtype='stock' pixname='gtk-properties'>"
+" </menuitem>"
+"</popup>";
+
+static void run_pref_dialog(BonoboUIComponent *uic, gpointer data, const char *cname)
+{
+}
+
+static BonoboUIVerb ctxtmenuverbs[] =
+{
+ BONOBO_UI_VERB("dca_pref", run_pref_dialog),
+ BONOBO_UI_VERB_END
+};
+
+static gint reconncb(struct appletdata *data)
+{
+ condtryconn(data->conduit);
+ return(FALSE);
+}
+
+static gboolean updatetip(struct appletdata *data)
+{
+ int diff, speed, left;
+ time_t now;
+ char buf[256];
+
+ if(data->curdisplay == NULL)
+ return(TRUE);
+ now = time(NULL);
+ if(data->curdisplay->cmptime == 0)
+ {
+ strcpy(buf, _("Calculating remaining time..."));
+ } else {
+ diff = data->curdisplay->pos - data->curdisplay->cmpsize;
+ speed = diff / (now - data->curdisplay->cmptime);
+ if(speed == 0)
+ {
+ strcpy(buf, _("Time left: Infinite (Transfer is standing still)"));
+ } else {
+ left = (data->curdisplay->size - data->curdisplay->pos) / speed;
+ sprintf(buf, _("Time left: %i:%02i"), left / 3600, (left / 60) % 60);
+ }
+ }
+ gtk_tooltips_set_tip(data->tips, GTK_WIDGET(data->applet), buf, NULL);
+ return(TRUE);
+}
+
+static void update(struct appletdata *data)
+{
+ char buf[256];
+
+ switch(data->conduit->state)
+ {
+ case CNDS_IDLE:
+ gtk_progress_bar_set_text(data->pbar, _("Not connected"));
+ gtk_label_set_text(data->label, "");
+ break;
+ case CNDS_SYN:
+ gtk_progress_bar_set_text(data->pbar, _("Connecting..."));
+ gtk_label_set_text(data->label, "");
+ break;
+ case CNDS_EST:
+ if(data->conduit->transfers == NULL)
+ {
+ gtk_progress_bar_set_fraction(data->pbar, 0);
+ gtk_progress_bar_set_text(data->pbar, "");
+ gtk_label_set_text(data->label, _("No transfers to display"));
+ } else if(data->curdisplay == NULL) {
+ gtk_progress_bar_set_fraction(data->pbar, 0);
+ gtk_progress_bar_set_text(data->pbar, "");
+ gtk_label_set_text(data->label, _("No transfer selected"));
+ } else {
+ if((data->curdisplay->pos > 0) && (data->curdisplay->size > 0))
+ {
+ sprintf(buf, "%'i/%'i", data->curdisplay->pos, data->curdisplay->size);
+ gtk_progress_bar_set_fraction(data->pbar, (double)data->curdisplay->pos / (double)data->curdisplay->size);
+ gtk_progress_bar_set_text(data->pbar, buf);
+ } else {
+ gtk_progress_bar_set_fraction(data->pbar, 0);
+ gtk_progress_bar_set_text(data->pbar, _("Initializing"));
+ }
+ gtk_label_set_text(data->label, data->curdisplay->tag);
+ }
+ break;
+ }
+}
+
+static void trsize(struct transfer *transfer, struct appletdata *data)
+{
+ update(data);
+}
+
+static void trpos(struct transfer *transfer, struct appletdata *data)
+{
+ update(data);
+}
+
+static void trnew(struct transfer *transfer, struct appletdata *data)
+{
+ if(data->curdisplay == NULL)
+ data->curdisplay = transfer;
+ update(data);
+}
+
+static void trfree(struct transfer *transfer, struct appletdata *data)
+{
+ if(data->curdisplay == transfer)
+ data->curdisplay = data->conduit->transfers;
+ update(data);
+}
+
+static void condstate(struct conduit *conduit, struct appletdata *data)
+{
+ if(conduit->state == CNDS_IDLE)
+ g_timeout_add(10000, (gboolean (*)(gpointer))reconncb, data);
+ update(data);
+}
+
+static void initcond(void)
+{
+ static int inited = 0;
+
+ if(!inited)
+ {
+ cb_trsize = (void (*)(struct transfer *, void *))trsize;
+ cb_trpos = (void (*)(struct transfer *, void *))trpos;
+ cb_trnew = (void (*)(struct transfer *, void *))trnew;
+ cb_trfree = (void (*)(struct transfer *, void *))trfree;
+ cb_condstate = (void (*)(struct conduit *, void *))condstate;
+ inited = 1;
+ }
+}
+
+static gboolean trview_applet_button_press(GtkWidget *widget, GdkEventButton *event, struct appletdata *data)
+{
+ if(event->button == 1)
+ {
+ if(data->curdisplay == NULL)
+ data->curdisplay = data->conduit->transfers;
+ else if(data->curdisplay->next == NULL)
+ data->curdisplay = data->conduit->transfers;
+ else
+ data->curdisplay = data->curdisplay->next;
+ update(data);
+ }
+ return(FALSE);
+}
+
+static void trview_applet_destroy(GtkWidget *widget, struct appletdata *data)
+{
+ freeconduit(data->conduit);
+ g_source_remove(data->tiptimeout);
+ g_object_unref(data->applet);
+ g_object_unref(data->tips);
+ free(data);
+}
+
+static gboolean trview_applet_fill(PanelApplet *applet, const gchar *iid, gpointer uudata)
+{
+ GtkWidget *hbox, *pbar, *label;
+ struct appletdata *data;
+
+ initcond();
+ if(strcmp(iid, "OAFIID:Dolcon_Transferapplet"))
+ return(FALSE);
+
+ panel_applet_setup_menu(applet, ctxtmenu, ctxtmenuverbs, NULL);
+
+ hbox = gtk_hbox_new(FALSE, 0);
+ label = gtk_label_new("");
+ gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
+ pbar = gtk_progress_bar_new();
+ gtk_box_pack_start(GTK_BOX(hbox), pbar, TRUE, TRUE, 0);
+ gtk_container_add(GTK_CONTAINER(applet), hbox);
+ gtk_widget_show_all(GTK_WIDGET(applet));
+
+ data = smalloc(sizeof(*data));
+ memset(data, 0, sizeof(*data));
+ g_object_ref(data->applet = applet);
+ data->conduit = newconduit(conduit_dclib, data);
+ data->pbar = GTK_PROGRESS_BAR(pbar);
+ g_object_ref(data->tips = gtk_tooltips_new());
+ data->tiptimeout = g_timeout_add(500, (gboolean (*)(gpointer))updatetip, data);
+ data->label = GTK_LABEL(label);
+
+ g_signal_connect(applet, "button-press-event", (GCallback)trview_applet_button_press, data);
+ g_signal_connect(applet, "destroy", (GCallback)trview_applet_destroy, data);
+
+ condtryconn(data->conduit);
+
+ update(data);
+
+ return(TRUE);
+}
+
+#define GETTEXT_PACKAGE PACKAGE
+#define GNOMELOCALEDIR LOCALEDIR
+
+PANEL_APPLET_BONOBO_FACTORY("OAFIID:Dolcon_Transferapplet_Factory",
+ PANEL_TYPE_APPLET,
+ "Doldaconnect Transfer Viewer",
+ "0",
+ trview_applet_fill,
+ NULL);
--- /dev/null
+/Makefile.am/1.11/Mon Oct 4 02:05:16 2004//
+/emacs-local/1.1/Fri Aug 13 18:05:08 2004//
+/inpdialog.desc/1.2/Sun Sep 26 03:18:56 2004//
+/main.c/1.22/Mon Nov 15 08:34:25 2004//
+/mainwnd.desc/1.14/Tue Oct 26 02:16:28 2004//
+/makegdesc/1.9/Sun Oct 3 22:28:16 2004//
+/pref.desc/1.2/Sun Sep 26 03:19:19 2004//
+/progressbar.c/1.1/Thu Aug 5 00:22:11 2004//
+/progressbar.h/1.1/Thu Aug 5 00:22:17 2004//
+D
--- /dev/null
+doldaconnect/clients/gtk2
--- /dev/null
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
--- /dev/null
+bin_PROGRAMS=dolcon
+
+dolcon_SOURCES= main.c \
+ progressbar.c \
+ progressbar.h
+
+EXTRA_DIST=mainwnd.desc inpdialog.desc pref.desc makegdesc
+
+BUILT_SOURCES=mainwnd.gtk inpdialog.gtk pref.gtk
+
+main.c: mainwnd.gtk inpdialog.gtk
+
+localedir=$(datadir)/locale
+dolcon_LDFLAGS=$(shell pkg-config --libs gtk+-2.0)
+dolcon_LDADD=$(top_srcdir)/lib/libdcui.la
+dolcon_CPPFLAGS=$(shell pkg-config --cflags gtk+-2.0) -DLOCALEDIR=\"$(localedir)\"
+
+%.gtk: %.desc makegdesc
+ cpp $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $< | ./makegdesc >$@
+
+mainwnd.desc: ../../config.h
--- /dev/null
+; -*-Lisp-*-
+
+; Use with:
+; (add-hook 'find-file-hooks
+; (lambda ()
+; (load (concat default-directory "emacs-local") t)))
+
+(if
+ (string-match "\\.[ch]$" (buffer-file-name (current-buffer)))
+ (progn
+ (make-local-variable 'compile-command)
+ (setq compile-command "make -k 'CFLAGS=-g -Wall'")
+))
--- /dev/null
+;prefix: inpdialog_
+:hbox
+ $simg stock: DIALOG_QUESTION size: DIALOG
+ :vbox
+ $lbl name: prompt label: " " var: y
+ $text name: entry var: y sig: activate
+ end
+end
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <malloc.h>
+#include <stdarg.h>
+#include <gtk/gtk.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <gdk/gdkkeysyms.h>
+#include <doldaconnect/uilib.h>
+#include <doldaconnect/uimisc.h>
+#include <doldaconnect/utils.h>
+#include <errno.h>
+#include <regex.h>
+#include <signal.h>
+#include <time.h>
+#include <pwd.h>
+#include <locale.h>
+#include <libintl.h>
+#include <assert.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "progressbar.h"
+
+
+struct fndata
+{
+ GtkTextBuffer *textbuf;
+};
+
+struct srchsize
+{
+ int size;
+ int num;
+ int slots;
+ double resptime;
+ GtkTreeRowReference *ref;
+};
+
+struct knownspeed
+{
+ char *userid;
+ int speed, seq;
+ time_t fetched;
+};
+
+GtkWidget *inpdialog;
+GtkListStore *fnmodel, *ulmodel, *dlmodel, *pubhubmodel;
+GtkTreeStore *srchmodel;
+GtkTreeModelFilter *srchmodelfilter;
+GtkTextTagTable *chattags;
+int dcfd = -1, gdkread = -1, gdkwrite = -1;
+int pubhubfd = -1, pubhubtag = -1, filterpubhub = 0;
+int curchat = -1;
+regex_t pubhubfilter;
+pid_t pubhubproc = 0;
+char *pubhubaddr = NULL;
+char *connectas = NULL;
+char *dcserver = NULL;
+int autoconn = 0;
+int srchautoupdate = 0;
+int cursrch = -1, nextsrch = -1;
+time_t srcheta;
+struct srchsize *srchsizes = NULL;
+struct knownspeed *knownspeeds = NULL;
+int numsizes = 0, numspeeds = 0, ksqueryseq = -1, ksquerytag = -1;
+
+gboolean initdeath(GtkWidget *, gpointer);
+void cb_main_connmenu_activate(GtkWidget *widget, gpointer data);
+void cb_main_dconnmenu_activate(GtkWidget *widget, gpointer data);
+void cb_main_prefmenu_activate(GtkWidget *widget, gpointer data);
+void cb_main_sdmenu_activate(GtkWidget *widget, gpointer data);
+void cb_inpdialog_entry_activate(GtkWidget *widget, gpointer data);
+void cb_main_fnaddr_activate(GtkWidget *widget, gpointer data);
+void cb_main_pubhubfilter_activate(GtkWidget *widget, gpointer data);
+void cb_main_dcnctbtn_clicked(GtkWidget *widget, gpointer data);
+void cb_main_phublist_cchange(GtkWidget *widget, gpointer data);
+void cb_main_phublist_activate(GtkWidget *widget, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data);
+void cb_main_chatnodes_activate(GtkWidget *widget, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data);
+void cb_main_srchres_activate(GtkWidget *widget, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data);
+void cb_main_chatstr_activate(GtkWidget *widget, gpointer data);
+void cb_main_simplesrch_changed(GtkWidget *widget, gpointer data);
+void cb_main_realsrch_changed(GtkWidget *widget, gpointer data);
+void cb_main_srchbtn_clicked(GtkWidget *widget, gpointer data);
+void cb_main_srchcanbtn_clicked(GtkWidget *widget, gpointer data);
+void cb_main_trlist_keypress(GtkWidget *widget, GdkEventKey *event, gpointer data);
+void cb_main_filternoslots_toggled(GtkToggleButton *widget, gpointer data);
+void dcfdcallback(gpointer data, gint source, GdkInputCondition condition);
+void srchstatupdate(void);
+void transnicebytefunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data);
+void transerrorinfo(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data);
+void percentagefunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data);
+void hidezerofunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data);
+void speedtimefunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data);
+
+#define DCCHARSET "windows-1252"
+
+#define _(text) gettext(text)
+
+#include "mainwnd.gtk"
+#include "inpdialog.gtk"
+#include "pref.gtk"
+
+void updatewrite(void)
+{
+ if(dcfd < 0)
+ return;
+ if(dc_wantwrite())
+ {
+ if(gdkwrite == -1)
+ gdkwrite = gdk_input_add(dcfd, GDK_INPUT_WRITE, dcfdcallback, NULL);
+ } else {
+ if(gdkwrite != -1)
+ {
+ gdk_input_remove(gdkwrite);
+ gdkwrite = -1;
+ }
+ }
+}
+
+void fndestroycb(struct dc_fnetnode *fn)
+{
+ struct fndata *data;
+ GtkTextBuffer *textbuf;
+
+ data = fn->udata;
+ g_object_unref(data->textbuf);
+ free(data);
+ if(curchat == fn->id)
+ {
+ textbuf = gtk_text_buffer_new(chattags);
+ gtk_text_view_set_buffer(GTK_TEXT_VIEW(main_chatview), textbuf);
+ g_object_unref(textbuf);
+ }
+}
+
+void addfndata(struct dc_fnetnode *fn)
+{
+ struct fndata *data;
+
+ if(fn->udata != NULL)
+ return;
+ fn->destroycb = fndestroycb;
+ data = smalloc(sizeof(*data));
+ data->textbuf = gtk_text_buffer_new(chattags);
+ fn->udata = data;
+}
+
+char *getfnstatestock(int state)
+{
+ if(state == DC_FNN_STATE_SYN)
+ return("gtk-jump-to");
+ if(state == DC_FNN_STATE_HS)
+ return("gtk-execute");
+ if(state == DC_FNN_STATE_EST)
+ return("gtk-yes");
+ if(state == DC_FNN_STATE_DEAD)
+ return("gtk-cancel");
+ return(NULL);
+}
+
+void updatehublist(void)
+{
+ int done;
+ struct dc_fnetnode *fn;
+ GtkTreeIter iter;
+ int id;
+ char *buf;
+ char *name;
+ int state, numusers;
+
+ for(fn = dc_fnetnodes; fn != NULL; fn = fn->next)
+ fn->found = 0;
+ done = 0;
+ while(!done)
+ {
+ done = 1;
+ if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(fnmodel), &iter))
+ {
+ do
+ {
+ gtk_tree_model_get(GTK_TREE_MODEL(fnmodel), &iter, 0, &id, -1);
+ if((fn = dc_findfnetnode(id)) == NULL)
+ {
+ /* I can't seem to get a sensible reply fromp
+ * gtk_list_store, so I'm just doing this
+ * instead. */
+ gtk_list_store_remove(fnmodel, &iter);
+ done = 0;
+ break;
+ } else {
+ gtk_tree_model_get(GTK_TREE_MODEL(fnmodel), &iter, 1, &name, 2, &state, 3, &numusers, -1);
+ if(fn->name == NULL)
+ buf = _("Unknown");
+ else
+ buf = icswcstombs(fn->name, "UTF-8", NULL);
+ if(strcmp(buf, name))
+ gtk_list_store_set(fnmodel, &iter, 1, buf, -1);
+ if(state != fn->state)
+ {
+ gtk_list_store_set(fnmodel, &iter, 2, fn->state, -1);
+ gtk_list_store_set(fnmodel, &iter, 4, getfnstatestock(fn->state), -1);
+ }
+ if(numusers != fn->numusers)
+ gtk_list_store_set(fnmodel, &iter, 3, fn->numusers, -1);
+ g_free(name);
+ fn->found = 1;
+ }
+ } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(fnmodel), &iter));
+ }
+ }
+ for(fn = dc_fnetnodes; fn != NULL; fn = fn->next)
+ {
+ if(!fn->found)
+ {
+ if(fn->name == NULL)
+ buf = _("Unknown");
+ else
+ buf = icswcstombs(fn->name, "UTF-8", NULL);
+ gtk_list_store_append(fnmodel, &iter);
+ gtk_list_store_set(fnmodel, &iter, 0, fn->id, 1, buf, 2, fn->state, 3, fn->numusers, 4, getfnstatestock(fn->state), -1);
+ addfndata(fn);
+ }
+ }
+}
+
+void percentagefunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
+{
+ int colnum;
+ float val;
+ char buf[64];
+
+ colnum = (int)data;
+ gtk_tree_model_get(model, iter, colnum, &val, -1);
+ snprintf(buf, 64, "%.2f%%", (double)(val * 100.0));
+ g_object_set(rend, "text", buf, NULL);
+}
+
+void transnicebytefunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
+{
+ int colnum, val;
+ char buf[64];
+
+ colnum = (int)data;
+ gtk_tree_model_get(model, iter, colnum, &val, -1);
+ if(val >= 0)
+ snprintf(buf, 64, "%'i", val);
+ else
+ strcpy(buf, _("Unknown"));
+ g_object_set(rend, "text", buf, NULL);
+}
+
+void hidezerofunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
+{
+ int colnum, val;
+ char buf[64];
+
+ colnum = (int)data;
+ gtk_tree_model_get(model, iter, colnum, &val, -1);
+ if(val > 0)
+ snprintf(buf, 64, "%i", val);
+ else
+ strcpy(buf, "");
+ g_object_set(rend, "text", buf, NULL);
+}
+
+void speedtimefunc(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
+{
+ int speed, size, time;
+ char buf[64];
+
+ gtk_tree_model_get(model, iter, 4, &size, 8, &speed, -1);
+ if(speed > 0)
+ {
+ time = (size / speed) / 60;
+ if(time < 1)
+ snprintf(buf, 64, "%'i (<00:01)", speed);
+ else
+ snprintf(buf, 64, "%'i (%02i:%02i)", speed, time / 60, time % 60);
+ } else if(speed == 0) {
+ strcpy(buf, "0");
+ } else {
+ strcpy(buf, _("Unknown"));
+ }
+ g_object_set(rend, "text", buf, NULL);
+}
+
+void transerrorinfo(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
+{
+ int error;
+ time_t errortime;
+ char finbuf[64], tbuf[64], *errstr;
+
+ gtk_tree_model_get(model, iter, 10, &error, 11, &errortime, -1);
+ if(error != DC_TRNSE_NOERROR)
+ {
+ if(error == DC_TRNSE_NOTFOUND)
+ errstr = _("Not found");
+ else if(error == DC_TRNSE_NOSLOTS)
+ errstr = _("No slots");
+ strftime(tbuf, 64, _("%H:%M:%S"), localtime(&errortime));
+ snprintf(finbuf, 64, _("%s (reported at %s)"), errstr, tbuf);
+ } else {
+ *finbuf = 0;
+ }
+ g_object_set(rend, "text", finbuf, NULL);
+}
+
+char *gettrstatestock(int state)
+{
+ if(state == DC_TRNS_WAITING)
+ return("gtk-jump-to");
+ if(state == DC_TRNS_HS)
+ return("gtk-execute");
+ if(state == DC_TRNS_MAIN)
+ return("gtk-network");
+ if(state == DC_TRNS_DONE)
+ return("gtk-yes");
+ return(NULL);
+}
+
+void updatetransferlists(void)
+{
+ int i;
+ int done;
+ struct dc_transfer *transfer;
+ GtkTreeIter iter;
+ int id;
+ char *buf;
+ char *peerid, *peernick, *path;
+ int state, dir, size, curpos, error;
+ time_t errortime;
+ GtkListStore *stores[3];
+
+ for(transfer = dc_transfers; transfer != NULL; transfer = transfer->next)
+ transfer->found = 0;
+ stores[DC_TRNSD_UNKNOWN] = NULL;
+ stores[DC_TRNSD_UP] = ulmodel;
+ stores[DC_TRNSD_DOWN] = dlmodel;
+ for(i = 0; i < 3; i++)
+ {
+ if(stores[i] == NULL)
+ continue;
+ done = 0;
+ while(!done)
+ {
+ done = 1;
+ if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(stores[i]), &iter))
+ {
+ do
+ {
+ gtk_tree_model_get(GTK_TREE_MODEL(stores[i]), &iter, 0, &id, 1, &dir, -1);
+ if(((transfer = dc_findtransfer(id)) == NULL) || (transfer->dir != dir))
+ {
+ gtk_list_store_remove(stores[i], &iter);
+ done = 0;
+ break;
+ } else {
+ transfer->found = 1;
+ gtk_tree_model_get(GTK_TREE_MODEL(stores[i]), &iter, 2, &state, 3, &peerid, 4, &peernick, 5, &path, 6, &size, 7, &curpos, 10, &error, 11, &errortime, -1);
+ if(state != transfer->state)
+ gtk_list_store_set(stores[i], &iter, 2, transfer->state, 8, gettrstatestock(transfer->state), -1);
+ if(size != transfer->size)
+ gtk_list_store_set(stores[i], &iter, 6, transfer->size, -1);
+ if(curpos != transfer->curpos)
+ gtk_list_store_set(stores[i], &iter, 7, transfer->curpos, -1);
+ if(error != transfer->error)
+ gtk_list_store_set(stores[i], &iter, 10, transfer->error, -1);
+ if(errortime != transfer->errortime)
+ gtk_list_store_set(stores[i], &iter, 11, transfer->errortime, -1);
+ if((transfer->size > 0) && (transfer->curpos > 0))
+ gtk_list_store_set(stores[i], &iter, 9, (float)transfer->curpos / (float)transfer->size, -1);
+ buf = icswcstombs(transfer->peerid, "UTF-8", NULL);
+ if(strcmp(buf, peerid))
+ gtk_list_store_set(stores[i], &iter, 3, buf, -1);
+ buf = icswcstombs(((transfer->peernick == NULL) || (transfer->peernick[0] == L'\0'))?transfer->peerid:transfer->peernick, "UTF-8", NULL);
+ if(strcmp(buf, peernick))
+ gtk_list_store_set(stores[i], &iter, 4, buf, -1);
+ buf = (transfer->path == NULL)?_("Unknown"):icswcstombs(transfer->path, "UTF-8", NULL);
+ if(strcmp(buf, path))
+ gtk_list_store_set(stores[i], &iter, 5, buf, -1);
+ g_free(peerid);
+ g_free(peernick);
+ g_free(path);
+ }
+ } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(stores[i]), &iter));
+ }
+ }
+ }
+ for(transfer = dc_transfers; transfer != NULL; transfer = transfer->next)
+ {
+ if(!transfer->found)
+ {
+ if(stores[transfer->dir] != NULL)
+ {
+ peerid = icwcstombs(transfer->peerid, "UTF-8");
+ peernick = icwcstombs(((transfer->peernick == NULL) || (transfer->peernick[0] == L'\0'))?transfer->peerid:transfer->peernick, "UTF-8");
+ path = (transfer->path == NULL)?_("Unknown"):icwcstombs(transfer->path, "UTF-8");
+ gtk_list_store_append(stores[transfer->dir], &iter);
+ gtk_list_store_set(stores[transfer->dir], &iter,
+ 0, transfer->id,
+ 1, transfer->dir,
+ 2, transfer->state,
+ 3, peerid,
+ 4, peernick,
+ 5, path,
+ 6, transfer->size,
+ 7, transfer->curpos,
+ 8, gettrstatestock(transfer->state),
+ 9, 0.0,
+ 10, transfer->error,
+ 11, transfer->errortime,
+ -1);
+ free(peerid);
+ free(peernick);
+ if(transfer->path != NULL)
+ free(path);
+ }
+ }
+ }
+}
+
+void updatesbar(char *msg)
+{
+ gtk_statusbar_pop(GTK_STATUSBAR(main_statusbar), 0);
+ gtk_statusbar_push(GTK_STATUSBAR(main_statusbar), 0, msg);
+}
+
+void freesrchsizes(void)
+{
+ int i;
+
+ for(i = 0; i < numsizes; i++)
+ {
+ if(srchsizes[i].ref != NULL)
+ gtk_tree_row_reference_free(srchsizes[i].ref);
+ }
+ if(srchsizes != NULL)
+ free(srchsizes);
+ srchsizes = NULL;
+ numsizes = 0;
+}
+
+void dcdisconnected(void)
+{
+ if(gdkread != -1)
+ {
+ gdk_input_remove(gdkread);
+ gdkread = -1;
+ }
+ dcfd = -1;
+ updatehublist();
+ updatetransferlists();
+ cursrch = nextsrch = -1;
+ gtk_tree_store_clear(srchmodel);
+ freesrchsizes();
+ gtk_widget_set_sensitive(main_connmenu, TRUE);
+ gtk_widget_set_sensitive(main_dconnmenu, FALSE);
+ gtk_widget_set_sensitive(main_simplesrch, TRUE);
+ gtk_widget_set_sensitive(main_realsrch, TRUE);
+ gtk_widget_set_sensitive(main_srchbtn, TRUE);
+ gtk_widget_set_sensitive(main_srchcanbtn, FALSE);
+ updatesbar(_("Disconnected"));
+}
+
+char *inputbox(char *title, char *prompt, char *def, int echo)
+{
+ int resp;
+ GtkWidget *swnd;
+ char *buf;
+
+ inpdialog = gtk_dialog_new_with_buttons(title, GTK_WINDOW(main_wnd), GTK_DIALOG_MODAL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
+ gtk_box_pack_start(GTK_BOX(GTK_DIALOG(inpdialog)->vbox), swnd = create_inpdialog_wnd(), TRUE, TRUE, 0);
+ gtk_widget_show(swnd);
+ if(!echo)
+ gtk_entry_set_visibility(GTK_ENTRY(inpdialog_entry), FALSE);
+ gtk_label_set_text(GTK_LABEL(inpdialog_prompt), prompt);
+ gtk_entry_set_text(GTK_ENTRY(inpdialog_entry), def);
+ resp = gtk_dialog_run(GTK_DIALOG(inpdialog));
+ if(!echo)
+ gtk_entry_set_visibility(GTK_ENTRY(inpdialog_entry), TRUE);
+ if(resp == GTK_RESPONSE_ACCEPT)
+ buf = strdup(gtk_entry_get_text(GTK_ENTRY(inpdialog_entry)));
+ else
+ buf = NULL;
+ gtk_widget_destroy(inpdialog);
+ updatewrite();
+ return(buf);
+}
+
+int msgbox(int type, int buttons, char *format, ...)
+{
+ GtkWidget *swnd;
+ va_list args;
+ char *buf;
+ int resp;
+
+ va_start(args, format);
+ buf = vsprintf2(format, args);
+ va_end(args);
+ swnd = gtk_message_dialog_new(GTK_WINDOW(main_wnd), GTK_DIALOG_MODAL, type, buttons, "%s", buf);
+ resp = gtk_dialog_run(GTK_DIALOG(swnd));
+ gtk_widget_destroy(swnd);
+ free(buf);
+ return(resp);
+}
+
+void readconfigfile(void)
+{
+ FILE *cfgfile;
+ char *homedir, *buf, *p;
+ int w, h;
+
+ if((homedir = getenv("HOME")) == NULL)
+ {
+ fprintf(stderr, "warning: could not find home directory!\n");
+ return;
+ }
+ buf = sprintf2("%s/.dolconrc", homedir);
+ if((cfgfile = fopen(buf, "r")) == NULL)
+ {
+ if(errno != ENOENT)
+ perror(buf);
+ free(buf);
+ return;
+ }
+ free(buf);
+ buf = smalloc(1024);
+ while(fgets(buf, 1024, cfgfile) != NULL)
+ {
+ if(strlen(buf) < 1)
+ continue;
+ p = buf + strlen(buf);
+ if(p[-1] == '\n')
+ *(--p) = 0;
+ if((p = strchr(buf, ':')) == NULL)
+ continue;
+ *(p++) = 0;
+ while((*p == ' ') || (*p == '\t'))
+ p++;
+ if(!strcmp(buf, "wnd-width"))
+ {
+ w = atoi(p);
+ } else if(!strcmp(buf, "wnd-height")) {
+ h = atoi(p);
+ } else if(!strcmp(buf, "pane1-pos")) {
+ gtk_paned_set_position(GTK_PANED(main_pane1), atoi(p));
+ } else if(!strcmp(buf, "pane2-pos")) {
+ gtk_paned_set_position(GTK_PANED(main_pane2), atoi(p));
+ } else if(!strcmp(buf, "pane3-pos")) {
+ gtk_paned_set_position(GTK_PANED(main_pane3), atoi(p));
+ } else if(!strcmp(buf, "pubhubaddr")) {
+ free(pubhubaddr);
+ pubhubaddr = sstrdup(p);
+ } else if(!strcmp(buf, "dcserver")) {
+ free(dcserver);
+ dcserver = sstrdup(p);
+ } else if(!strcmp(buf, "advexpanded")) {
+ gtk_expander_set_expanded(GTK_EXPANDER(main_advexp), atoi(p));
+ } else if(!strcmp(buf, "connectas")) {
+ free(connectas);
+ connectas = sstrdup(p);
+ } else if(!strcmp(buf, "autoconn")) {
+ autoconn = atoi(p);
+ } else if(!strcmp(buf, "filternoslots")) {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(main_filternoslots), atoi(p));
+ }
+ }
+ free(buf);
+ fclose(cfgfile);
+/*
+ if(w != 1589)
+ abort();
+*/
+ gtk_window_resize(GTK_WINDOW(main_wnd), w, h);
+}
+
+void updateconfigfile(void)
+{
+ FILE *cfgfile;
+ char *homedir, *buf;
+ int w, h;
+
+ if((homedir = getenv("HOME")) == NULL)
+ {
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not get your home directory!"));
+ return;
+ }
+ buf = sprintf2("%s/.dolconrc", homedir);
+ if((cfgfile = fopen(buf, "w")) == NULL)
+ {
+ free(buf);
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not open configuration file for writing: %s"), strerror(errno));
+ return;
+ }
+ free(buf);
+ gtk_window_get_size(GTK_WINDOW(main_wnd), &w, &h);
+ fprintf(cfgfile, "wnd-width: %i\n", w);
+ fprintf(cfgfile, "wnd-height: %i\n", h);
+ fprintf(cfgfile, "pane1-pos: %i\n", gtk_paned_get_position(GTK_PANED(main_pane1)));
+ fprintf(cfgfile, "pane2-pos: %i\n", gtk_paned_get_position(GTK_PANED(main_pane2)));
+ fprintf(cfgfile, "pane3-pos: %i\n", gtk_paned_get_position(GTK_PANED(main_pane3)));
+ fprintf(cfgfile, "pubhubaddr: %s\n", pubhubaddr);
+ fprintf(cfgfile, "dcserver: %s\n", dcserver);
+ fprintf(cfgfile, "advexpanded: %i\n", gtk_expander_get_expanded(GTK_EXPANDER(main_advexp)));
+ fprintf(cfgfile, "connectas: %s\n", connectas);
+ fprintf(cfgfile, "autoconn: %i\n", autoconn);
+ fprintf(cfgfile, "filternoslots: %i\n", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(main_filternoslots)));
+ fclose(cfgfile);
+}
+
+gboolean initdeath(GtkWidget *widget, gpointer data)
+{
+ updateconfigfile();
+ gtk_main_quit();
+ return(TRUE);
+}
+
+void cb_inpdialog_entry_activate(GtkWidget *widget, gpointer data)
+{
+ gtk_dialog_response(GTK_DIALOG(inpdialog), GTK_RESPONSE_ACCEPT);
+}
+
+int loginconv(int type, wchar_t *prompt, char **resp, void *data)
+{
+ int ret;
+ char *buf;
+
+ ret = 0;
+ buf = icwcstombs(prompt, "UTF-8");
+ switch(type)
+ {
+ case DC_LOGIN_CONV_NOECHO:
+ if((*resp = inputbox(_("Login"), buf, "", 0)) == NULL)
+ ret = 1;
+ break;
+ case DC_LOGIN_CONV_ECHO:
+ if((*resp = inputbox(_("Login"), buf, "", 1)) == NULL)
+ ret = 1;
+ break;
+ case DC_LOGIN_CONV_INFO:
+ msgbox(GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "%s", buf);
+ break;
+ case DC_LOGIN_CONV_ERROR:
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", buf);
+ break;
+ }
+ free(buf);
+ updatewrite();
+ return(ret);
+}
+
+void getfnlistcallback(int resp, void *data)
+{
+ updatehublist();
+}
+
+void gettrlistcallback(int resp, void *data)
+{
+ updatetransferlists();
+}
+
+void logincallback(int err, wchar_t *reason, void *data)
+{
+ switch(err)
+ {
+ case DC_LOGIN_ERR_SUCCESS:
+ dc_queuecmd(NULL, NULL, L"notify", L"all", L"on", NULL);
+ dc_getfnlistasync(getfnlistcallback, NULL);
+ dc_gettrlistasync(gettrlistcallback, NULL);
+ updatesbar("Authenticated");
+ break;
+ case DC_LOGIN_ERR_NOLOGIN:
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not negotiate an acceptable authentication mechanism"));
+ dc_disconnect();
+ dcdisconnected();
+ break;
+ case DC_LOGIN_ERR_SERVER:
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("The server has encountered an error"));
+ dc_disconnect();
+ dcdisconnected();
+ break;
+ case DC_LOGIN_ERR_USER:
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Internal client error"));
+ dc_disconnect();
+ dcdisconnected();
+ break;
+ case DC_LOGIN_ERR_CONV:
+ dc_disconnect();
+ dcdisconnected();
+ break;
+ case DC_LOGIN_ERR_AUTHFAIL:
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Login attempt failed!"));
+ dc_disconnect();
+ dcdisconnected();
+ break;
+ }
+ updatewrite();
+}
+
+GtkTreeIter *ref2iter(GtkTreeRowReference *ref)
+{
+ static GtkTreeIter iter;
+ GtkTreePath *path;
+
+ assert((path = gtk_tree_row_reference_get_path(ref)) != NULL);
+ assert(gtk_tree_model_get_iter(GTK_TREE_MODEL(srchmodel), &iter, path));
+ gtk_tree_path_free(path);
+ return(&iter);
+}
+
+GtkTreeRowReference *iter2ref(GtkTreeIter *iter)
+{
+ GtkTreePath *path;
+ GtkTreeRowReference *ref;
+
+ assert((path = gtk_tree_model_get_path(GTK_TREE_MODEL(srchmodel), iter)) != NULL);
+ assert((ref = gtk_tree_row_reference_new(GTK_TREE_MODEL(srchmodel), path)) != NULL);
+ gtk_tree_path_free(path);
+ return(ref);
+}
+
+struct srchsize *finddiscsize(void)
+{
+ int i;
+ GtkTreeIter iter;
+
+ for(i = 0; i < numsizes; i++)
+ {
+ if(srchsizes[i].size == -1)
+ return(&srchsizes[i]);
+ }
+ srchsizes = srealloc(srchsizes, sizeof(*srchsizes) * ++numsizes);
+ srchsizes[i].size = -1;
+ srchsizes[i].num = 1;
+ srchsizes[i].slots = 0;
+ srchsizes[i].resptime = 0.0;
+ gtk_tree_store_append(srchmodel, &iter, NULL);
+ gtk_tree_store_set(srchmodel, &iter, 3, _("Discrete sizes"), 7, 1, -1);
+ srchsizes[i].ref = iter2ref(&iter);
+ return(&srchsizes[i]);
+}
+
+struct knownspeed *findksentbyname(char *userid)
+{
+ int i;
+
+ for(i = 0; i < numspeeds; i++)
+ {
+ if(!strcmp(knownspeeds[i].userid, userid))
+ return(&knownspeeds[i]);
+ }
+ return(NULL);
+}
+
+struct knownspeed *findksentbyseq(int seq)
+{
+ int i;
+
+ for(i = 0; i < numspeeds; i++)
+ {
+ if(knownspeeds[i].seq == seq)
+ return(&knownspeeds[i]);
+ }
+ return(NULL);
+}
+
+gboolean ksupdaterow(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
+{
+ struct knownspeed *ks;
+ char *userid;
+
+ gtk_tree_model_get(GTK_TREE_MODEL(model), iter, 1, &userid, -1);
+ if(userid == NULL)
+ return(FALSE);
+ ks = findksentbyname(userid);
+ if(ks == NULL)
+ {
+ knownspeeds = srealloc(knownspeeds, (numspeeds + 1) * sizeof(*knownspeeds));
+ ks = &knownspeeds[numspeeds];
+ numspeeds++;
+ ks->userid = sstrdup(userid);
+ ks->speed = -1;
+ ks->seq = -2;
+ ksqueryseq = -2;
+ }
+ g_free(userid);
+ if(ks->speed != -1)
+ gtk_tree_store_set(GTK_TREE_STORE(model), iter, 8, ks->speed, -1);
+ return(FALSE);
+}
+
+gint ksupdatecb(gpointer data)
+{
+ int i, oldnum;
+ time_t now;
+ wchar_t **users, *buf;
+ size_t userssize, usersdata;
+
+ if(ksquerytag != -1)
+ return(TRUE);
+ now = time(NULL);
+ oldnum = numspeeds;
+ for(i = 0; i < numspeeds;)
+ {
+ if(now - knownspeeds[i].fetched > 60)
+ {
+ free(knownspeeds[i].userid);
+ memmove(&knownspeeds[i], &knownspeeds[i + 1], (--numspeeds - i) * sizeof(*knownspeeds));
+ } else {
+ i++;
+ }
+ }
+ if(oldnum != numspeeds)
+ knownspeeds = srealloc(knownspeeds, numspeeds * sizeof(*knownspeeds));
+ gtk_tree_model_foreach(GTK_TREE_MODEL(srchmodel), ksupdaterow, NULL);
+ if(ksqueryseq == -2)
+ {
+ users = NULL;
+ userssize = usersdata = 0;
+ ksqueryseq = 0;
+ for(i = 0; i < numspeeds; i++)
+ {
+ if(knownspeeds[i].seq == -2)
+ {
+ assert((buf = icmbstowcs(knownspeeds[i].userid, "UTF-8")) != NULL);
+ knownspeeds[i].seq = ksqueryseq++;
+ addtobuf(users, buf);
+ }
+ }
+ addtobuf(users, NULL);
+ ksquerytag = dc_queuecmd(NULL, NULL, L"filtercmd", L"userspeeda", L"%%a", users, NULL);
+ dc_freewcsarr(users);
+ }
+ return(TRUE);
+}
+
+void handleresps(void)
+{
+ int i;
+ struct dc_response *resp;
+ struct dc_intresp *ires;
+ struct dc_fnetnode *fn;
+ struct fndata *fndata;
+ GtkTextIter iter;
+ GtkTreeIter titer, piter;
+ char *buf, *p;
+ int tosbuf;
+ struct srchsize *ss;
+ struct knownspeed *ks;
+
+ while((resp = dc_getresp()) != NULL)
+ {
+ if(!wcscmp(resp->cmdname, L".connect"))
+ {
+ if(resp->code == 200)
+ {
+ tosbuf = 0x10; /* Minimum cost */
+ setsockopt(dcfd, SOL_IP, IP_TOS, &tosbuf, sizeof(tosbuf));
+ updatesbar(_("Connected"));
+ dc_loginasync(connectas, 1, loginconv, logincallback, NULL);
+ } else {
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("The server refused the connection"));
+ dc_disconnect();
+ dcdisconnected();
+ }
+ } else if(!wcscmp(resp->cmdname, L".notify")) {
+ dc_uimisc_handlenotify(resp);
+ switch(resp->code)
+ {
+ case 600:
+ if((ires = dc_interpret(resp)) != NULL)
+ {
+ if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
+ {
+ fndata = fn->udata;
+ gtk_text_buffer_get_end_iter(fndata->textbuf, &iter);
+ if((buf = icwcstombs(ires->argv[3].val.str, "UTF-8")) != NULL)
+ {
+ gtk_text_buffer_insert_with_tags_by_name(fndata->textbuf, &iter, "<", -1, "sender", NULL);
+ gtk_text_buffer_insert_with_tags_by_name(fndata->textbuf, &iter, buf, -1, "sender", NULL);
+ gtk_text_buffer_insert_with_tags_by_name(fndata->textbuf, &iter, ">", -1, "sender", NULL);
+ gtk_text_buffer_insert(fndata->textbuf, &iter, " ", -1);
+ free(buf);
+ }
+ if((buf = icwcstombs(ires->argv[4].val.str, "UTF-8")) != NULL)
+ {
+ gtk_text_buffer_insert(fndata->textbuf, &iter, buf, -1);
+ gtk_text_buffer_insert(fndata->textbuf, &iter, "\n", -1);
+ free(buf);
+ if(curchat == fn->id)
+ gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(main_chatview), &iter, 0, 0, 0, 0);
+ }
+ }
+ dc_freeires(ires);
+ }
+ break;
+ case 601:
+ case 602:
+ case 603:
+ case 604:
+ case 605:
+ updatehublist();
+ break;
+ case 610:
+ case 611:
+ case 612:
+ case 613:
+ case 614:
+ case 615:
+ case 616:
+ case 617:
+ updatetransferlists();
+ break;
+ case 620:
+ if((ires = dc_interpret(resp)) != NULL)
+ {
+ if(ires->argv[0].val.num == nextsrch)
+ srcheta = time(NULL) + ires->argv[0].val.num;
+ dc_freeires(ires);
+ }
+ break;
+ case 621:
+ if((ires = dc_interpret(resp)) != NULL)
+ {
+ if(ires->argv[0].val.num == nextsrch)
+ {
+ if(cursrch != -1)
+ dc_queuecmd(NULL, NULL, L"cansrch", L"%%i", cursrch, NULL);
+ cursrch = nextsrch;
+ nextsrch = -1;
+ gtk_widget_set_sensitive(main_realsrch, TRUE);
+ gtk_widget_set_sensitive(main_simplesrch, TRUE);
+ gtk_widget_set_sensitive(main_srchbtn, TRUE);
+ gtk_widget_set_sensitive(main_srchcanbtn, FALSE);
+ srchstatupdate();
+ gtk_entry_set_text(GTK_ENTRY(main_realsrch), "");
+ gtk_entry_set_text(GTK_ENTRY(main_simplesrch), "");
+ gtk_tree_store_clear(srchmodel);
+ freesrchsizes();
+ }
+ dc_freeires(ires);
+ }
+ break;
+ case 622:
+ if((ires = dc_interpret(resp)) != NULL)
+ {
+ if(ires->argv[0].val.num == cursrch)
+ {
+ for(i = 0; i < numsizes; i++)
+ {
+ if(srchsizes[i].size == ires->argv[4].val.num)
+ break;
+ }
+ if(i == numsizes)
+ {
+ srchsizes = srealloc(srchsizes, sizeof(*srchsizes) * ++numsizes);
+ srchsizes[i].size = ires->argv[4].val.num;
+ srchsizes[i].num = 1;
+ srchsizes[i].slots = ires->argv[5].val.num;
+ srchsizes[i].resptime = ires->argv[7].val.flnum;
+ ss = finddiscsize();
+ ss->slots += ires->argv[5].val.num;
+ if((ss->resptime == 0.0) || (ss->resptime > ires->argv[7].val.flnum))
+ ss->resptime = ires->argv[7].val.flnum;
+ piter = *ref2iter(ss->ref);
+ gtk_tree_store_set(srchmodel, &piter, 5, ss->slots, 6, ss->resptime, -1);
+ gtk_tree_store_append(srchmodel, &titer, &piter);
+ srchsizes[i].ref = iter2ref(&titer);
+ } else if(srchsizes[i].num == 1) {
+ char *filename, *peername, *fnetname;
+ int slots, speed;
+ double resptime;
+
+ gtk_tree_model_get(GTK_TREE_MODEL(srchmodel), ref2iter(srchsizes[i].ref), 0, &fnetname, 1, &peername, 3, &filename, 5, &slots, 6, &resptime, 8, &speed, -1);
+ gtk_tree_store_remove(srchmodel, ref2iter(srchsizes[i].ref));
+ gtk_tree_row_reference_free(srchsizes[i].ref);
+ ss = finddiscsize();
+ ss->slots -= slots;
+ gtk_tree_store_set(srchmodel, ref2iter(ss->ref), 5, ss->slots, -1);
+ gtk_tree_store_append(srchmodel, &piter, NULL);
+ srchsizes[i].slots = ires->argv[5].val.num + slots;
+ srchsizes[i].resptime = (ires->argv[7].val.flnum < resptime)?ires->argv[7].val.flnum:resptime;
+ srchsizes[i].num = 2;
+ srchsizes[i].ref = iter2ref(&piter);
+ gtk_tree_store_set(srchmodel, &piter, 4, srchsizes[i].size, 5, srchsizes[i].slots, 6, srchsizes[i].resptime, 7, 2, -1);
+ if((buf = icwcstombs(ires->argv[1].val.str, "UTF-8")) != NULL)
+ {
+ p = buf;
+ /* XXX: Too DC-specific! */
+ if(strrchr(p, '\\') != NULL)
+ p = strrchr(p, '\\') + 1;
+ gtk_tree_store_set(srchmodel, &piter, 3, p, -1);
+ free(buf);
+ }
+ gtk_tree_store_append(srchmodel, &titer, &piter);
+ gtk_tree_store_set(srchmodel, &titer, 0, fnetname, 1, peername, 2, peername, 3, filename, 4, srchsizes[i].size, 5, slots, 6, resptime, 8, speed, -1);
+ g_free(filename); g_free(peername); g_free(fnetname);
+ gtk_tree_store_append(srchmodel, &titer, &piter);
+ } else {
+ srchsizes[i].num++;
+ srchsizes[i].slots += ires->argv[5].val.num;
+ if(ires->argv[7].val.flnum < srchsizes[i].resptime)
+ srchsizes[i].resptime = ires->argv[7].val.flnum;
+ piter = *ref2iter(srchsizes[i].ref);
+ gtk_tree_store_set(srchmodel, &piter, 5, srchsizes[i].slots, 6, srchsizes[i].resptime, 7, srchsizes[i].num, -1);
+ gtk_tree_store_append(srchmodel, &titer, &piter);
+ }
+ if((buf = icwcstombs(ires->argv[1].val.str, "UTF-8")) != NULL)
+ {
+ gtk_tree_store_set(srchmodel, &titer, 3, buf, -1);
+ free(buf);
+ }
+ if((buf = icwcstombs(ires->argv[2].val.str, "UTF-8")) != NULL)
+ {
+ gtk_tree_store_set(srchmodel, &titer, 0, buf, -1);
+ free(buf);
+ }
+ if((buf = icwcstombs(ires->argv[3].val.str, "UTF-8")) != NULL)
+ {
+ gtk_tree_store_set(srchmodel, &titer, 1, buf, -1);
+ gtk_tree_store_set(srchmodel, &titer, 2, buf, -1);
+ free(buf);
+ }
+ gtk_tree_store_set(srchmodel, &titer, 4, ires->argv[4].val.num, 5, ires->argv[5].val.num, 6, ires->argv[7].val.flnum, 8, -1, -1);
+ }
+ dc_freeires(ires);
+ }
+ break;
+ default:
+ break;
+ }
+ } else if(!wcscmp(resp->cmdname, L"filtercmd")) {
+ if((ksquerytag >= 0) && (ksquerytag == resp->tag))
+ {
+ for(i = 0; i < resp->numlines; i++)
+ {
+ assert((ks = findksentbyseq(i)) != NULL);
+ ks->speed = wcstol(resp->rlines[i].argv[1], NULL, 10);
+ ks->seq = -1;
+ ks->fetched = time(NULL);
+ }
+ ksquerytag = -1;
+ ksupdatecb(NULL);
+ }
+ }
+ dc_freeresp(resp);
+ }
+ updatewrite();
+}
+
+void dcfdcallback(gpointer data, gint source, GdkInputCondition condition)
+{
+ int errnobak;
+
+ if(((condition & GDK_INPUT_READ) && dc_handleread()) || ((condition & GDK_INPUT_WRITE) && dc_handlewrite()))
+ {
+ errnobak = errno;
+ dcdisconnected();
+ if(errnobak == 0)
+ {
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("The server has closed the connection"));
+ } else {
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("The connection to the server failed:\n\n%s"), strerror(errnobak));
+ }
+ return;
+ }
+ handleresps();
+}
+
+void cb_main_dconnmenu_activate(GtkWidget *widget, gpointer data)
+{
+ if(dcfd < 0)
+ return;
+ dc_disconnect();
+ dcdisconnected();
+}
+
+void cb_main_prefmenu_activate(GtkWidget *widget, gpointer data)
+{
+ GtkWidget *dialog, *swnd;
+ int resp;
+
+ dialog = gtk_dialog_new_with_buttons(_("Preferences"), GTK_WINDOW(main_wnd), GTK_DIALOG_MODAL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
+ gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), swnd = create_pref_wnd(), TRUE, TRUE, 0);
+ gtk_entry_set_text(GTK_ENTRY(pref_pubhuburl), pubhubaddr);
+ gtk_entry_set_text(GTK_ENTRY(pref_connectas), connectas);
+ gtk_entry_set_text(GTK_ENTRY(pref_dcserver), dcserver);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pref_autoconn), autoconn);
+ gtk_widget_show(swnd);
+ resp = gtk_dialog_run(GTK_DIALOG(dialog));
+ if(resp == GTK_RESPONSE_ACCEPT)
+ {
+ free(pubhubaddr);
+ pubhubaddr = sstrdup(gtk_entry_get_text(GTK_ENTRY(pref_pubhuburl)));
+ free(connectas);
+ connectas = sstrdup(gtk_entry_get_text(GTK_ENTRY(pref_connectas)));
+ free(dcserver);
+ dcserver = sstrdup(gtk_entry_get_text(GTK_ENTRY(pref_dcserver)));
+ autoconn = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pref_autoconn));
+ }
+ gtk_widget_destroy(dialog);
+}
+
+void dcconnect(char *host)
+{
+ dcfd = dc_connect(host, -1);
+ if(dcfd < 0)
+ {
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not connect:\n\n%s"), strerror(errno));
+ return;
+ }
+ gdkread = gdk_input_add(dcfd, GDK_INPUT_READ, dcfdcallback, NULL);
+ updatewrite();
+ gtk_widget_set_sensitive(main_connmenu, FALSE);
+ gtk_widget_set_sensitive(main_dconnmenu, TRUE);
+ updatesbar(_("Connecting..."));
+}
+
+void cb_main_connmenu_activate(GtkWidget *widget, gpointer data)
+{
+ char *buf;
+
+ if(dcfd >= 0)
+ return;
+ if((buf = inputbox(_("Connect"), _("Server address:"), dcserver, 1)) == NULL)
+ return;
+ dcconnect(buf);
+ free(buf);
+}
+
+void cb_main_sdmenu_activate(GtkWidget *widget, gpointer data)
+{
+ int tag;
+ struct dc_response *resp;
+
+ if(dcfd < 0)
+ {
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server"));
+ return;
+ }
+ tag = dc_queuecmd(NULL, NULL, L"shutdown", NULL);
+ if((resp = dc_gettaggedrespsync(tag)) != NULL)
+ {
+ if(resp->code == 502)
+ msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that"));
+ dc_freeresp(resp);
+ }
+ handleresps();
+}
+
+void cb_main_fnaddr_activate(GtkWidget *widget, gpointer data)
+{
+ int tag;
+ char *buf;
+ struct dc_response *resp;
+
+ if(dcfd < 0)
+ {
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server"));
+ return;
+ }
+ buf = sstrdup(gtk_entry_get_text(GTK_ENTRY(main_fnaddr)));
+ if(strchr(buf, ':') == NULL)
+ {
+ buf = srealloc(buf, strlen(buf) + 5);
+ strcat(buf, ":411");
+ }
+ tag = dc_queuecmd(NULL, NULL, L"cnct", L"dc", L"%%s", buf, NULL);
+ free(buf);
+ if((resp = dc_gettaggedrespsync(tag)) != NULL)
+ {
+ if(resp->code == 502)
+ msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that"));
+ if(resp->code == 509)
+ msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("The server could not parse that address"));
+ dc_freeresp(resp);
+ }
+ gtk_entry_set_text(GTK_ENTRY(main_fnaddr), "");
+ handleresps();
+}
+
+void pubhubfdcallback(gpointer data, gint source, GdkInputCondition condition)
+{
+ static char buf[65536];
+ static int bufpos = 0;
+ int ret, i;
+ char *p, *p2;
+ char *fields[4];
+ wchar_t *wbuf;
+ GtkTreeIter iter;
+ int sorted, sortcol;
+ GtkSortType sortorder;
+ GtkTreeModel *sortmodel;
+
+ if(!(condition & GDK_INPUT_READ))
+ return;
+ if(bufpos == 1024)
+ bufpos = 0;
+ ret = read(pubhubfd, buf + bufpos, sizeof(buf) - bufpos);
+ if(ret <= 0)
+ {
+ if(ret < 0)
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not read from public hub listing process: %s"), strerror(errno));
+ close(pubhubfd);
+ gdk_input_remove(pubhubtag);
+ kill(pubhubproc, SIGINT);
+ pubhubfd = pubhubtag = -1;
+ pubhubproc = 0;
+ bufpos = 0;
+ if(filterpubhub)
+ {
+ regfree(&pubhubfilter);
+ filterpubhub = 0;
+ }
+ return;
+ }
+ bufpos += ret;
+ sortmodel = gtk_tree_view_get_model(GTK_TREE_VIEW(main_phublist));
+ sorted = gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(sortmodel), &sortcol, &sortorder);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(main_phublist), NULL);
+ while((p = memchr(buf, '\n', bufpos)) != NULL)
+ {
+ *(p++) = 0;
+ if(!filterpubhub || !regexec(&pubhubfilter, buf, 0, NULL, 0))
+ {
+ p2 = buf;
+ for(i = 0; i < 4; i++)
+ {
+ fields[i] = p2;
+ if((p2 = strchr(p2, '|')) == NULL)
+ break;
+ *(p2++) = 0;
+ }
+ if(i == 4)
+ {
+ for(i = 0; i < 4; i++)
+ {
+ if((wbuf = icsmbstowcs(fields[i], DCCHARSET, NULL)) == NULL)
+ {
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Could not decode hublist - aborting at this point: %s"), strerror(errno));
+ kill(pubhubproc, SIGINT);
+ break;
+ }
+ if((fields[i] = icwcstombs(wbuf, "UTF-8")) == NULL)
+ break;
+ }
+ if(i == 4)
+ {
+ gtk_list_store_append(pubhubmodel, &iter);
+ gtk_list_store_set(pubhubmodel, &iter, 0, fields[0], 1, fields[1], 2, fields[2], 3, atoi(fields[3]), -1);
+ }
+ for(i--; i >= 0; i--)
+ free(fields[i]);
+ }
+ }
+ memmove(buf, p, bufpos -= p - buf);
+ }
+ sortmodel = gtk_tree_model_sort_new_with_model(GTK_TREE_MODEL(pubhubmodel));
+ if(sorted)
+ gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(sortmodel), sortcol, sortorder);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(main_phublist), sortmodel);
+}
+
+void cb_main_pubhubfilter_activate(GtkWidget *widget, gpointer data)
+{
+ int pipe1[2], pipe2[2];
+ int len, err;
+ const char *buf;
+ char errbuf[1024];
+
+ if(pubhubtag >= 0)
+ gdk_input_remove(pubhubtag);
+ if(pubhubfd >= 0)
+ close(pubhubfd);
+ if(pubhubproc > 0)
+ kill(pubhubproc, SIGINT);
+ if(filterpubhub)
+ {
+ regfree(&pubhubfilter);
+ filterpubhub = 0;
+ }
+ buf = gtk_entry_get_text(GTK_ENTRY(main_pubhubfilter));
+ if(*buf)
+ {
+ if((err = regcomp(&pubhubfilter, buf, REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0)
+ {
+ regerror(err, &pubhubfilter, errbuf, sizeof(errbuf));
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Could not compile regex: %s", errbuf);
+ regfree(&pubhubfilter);
+ filterpubhub = 0;
+ return;
+ }
+ filterpubhub = 1;
+ }
+ gtk_list_store_clear(pubhubmodel);
+ pipe(pipe1);
+ if((pubhubproc = fork()) == 0)
+ {
+ dup2(pipe1[1], 1);
+ close(pipe1[0]);
+ close(pipe1[1]);
+ execlp("wget", "wget", "-qO", "-", pubhubaddr, NULL);
+ perror("wget");
+ exit(127);
+ }
+ close(pipe1[1]);
+ pubhubfd = pipe1[0];
+ len = strlen(pubhubaddr);
+ if((len > 4) && !strcmp(pubhubaddr + len - 4, ".bz2"))
+ {
+ pipe(pipe2);
+ if(fork() == 0)
+ {
+ dup2(pipe1[0], 0);
+ dup2(pipe2[1], 1);
+ close(pipe1[0]);
+ close(pipe2[0]);
+ close(pipe2[1]);
+ execlp("bzcat", "bzcat", NULL);
+ perror("bzcat");
+ exit(127);
+ }
+ close(pipe1[0]);
+ close(pipe2[1]);
+ pubhubfd = pipe2[0];
+ }
+ pubhubtag = gdk_input_add(pubhubfd, GDK_INPUT_READ, pubhubfdcallback, NULL);
+}
+
+void cb_main_dcnctbtn_clicked(GtkWidget *widget, gpointer data)
+{
+ GtkTreeIter iter;
+ int tag, id;
+ struct dc_response *resp;
+
+ if(dcfd < 0)
+ {
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server"));
+ return;
+ }
+ if(!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(main_fnetnodes)), NULL, &iter))
+ {
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("No hub selected"));
+ return;
+ }
+ gtk_tree_model_get(GTK_TREE_MODEL(fnmodel), &iter, 0, &id, -1);
+ tag = dc_queuecmd(NULL, NULL, L"dcnct", L"%%i", id, NULL);
+ if((resp = dc_gettaggedrespsync(tag)) != NULL)
+ {
+ if(resp->code == 502)
+ msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that"));
+ dc_freeresp(resp);
+ }
+ handleresps();
+}
+
+void cb_main_phublist_cchange(GtkWidget *widget, gpointer data)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ char *addr;
+
+ if(!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(main_phublist)), &model, &iter))
+ return;
+ gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 1, &addr, -1);
+ gtk_entry_set_text(GTK_ENTRY(main_fnaddr), addr);
+ g_free(addr);
+}
+
+void cb_main_phublist_activate(GtkWidget *widget, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data)
+{
+ int tag;
+ struct dc_response *resp;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ char *buf;
+
+ if(dcfd < 0)
+ {
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server"));
+ return;
+ }
+ model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
+ if(!gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path))
+ return;
+ gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 1, &buf, -1);
+ if(strchr(buf, ':') == NULL)
+ {
+ buf = g_realloc(buf, strlen(buf) + 5);
+ strcat(buf, ":411");
+ }
+ tag = dc_queuecmd(NULL, NULL, L"cnct", L"dc", L"%%s", buf, NULL);
+ g_free(buf);
+ gtk_entry_set_text(GTK_ENTRY(main_fnaddr), "");
+ if((resp = dc_gettaggedrespsync(tag)) != NULL)
+ {
+ if(resp->code == 502)
+ msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that"));
+ if(resp->code == 509)
+ msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("The server could not parse that address"));
+ dc_freeresp(resp);
+ }
+ handleresps();
+}
+
+void cb_main_chatnodes_activate(GtkWidget *widget, GtkTreePath *path, GtkTreeViewColumn *col, gpointer uudata)
+{
+ GtkTreeIter iter;
+ int id;
+ struct dc_fnetnode *fn;
+ struct fndata *data;
+
+ if(!gtk_tree_model_get_iter(GTK_TREE_MODEL(fnmodel), &iter, path))
+ return;
+ gtk_tree_model_get(GTK_TREE_MODEL(fnmodel), &iter, 0, &id, -1);
+ if((fn = dc_findfnetnode(id)) == NULL)
+ return;
+ data = fn->udata;
+ curchat = id;
+ if(gtk_tree_model_get_iter_first(GTK_TREE_MODEL(fnmodel), &iter))
+ {
+ do
+ {
+ gtk_tree_model_get(GTK_TREE_MODEL(fnmodel), &iter, 0, &id, -1);
+ if(id == curchat)
+ gtk_list_store_set(fnmodel, &iter, 5, "gtk-apply", -1);
+ else
+ gtk_list_store_set(fnmodel, &iter, 5, NULL, -1);
+ } while(gtk_tree_model_iter_next(GTK_TREE_MODEL(fnmodel), &iter));
+ }
+ gtk_text_view_set_buffer(GTK_TEXT_VIEW(main_chatview), GTK_TEXT_BUFFER(data->textbuf));
+}
+
+void cb_main_chatstr_activate(GtkWidget *widget, gpointer data)
+{
+ int tag;
+ const char *buf;
+ struct dc_response *resp;
+
+ if(dcfd < 0)
+ {
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server"));
+ return;
+ }
+ if(curchat < 0)
+ {
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("No hub selected"));
+ return;
+ }
+ buf = gtk_entry_get_text(GTK_ENTRY(main_chatstr));
+ tag = dc_queuecmd(NULL, NULL, L"sendchat", L"%%i", curchat, L"1", L"", L"%%s", buf, NULL);
+ if((resp = dc_gettaggedrespsync(tag)) != NULL)
+ {
+ if(resp->code == 502)
+ msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that"));
+ else if(resp->code == 504)
+ msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("This hub could not support all the types of characters in your chat message"));
+ else if(resp->code == 513)
+ msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("This hub does not support chatting"));
+ else if(resp->code != 200)
+ msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("An error occurred while trying to chat (%i)"), resp->code);
+ dc_freeresp(resp);
+ }
+ gtk_entry_set_text(GTK_ENTRY(main_chatstr), "");
+ handleresps();
+}
+
+void updatesrchfld(const char *simple)
+{
+ char *buf, *s;
+ char *p, *p2;
+ size_t bufsize, bufdata;
+
+ s = sstrdup(simple);
+ buf = NULL;
+ bufsize = bufdata = 0;
+ p = s;
+ do
+ {
+ p2 = strchr(p, ' ');
+ if(p2 != NULL)
+ *(p2++) = 0;
+ if(*p)
+ {
+ if(bufdata > 0)
+ bufcat(buf, " & ", 3);
+ bufcat(buf, "N~", 2);
+ for(; *p; p++)
+ {
+ if(strchr("[]()$^.*?+\\|\"", *p) != NULL)
+ addtobuf(buf, '\\');
+ addtobuf(buf, *p);
+ }
+ }
+ p = p2;
+ } while(p2 != NULL);
+ addtobuf(buf, 0);
+ gtk_entry_set_text(GTK_ENTRY(main_realsrch), buf);
+ free(buf);
+ free(s);
+}
+
+void cb_main_simplesrch_changed(GtkWidget *widget, gpointer data)
+{
+ if(srchautoupdate)
+ return;
+ srchautoupdate = 1;
+ updatesrchfld(gtk_entry_get_text(GTK_ENTRY(main_simplesrch)));
+ srchautoupdate = 0;
+}
+
+void cb_main_realsrch_changed(GtkWidget *widget, gpointer data)
+{
+ if(srchautoupdate)
+ return;
+ srchautoupdate = 1;
+ gtk_entry_set_text(GTK_ENTRY(main_simplesrch), "");
+ srchautoupdate = 0;
+}
+
+void cb_main_srchbtn_clicked(GtkWidget *widget, gpointer data)
+{
+ wchar_t **toks;
+ int tag;
+ struct dc_response *resp;
+ struct dc_intresp *ires;
+
+ if(dcfd < 0)
+ {
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server"));
+ return;
+ }
+ if(nextsrch != -1) /* Impossible case, but oh well... */
+ return;
+ toks = dc_lexsexpr(icsmbstowcs((char *)gtk_entry_get_text(GTK_ENTRY(main_realsrch)), "UTF-8", NULL));
+ if(*toks == NULL)
+ {
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Please enter a search expression before searching"));
+ return;
+ }
+ tag = dc_queuecmd(NULL, NULL, L"search", L"all", L"%%a", toks, NULL);
+ dc_freewcsarr(toks);
+ if((resp = dc_gettaggedrespsync(tag)) != NULL)
+ {
+ if(resp->code == 501)
+ msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("Could not find any hubs to search on"));
+ else if(resp->code == 502)
+ msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that"));
+ else if(resp->code == 509)
+ msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("The server could not parse your search expression"));
+ else if(resp->code != 200)
+ msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("An error occurred while trying to search (%i)"), resp->code);
+ if(resp->code == 200)
+ {
+ if((ires = dc_interpret(resp)) != NULL)
+ {
+ nextsrch = ires->argv[0].val.num;
+ srcheta = time(NULL) + ires->argv[1].val.num;
+ dc_freeires(ires);
+ }
+ gtk_widget_set_sensitive(main_realsrch, FALSE);
+ gtk_widget_set_sensitive(main_simplesrch, FALSE);
+ gtk_widget_set_sensitive(main_srchbtn, FALSE);
+ gtk_widget_set_sensitive(main_srchcanbtn, TRUE);
+ srchstatupdate();
+ }
+ dc_freeresp(resp);
+ }
+ handleresps();
+}
+
+void cb_main_srchcanbtn_clicked(GtkWidget *widget, gpointer data)
+{
+ if(nextsrch == -1)
+ return;
+ dc_queuecmd(NULL, NULL, L"cansrch", L"%%i", nextsrch, NULL);
+ nextsrch = -1;
+ gtk_widget_set_sensitive(main_realsrch, TRUE);
+ gtk_widget_set_sensitive(main_simplesrch, TRUE);
+ gtk_widget_set_sensitive(main_srchbtn, TRUE);
+ gtk_widget_set_sensitive(main_srchcanbtn, FALSE);
+ srchstatupdate();
+}
+
+void cb_main_trlist_keypress(GtkWidget *widget, GdkEventKey *event, gpointer data)
+{
+ int id, tag;
+ GtkTreeSelection *sel;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ struct dc_response *resp;
+
+ if((event->type == GDK_KEY_PRESS) && (event->keyval == GDK_Delete))
+ {
+ sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
+ if(gtk_tree_selection_get_selected(sel, &model, &iter))
+ {
+ gtk_tree_model_get(model, &iter, 0, &id, -1);
+ tag = dc_queuecmd(NULL, NULL, L"cancel", L"%%i", id, NULL);
+ if((resp = dc_gettaggedrespsync(tag)) != NULL)
+ {
+ if(resp->code == 502)
+ msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that"));
+ else if(resp->code != 200)
+ msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("An error occurred while trying to cancel (%i)"), resp->code);
+ dc_freeresp(resp);
+ }
+ handleresps();
+ }
+ }
+}
+
+void cb_main_srchres_activate(GtkWidget *widget, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data)
+{
+ int tag;
+ struct dc_response *resp;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ int size, num;
+ char *tfnet, *tpeerid, *tfilename, *arg;
+ wchar_t *fnet, *peerid, *filename;
+
+ if(dcfd < 0)
+ {
+ msgbox(GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Not connected to DC server"));
+ return;
+ }
+ model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
+ if(!gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, path))
+ return;
+ gtk_tree_model_get(model, &iter, 7, &num, -1);
+ if(num > 0)
+ return;
+ gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 0, &tfnet, 1, &tpeerid, 3, &tfilename, 4, &size, -1);
+ fnet = icmbstowcs(tfnet, "UTF-8");
+ peerid = icmbstowcs(tpeerid, "UTF-8");
+ filename = icmbstowcs(tfilename, "UTF-8");
+ if((fnet == NULL) || (peerid == NULL) || (filename == NULL))
+ {
+ if(fnet != NULL)
+ free(fnet);
+ if(peerid != NULL)
+ free(peerid);
+ if(filename != NULL)
+ free(filename);
+ g_free(tfnet);
+ g_free(tpeerid);
+ g_free(tfilename);
+ return;
+ }
+ g_free(tfnet);
+ g_free(tpeerid);
+ g_free(tfilename);
+ arg = (char *)gtk_entry_get_text(GTK_ENTRY(main_dlarg));
+ if(*arg)
+ tag = dc_queuecmd(NULL, NULL, L"download", fnet, L"%%ls", peerid, L"%%ls", filename, L"%%i", size, L"user", L"%%s", arg, NULL);
+ else
+ tag = dc_queuecmd(NULL, NULL, L"download", fnet, L"%%ls", peerid, L"%%ls", filename, L"%%i", size, NULL);
+ free(fnet);
+ free(peerid);
+ free(filename);
+ if((resp = dc_gettaggedrespsync(tag)) != NULL)
+ {
+ if(resp->code == 502)
+ msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("You do not have permission to do that"));
+ if(resp->code != 200)
+ msgbox(GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, _("An error occurred while trying to queue the download (%i)"), resp->code);
+ dc_freeresp(resp);
+ }
+ handleresps();
+}
+
+gboolean srchfilterfunc(GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
+{
+ int slots;
+ int filteratall;
+
+ filteratall = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(main_filternoslots));
+ if(!filteratall)
+ return(TRUE);
+ gtk_tree_model_get(model, iter, 5, &slots, -1);
+ if(slots < 1)
+ return(FALSE);
+ return(TRUE);
+}
+
+void cb_main_filternoslots_toggled(GtkToggleButton *widget, gpointer data)
+{
+ gtk_tree_model_filter_refilter(srchmodelfilter);
+}
+
+void srchstatupdate(void)
+{
+ char buf[1024];
+
+ if(nextsrch == -1)
+ {
+ snprintf(buf, 1024, _("Ready to search"));
+ } else {
+ snprintf(buf, 1024, _("Search scheduled and will be submitted in %i seconds"), (int)(srcheta - time(NULL)));
+ }
+ if(strcmp(gtk_label_get_text(GTK_LABEL(main_srchstatus)), buf))
+ gtk_label_set_text(GTK_LABEL(main_srchstatus), buf);
+}
+
+gint srchstatupdatecb(gpointer data)
+{
+ srchstatupdate();
+ return(TRUE);
+}
+
+void initchattags(void)
+{
+ GtkTextTag *tag;
+
+ chattags = gtk_text_tag_table_new();
+ tag = gtk_text_tag_new("sender");
+ g_object_set(tag, "foreground", "blue", NULL);
+ gtk_text_tag_table_add(chattags, tag);
+}
+
+int main(int argc, char **argv)
+{
+ GtkWidget *wnd;
+ PangoFontDescription *monospacefont;
+ GtkTreeModel *sortmodel;
+ struct passwd *pwent;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ gtk_init(&argc, &argv);
+ dc_init();
+ signal(SIGCHLD, SIG_IGN);
+ pubhubaddr = sstrdup("http://www.neo-modus.com/PublicHubList.config");
+ dcserver = sstrdup("localhost");
+ if((pwent = getpwuid(getuid())) == NULL)
+ {
+ fprintf(stderr, "could not get your passwd data");
+ exit(1);
+ }
+ connectas = sstrdup(pwent->pw_name);
+ wnd = create_main_wnd();
+ initchattags();
+
+ fnmodel = gtk_list_store_new(6, G_TYPE_INT, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(main_fnetnodes), GTK_TREE_MODEL(fnmodel));
+ gtk_tree_view_set_model(GTK_TREE_VIEW(main_chatnodes), GTK_TREE_MODEL(fnmodel));
+
+ pubhubmodel = gtk_list_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT);
+ sortmodel = gtk_tree_model_sort_new_with_model(GTK_TREE_MODEL(pubhubmodel));
+ gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(sortmodel), 3, GTK_SORT_DESCENDING);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(main_phublist), GTK_TREE_MODEL(sortmodel));
+ g_object_unref(sortmodel);
+
+ dlmodel = gtk_list_store_new(12, G_TYPE_INT, /* id */
+ G_TYPE_INT, /* dir */
+ G_TYPE_INT, /* state */
+ G_TYPE_STRING, /* peerid */
+ G_TYPE_STRING, /* peernick */
+ G_TYPE_STRING, /* path */
+ G_TYPE_INT, /* size */
+ G_TYPE_INT, /* curpos */
+ G_TYPE_STRING, /* stock */
+ G_TYPE_FLOAT, /* percentage */
+ G_TYPE_INT, /* error */
+ G_TYPE_INT); /* errortime */
+ gtk_tree_view_set_model(GTK_TREE_VIEW(main_downloads), GTK_TREE_MODEL(dlmodel));
+
+ ulmodel = gtk_list_store_new(12, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_STRING, G_TYPE_FLOAT, G_TYPE_INT, G_TYPE_INT);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(main_uploads), GTK_TREE_MODEL(ulmodel));
+
+ srchmodel = gtk_tree_store_new(9, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT, G_TYPE_DOUBLE, G_TYPE_INT, G_TYPE_INT);
+ srchmodelfilter = GTK_TREE_MODEL_FILTER(gtk_tree_model_filter_new(GTK_TREE_MODEL(srchmodel), NULL));
+ gtk_tree_model_filter_set_visible_func(srchmodelfilter, srchfilterfunc, NULL, NULL);
+ sortmodel = gtk_tree_model_sort_new_with_model(GTK_TREE_MODEL(srchmodelfilter));
+ gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(sortmodel), 4, GTK_SORT_DESCENDING);
+ gtk_tree_view_set_model(GTK_TREE_VIEW(main_srchres), GTK_TREE_MODEL(sortmodel));
+ g_object_unref(sortmodel);
+
+ monospacefont = pango_font_description_from_string("Monospace 10");
+ gtk_widget_modify_font(main_chatview, monospacefont);
+ pango_font_description_free(monospacefont);
+ readconfigfile();
+ updatesbar(_("Disconnected"));
+ gtk_widget_show(wnd);
+ if(autoconn)
+ dcconnect(dcserver);
+ g_timeout_add(500, srchstatupdatecb, NULL);
+ g_timeout_add(5000, ksupdatecb, NULL);
+ gtk_main();
+ return(0);
+}
--- /dev/null
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+;prefix: main_
+;hasaccels: y
+;hascolumns: y
+;hasrenderers: y
+:wnd name: wnd title: "Dolda Connect" var: y sig(delete_event):initdeath
+ :vbox
+ :menubar
+ :menuitem label: _Main
+ :menu
+ $menuitem name: connmenu label: _Connect sig: activate accel: "CONTROL+C" var: y
+ $menuitem name: dconnmenu label: _Disconnect sig: activate accel: "CONTROL+D" var: y sensitive: FALSE
+ $menusep
+ $menuitem name: sdmenu label: "_Shut down daemon" sig: activate
+ $smenuitem stock: QUIT sig(activate):initdeath
+ end
+ end
+ :menuitem label: Op_tions
+ :menu
+ $smenuitem name: prefmenu stock: PREFERENCES sig: activate
+ end
+ end
+ end
+ :vpaned name: pane1 var: y expand: TRUE fill: TRUE resize: 1
+ :notebook
+ :vpaned name: pane2 var: y nblabel: "_Hub connections" resize: 2
+ :vbox
+ $mlbl label: "Connected hu_bs" mwidget: fnetnodes
+ :sw expand: TRUE fill: TRUE
+ :treeview name: fnetnodes var: y rules: TRUE
+ :tvcol title: "Hub name" sortcol: 1 resizable: TRUE
+ $pixbufrend stock_id: 4
+ $textrend text: 1
+ end
+ $tvcol title: "# users" text: 3 sortcol: 3 resizable: TRUE
+ end
+ end
+ $btn name: dcnctbtn label: "D_isconnect" sig: clicked
+ end
+ :vbox
+ $mlbl label: "_Public hub list" mwidget: phublist
+ :sw expand: TRUE fill: TRUE
+ :treeview name: phublist var: y rules: TRUE searchcol: 0 sig(cursor-changed): cb_main_phublist_cchange sig(row-activated): cb_main_phublist_activate
+ $tvcol title: "# users" text: 3 sortcol: 3 resizable: TRUE
+ $tvcol title: "Name" text: 0 sortcol: 0 resizable: TRUE
+ $tvcol title: "Description" text: 2 sortcol: 2 resizable: TRUE
+ end
+ end
+ :table rows: 2 cols: 3 fill: TRUE
+ $mlbl label: "_Filter:" tx: 0 ty: 0 mwidget: pubhubfilter
+ $text name: pubhubfilter var: y expand: y fill: y sig: activate tx: 1 ty: 0
+ $btn label: "_Get public hub list" sig(clicked): cb_main_pubhubfilter_activate tx: 2 ty: 0
+ $mlbl label: "_Address:" tx: 0 ty: 1 mwidget: fnaddr
+ $text name: fnaddr var: y expand: y fill: y sig: activate tx: 1 ty: 1
+ $btn label: "C_onnect" sig(clicked): cb_main_fnaddr_activate tx: 2 ty: 1
+ end
+ end
+ end
+ :vpaned name: pane3 var: y nblabel: "_Chat" resize: 2
+ :vbox
+ $mlbl label: "Hu_bs" mwidget: chatnodes
+ :sw fill: TRUE expand: TRUE
+ :treeview name: chatnodes var: y rules: TRUE sig(row-activated): cb_main_chatnodes_activate
+ :tvcol title: "Hub name" sortcol: 1
+ $pixbufrend stock_id: 5
+ $textrend text: 1
+ end
+ end
+ end
+ end
+ :vbox
+ :sw fill: TRUE expand: TRUE
+ $textview name: chatview var: y editable: FALSE
+ end
+ :hbox
+ $mlbl label: "Chat st_ring:" mwidget: chatstr
+ $text name: chatstr var: y expand: TRUE fill: TRUE sig: activate
+ $btn label: "S_end" sig(clicked): cb_main_chatstr_activate
+ end
+ end
+ end
+ :vbox nblabel: "_Search"
+ :hbox
+ $mlbl label: "S_imple search:" mwidget: simplesrch
+ $text name: simplesrch var: y expand: TRUE fill: TRUE sig: changed sig(activate): cb_main_srchbtn_clicked
+ $btn name: srchbtn var: y label: "S_earch" sig: clicked
+ $btn name: srchcanbtn var: y label: "C_ancel" sig: clicked sensitive: FALSE
+ end
+ $chk name: filternoslots var: y label: "Displa_y results with free slots only" sig: toggled
+ :exp label: "Ad_vanced" name: advexp var: y
+ :table rows: 2 cols: 2
+ $mlbl tx: 0 ty: 0 label: "C_omplete search expression:" mwidget: realsrch
+ $text tx: 1 ty: 0 name: realsrch var: y expand: y fill: y sig: changed sig(activate): cb_main_srchbtn_clicked
+ $mlbl tx: 0 ty: 1 label: "Filter ar_gument:" mwidget: dlarg
+ $text tx: 1 ty: 1 name: dlarg var: y expand: y fill: y
+ end
+ end
+ $lbl name: srchstatus var: y label: "Ready to search" fill: TRUE
+ $hr pad: 5
+ $mlbl label: "Search _results:" mwidget: srchres
+ :sw expand: TRUE fill: TRUE
+ :treeview name: srchres var: y rules: TRUE searchcol: 3 sig(row-activated): cb_main_srchres_activate
+ :tvcol title: "#" sortcol: 7 resizable: FALSE
+ $textrend func: hidezerofunc funcdata: "(gpointer)7"
+ end
+ $tvcol title: "Peer name" text: 2 sortcol: 2 resizable: TRUE
+ $tvcol title: "File name" text: 3 sortcol: 3 resizable: TRUE expander: y
+ :tvcol title: "Size" sortcol: 4 resizable: TRUE
+ $textrend func: transnicebytefunc funcdata: "(gpointer)4"
+ end
+ $tvcol title: "Slots" text: 5 sortcol: 5 resizable: TRUE
+ :tvcol title: "Known speed" sortcol: 8 resizable: TRUE
+ $textrend func: speedtimefunc
+ end
+ $tvcol title: "Rsp. time" text: 6 sortcol: 6 resizable: TRUE
+ end
+ end
+ end
+ end
+ :notebook
+ :vbox nblabel: "_Downloads"
+ $mlbl label: "_List of downloads:" mwidget: downloads
+ :sw fill: TRUE expand: TRUE
+ :treeview name: downloads var: y sig(key-press-event): cb_main_trlist_keypress
+ :tvcol title: "User Name"
+ $pixbufrend stock_id: 8
+ $textrend text: 4
+ end
+ $tvcol title: "File Name" text: 5
+ :tvcol title: "Size"
+ $textrend func: transnicebytefunc funcdata: "(gpointer)6"
+ end
+ :tvcol title: "Position"
+#ifdef ENABLE_GTK2PBAR
+ $custrend newfunc: custom_cell_renderer_progress_new attr(percentage): 9
+#else
+ $textrend func: percentagefunc funcdata: "(gpointer)9" expand: FALSE
+#endif
+ $textrend func: transnicebytefunc funcdata: "(gpointer)7"
+ end
+ :tvcol title: "Error"
+ $textrend func: transerrorinfo
+ end
+ end
+ end
+ end
+ :vbox nblabel: "_Uploads"
+ $mlbl label: "_List of uploads:" mwidget: uploads
+ :sw fill: TRUE expand: TRUE
+ :treeview name: uploads var: y sig(key-press-event): cb_main_trlist_keypress
+ :tvcol title: "User Name"
+ $pixbufrend stock_id: 8
+ $textrend text: 4
+ end
+ $tvcol title: "File Name" text: 5
+ :tvcol title: "Size"
+ $textrend func: transnicebytefunc funcdata: "(gpointer)6"
+ end
+ :tvcol title: "Position"
+#ifdef ENABLE_GTK2PBAR
+ $custrend newfunc: custom_cell_renderer_progress_new attr(percentage): 9
+#else
+ $textrend func: percentagefunc funcdata: "(gpointer)9" expand: FALSE
+#endif
+ $textrend func: transnicebytefunc funcdata: "(gpointer)7"
+ end
+ end
+ end
+ end
+ end
+ end
+ $sbar var: y name: statusbar
+ end
+end
--- /dev/null
+#!/usr/bin/perl
+
+$tempvar = 0;
+
+sub printwidgets
+{
+ my($widget, $sl, $p, $sig, $cb, $data, $pf, $cpf, $mod, $key, @delayedlines);
+ $sl = $_[1];
+ $p = " " . (" " x $sl);
+ $cpf = $_[2];
+ @delayedlines = ();
+ foreach $widget (@{$_[0]})
+ {
+ if($widget->{"type"} eq "wnd")
+ {
+ print "${p}stack[$sl] = gtk_window_new(GTK_WINDOW_TOPLEVEL);\n";
+ if($options{"hasaccels"}) {
+ print "${p}gtk_window_add_accel_group(GTK_WINDOW(stack[$sl]), accel_group);\n";
+ }
+ if($widget->{"title"}) {
+ print "${p}gtk_window_set_title(GTK_WINDOW(stack[$sl]), \"" . $widget->{"title"} . "\");\n";
+ }
+ $pf = sub
+ {
+ my($widget, $p, $sl) = @_;
+ print "${p}gtk_container_add(GTK_CONTAINER(stack[" . ($sl - 1) . "]), stack[$sl]);\n";
+ }
+ } elsif($widget->{"type"} =~ /[hv]box/) {
+ print "${p}stack[$sl] = gtk_" . $widget->{"type"} . "_new(";
+ print $widget->{"homo"}?"TRUE, ":"FALSE, ";
+ print $widget->{"spacing"} || "0";
+ print ");\n";
+ $pf = sub
+ {
+ my($widget, $p, $sl) = @_;
+ print "${p}gtk_box_pack_start(GTK_BOX(stack[" . ($sl - 1) . "]), stack[$sl], ";
+ print (($widget->{"expand"} || $widget->{"parent"}->{"dexpand"})?"TRUE, ":"FALSE, ");
+ print (($widget->{"fill"} || $widget->{"parent"}->{"dfill"})?"TRUE, ":"FALSE, ");
+ print $widget->{"pad"} || "0";
+ print ");\n";
+ }
+ } elsif($widget->{"type"} eq "table") {
+ print "${p}stack[$sl] = gtk_table_new(" . $widget->{"rows"} . ", " . $widget->{"cols"};
+ print ", " . (($widget->{"homo"} eq "TRUE")?"TRUE":"FALSE");
+ print ");\n";
+ $pf = sub
+ {
+ my($widget, $p, $sl) = @_;
+ print "${p}gtk_table_attach(GTK_TABLE(stack[" . ($sl - 1) . "]), stack[$sl]";
+ print ", " . $widget->{"tx"};
+ print ", " . ($widget->{"tx"} + (defined($widget->{"tw"})?$widget->{"tw"}:1));
+ print ", " . $widget->{"ty"};
+ print ", " . ($widget->{"ty"} + (defined($widget->{"th"})?$widget->{"th"}:1));
+ if($widget->{"fill"} eq "y") {
+ $widget->{"fillx"} = "y";
+ $widget->{"filly"} = "y";
+ }
+ if($widget->{"shrink"} eq "y") {
+ $widget->{"shrinkx"} = "y";
+ $widget->{"shrinky"} = "y";
+ }
+ if($widget->{"expand"} eq "y") {
+ $widget->{"expandx"} = "y";
+ $widget->{"expandy"} = "y";
+ }
+ print ", 0";
+ print " | GTK_FILL" if $widget->{"fillx"} eq "y";
+ print " | GTK_SHRINK" if $widget->{"shrinkx"} eq "y";
+ print " | GTK_EXPAND" if $widget->{"expandx"} eq "y";
+ print ", 0";
+ print " | GTK_FILL" if $widget->{"filly"} eq "y";
+ print " | GTK_SHRINK" if $widget->{"shrinky"} eq "y";
+ print " | GTK_EXPAND" if $widget->{"expandy"} eq "y";
+ print ", " . (defined($widget->{"padx"})?$widget->{"padx"}:"0");
+ print ", " . (defined($widget->{"pady"})?$widget->{"pady"}:"0");
+ print ");\n";
+ }
+ } elsif($widget->{"type"} eq "btn") {
+ $widget->{"label"} || die("Can't have button without label\n");
+ print "${p}stack[$sl] = gtk_button_new_with_mnemonic(_(\"" . $widget->{"label"} . "\"));\n";
+ } elsif($widget->{"type"} eq "chk") {
+ $widget->{"label"} || die("Can't have check button without label\n");
+ print "${p}stack[$sl] = gtk_check_button_new_with_mnemonic(_(\"" . $widget->{"label"} . "\"));\n";
+ } elsif($widget->{"type"} eq "sbtn") {
+ $widget->{"stock"} || die("Can't have button without stock\n");
+ print "${p}stack[$sl] = gtk_button_new_from_stock(GTK_STOCK_" . $widget->{"stock"} . ");\n";
+ } elsif($widget->{"type"} eq "simg") {
+ $widget->{"stock"} || die("Can't have image without stock\n");
+ $widget->{"size"} || die("Can't have image without size\n");
+ print "${p}stack[$sl] = gtk_image_new_from_stock(GTK_STOCK_" . $widget->{"stock"} . ", GTK_ICON_SIZE_" . $widget->{"size"} . ");\n";
+ } elsif($widget->{"type"} eq "lbl") {
+ $widget->{"label"} || die("Can't have label without label\n");
+ print "${p}stack[$sl] = gtk_label_new(_(\"" . $widget->{"label"} . "\"));\n";
+ } elsif($widget->{"type"} eq "mlbl") {
+ $widget->{"label"} || die("Can't have label without label\n");
+ print "${p}stack[$sl] = gtk_label_new_with_mnemonic(_(\"" . $widget->{"label"} . "\"));\n";
+ if(defined($widget->{"mwidget"}))
+ {
+ if($widget->{"var"} ne "y") {
+ $widget->{"var"} = "l";
+ }
+ if(!defined($widget->{"name"})) {
+ $widget->{"name"} = "temp" . $tempvar++;
+ }
+ $str = "gtk_label_set_mnemonic_widget(GTK_LABEL(";
+ if($widget->{"var"} eq "y") {
+ $str .= $options{"prefix"};
+ }
+ $str .= $widget->{"name"};
+ $str .= "), " . $options{"prefix"} . $widget->{"mwidget"} . ");";
+ push @delayedlines, ($str);
+ }
+ } elsif($widget->{"type"} eq "text") {
+ print "${p}stack[$sl] = gtk_entry_new();\n";
+ if($widget->{"default"}) {
+ print "${p}gtk_entry_set_text(GTK_ENTRY(stack[$sl]), \"" . $widget->{"default"} . "\");\n";
+ }
+ } elsif($widget->{"type"} eq "menubar") {
+ print "${p}stack[$sl] = gtk_menu_bar_new();\n";
+ $pf = sub
+ {
+ my($widget, $p, $sl) = @_;
+ print "${p}gtk_menu_shell_append(GTK_MENU_SHELL(stack[" . ($sl - 1) . "]), stack[$sl]);\n";
+ }
+ } elsif($widget->{"type"} eq "menuitem") {
+ print "${p}stack[$sl] = gtk_menu_item_new_with_mnemonic(_(\"" . $widget->{"label"} . "\"));\n";
+ $pf = sub
+ {
+ my($widget, $p, $sl) = @_;
+ print "${p}gtk_menu_item_set_submenu(GTK_MENU_ITEM(stack[" . ($sl - 1) . "]), stack[$sl]);\n";
+ }
+ } elsif($widget->{"type"} eq "smenuitem") {
+ print "${p}stack[$sl] = gtk_image_menu_item_new_from_stock(GTK_STOCK_" . $widget->{"stock"} . ", accel_group);\n";
+ $pf = sub
+ {
+ my($widget, $p, $sl) = @_;
+ print "${p}gtk_menu_item_set_submenu(GTK_MENU_ITEM(stack[" . ($sl - 1) . "]), stack[$sl]);\n";
+ }
+ } elsif($widget->{"type"} eq "menusep") {
+ print "${p}stack[$sl] = gtk_separator_menu_item_new();\n";
+ } elsif($widget->{"type"} eq "menu") {
+ print "${p}stack[$sl] = gtk_menu_new();\n";
+ if($options{"hasaccels"}) {
+ print "${p}gtk_menu_set_accel_group(GTK_MENU(stack[$sl]), accel_group);\n";
+ }
+ $pf = sub
+ {
+ my($widget, $p, $sl) = @_;
+ print "${p}gtk_menu_shell_append(GTK_MENU_SHELL(stack[" . ($sl - 1) . "]), stack[$sl]);\n";
+ };
+ $widget->{"noshow"} = 1;
+ } elsif($widget->{"type"} =~ /^[hv]paned$/) {
+ print "${p}stack[$sl] = gtk_" . $widget->{"type"} . "_new();\n";
+ $widget->{"cur"} = 1;
+ $pf = sub
+ {
+ my($widget, $p, $sl) = @_;
+ print "${p}gtk_paned_pack" . ($widget->{"parent"}->{"cur"}) . "(GTK_PANED(stack[" . ($sl - 1) . "]), stack[$sl]";
+ print ", " . ((index($widget->{"parent"}->{"resize"}, $widget->{"parent"}->{"cur"}) < 0)?"FALSE":"TRUE");
+ print ", " . ((index($widget->{"parent"}->{"shrink"}, $widget->{"parent"}->{"cur"}) < 0)?"FALSE":"TRUE");
+ print ");\n";
+ $widget->{"parent"}->{"cur"}++;
+ }
+ } elsif($widget->{"type"} eq "notebook") {
+ print "${p}stack[$sl] = gtk_notebook_new();\n";
+ $pf = sub
+ {
+ my($widget, $p, $sl) = @_;
+ print "${p}gtk_notebook_append_page(GTK_NOTEBOOK(stack[" . ($sl - 1) . "]), stack[$sl]";
+ print ", gtk_label_new_with_mnemonic(_(\"" . $widget->{"nblabel"} . "\"))";
+ print ");\n";
+ }
+ } elsif($widget->{"type"} eq "sw") {
+ print "${p}stack[$sl] = gtk_scrolled_window_new(NULL, NULL);\n";
+ $pf = sub
+ {
+ my($widget, $p, $sl) = @_;
+ print "${p}gtk_container_add(GTK_CONTAINER(stack[" . ($sl - 1) . "]), stack[$sl]);\n";
+ }
+ } elsif($widget->{"type"} eq "frame") {
+ print "${p}stack[$sl] = gtk_frame_new(_(\"" . $widget->{"label"} . "\"));\n";
+ $pf = sub
+ {
+ my($widget, $p, $sl) = @_;
+ print "${p}gtk_container_add(GTK_CONTAINER(stack[" . ($sl - 1) . "]), stack[$sl]);\n";
+ }
+ } elsif($widget->{"type"} eq "exp") {
+ print "${p}stack[$sl] = gtk_expander_new_with_mnemonic(_(\"" . $widget->{"label"} . "\"));\n";
+ $pf = sub
+ {
+ my($widget, $p, $sl) = @_;
+ print "${p}gtk_container_add(GTK_CONTAINER(stack[" . ($sl - 1) . "]), stack[$sl]);\n";
+ }
+ } elsif($widget->{"type"} eq "treeview") {
+ print "${p}stack[$sl] = gtk_tree_view_new();\n";
+ if(defined($widget->{"hvis"})) {
+ print "${p}gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(stack[$sl]), " . $widget->{"hvis"} . ");\n";
+ }
+ if(defined($widget->{"rules"})) {
+ print "${p}gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(stack[$sl]), " . $widget->{"rules"} . ");\n";
+ }
+ if(defined($widget->{"searchcol"})) {
+ print "${p}gtk_tree_view_set_search_column(GTK_TREE_VIEW(stack[$sl]), " . $widget->{"searchcol"} . ");\n";
+ print "${p}gtk_tree_view_set_enable_search(GTK_TREE_VIEW(stack[$sl]), TRUE);\n";
+ }
+ $pf = sub
+ {
+ my($widget, $p, $sl) = @_;
+ print "${p}gtk_tree_view_append_column(GTK_TREE_VIEW(stack[" . ($sl - 1) . "]), column);\n";
+ if($widget->{"expander"} eq "y") {
+ print "${p}gtk_tree_view_set_expander_column(GTK_TREE_VIEW(stack[" . ($sl - 1) . "]), column);\n";
+ }
+ }
+ } elsif($widget->{"type"} eq "tvcol") {
+ if(!defined($widget->{"subwidgets"}))
+ {
+ print "${p}column = gtk_tree_view_column_new_with_attributes(";
+ print "_(\"" . $widget->{"title"} . "\")";
+ print ", gtk_cell_renderer_text_new()";
+ if(defined($widget->{"text"})) {
+ print ", \"text\", " . $widget->{"text"};
+ }
+ print ", NULL);\n";
+ } else {
+ print "${p}column = gtk_tree_view_column_new();\n";
+ print "${p}gtk_tree_view_column_set_title(column, _(\"" . $widget->{"title"} . "\"));\n";
+ }
+ if(defined($widget->{"sortcol"})) {
+ print "${p}gtk_tree_view_column_set_sort_column_id(column, " . $widget->{"sortcol"} . ");\n";
+ }
+ if(defined($widget->{"resizable"})) {
+ print "${p}gtk_tree_view_column_set_resizable(column, " . $widget->{"resizable"} . ");\n";
+ }
+ $widget->{"noshow"} = 1;
+ $pf = sub
+ {
+ }
+ } elsif($widget->{"type"} eq "textrend") {
+ print "${p}renderer = gtk_cell_renderer_text_new();\n";
+ print "${p}gtk_tree_view_column_pack_start(column, renderer, " . (defined($widget->{"expand"})?$widget->{"expand"}:"TRUE") . ");\n";
+ if(defined($widget->{"text"})) {
+ print "${p}gtk_tree_view_column_add_attribute(column, renderer, \"text\", " . $widget->{"text"} . ");\n";
+ }
+ if(defined($widget->{"func"})) {
+ print "${p}gtk_tree_view_column_set_cell_data_func(column, renderer, " . $widget->{"func"} . ", " . ($widget->{"funcdata"} || "NULL") . ", NULL);\n";
+ }
+ $widget->{"noshow"} = 1;
+ } elsif($widget->{"type"} eq "custrend") {
+ print "${p}renderer = GTK_CELL_RENDERER(" . $widget->{"newfunc"} . "());\n";
+ print "${p}gtk_tree_view_column_pack_start(column, renderer, " . (defined($widget->{"expand"})?$widget->{"expand"}:"FALSE") . ");\n";
+ foreach $attr (keys %{$widget})
+ {
+ if($attr =~ /attr\((\S+)\)/)
+ {
+ print "${p}gtk_tree_view_column_add_attribute(column, renderer, \"" . $1 . "\", " . $widget->{$attr} . ");\n";
+ }
+ }
+ $widget->{"noshow"} = 1;
+ } elsif($widget->{"type"} eq "pixbufrend") {
+ print "${p}renderer = gtk_cell_renderer_pixbuf_new();\n";
+ print "${p}gtk_tree_view_column_pack_start(column, renderer, FALSE);\n";
+ if(defined($widget->{"stock_id"})) {
+ print "${p}gtk_tree_view_column_add_attribute(column, renderer, \"stock_id\", " . $widget->{"stock_id"} . ");\n";
+ }
+ $widget->{"noshow"} = 1;
+ } elsif($widget->{"type"} eq "textview") {
+ print "${p}stack[$sl] = gtk_text_view_new();\n";
+ if(defined($widget->{"editable"})) {
+ print "${p}gtk_text_view_set_editable(GTK_TEXT_VIEW(stack[$sl]), " . $widget->{"editable"} . ");\n";
+ }
+ } elsif($widget->{"type"} eq "hr") {
+ print "${p}stack[$sl] = gtk_hseparator_new();\n";
+ } elsif($widget->{"type"} eq "sbar") {
+ print "${p}stack[$sl] = gtk_statusbar_new();\n";
+ if($widget->{"grip"} eq "n") {
+ print "${p}gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(stack[$sl]), FALSE);\n";
+ }
+ } else {
+ print STDERR "Unknown widget: " . $widget->{"type"} ."\n";
+ }
+ if($widget->{"sensitive"}) {
+ print "${p}gtk_widget_set_sensitive(stack[$sl], " . $widget->{"sensitive"} . ");\n";
+ }
+ if($widget->{"var"} eq "y") {
+ print $p . $options{"prefix"} . $widget->{"name"} . " = stack[$sl];\n";
+ }
+ if($widget->{"var"} eq "l") {
+ print $p . "GtkWidget *" . $widget->{"name"} . " = stack[$sl];\n";
+ }
+ if($widget->{"sig"})
+ {
+ while($widget->{"sig"} =~ /\G([\w_]+),?/g) {
+ print "${p}g_signal_connect(G_OBJECT(stack[$sl]), \"$1\", G_CALLBACK(cb_" . $options{"prefix"} . $widget->{"name"} . "_" . $1 . "), (gpointer)NULL);\n";
+ }
+ }
+ if($widget->{"accel"})
+ {
+ $mod = "";
+ while($widget->{"accel"} =~ /\G(\w+)\+/gc)
+ {
+ $mod .= " | " if($mod);
+ $mod = $mod . "GDK_" . $1 . "_MASK";
+ }
+ $mod || ($mod = "0");
+ $widget->{"accel"} =~ /\G(\w+)/g;
+ $key = $1;
+ print "${p}gtk_widget_add_accelerator(stack[$sl], \"activate\", accel_group, GDK_$key, $mod, GTK_ACCEL_VISIBLE);\n";
+ }
+ foreach $attr (keys %{$widget})
+ {
+ if($attr =~ /^sig\((\S+)\)/)
+ {
+ $sig = $1;
+ if($widget->{$attr} =~ /([^,]*),(.*)/)
+ {
+ $cb = $1;
+ $data = $2;
+ } else {
+ $cb = $widget->{$attr};
+ $data = "NULL";
+ }
+ print "${p}g_signal_connect(G_OBJECT(stack[$sl]), \"$1\", G_CALLBACK($cb), (gpointer)$data);\n";
+ }
+ }
+ if($widget->{"subwidgets"})
+ {
+ print "$p\n";
+ printwidgets($widget->{"subwidgets"}, $sl + 1, $pf);
+ }
+ if($sl > 0)
+ {
+ &$cpf($widget, $p, $sl);
+ if(!$widget->{"noshow"}) {
+ print "${p}gtk_widget_show(stack[$sl]);\n";
+ }
+ }
+ print "$p\n";
+ }
+ foreach $line (@delayedlines)
+ {
+ print $p . $line . "\n";
+ }
+}
+
+sub printvars
+{
+ my($widget);
+ foreach $widget (@{$_[0]})
+ {
+ if($widget->{"var"})
+ {
+ print "GtkWidget *" . $options{"prefix"} . $widget->{"name"} .";\n";
+ }
+ printvars($widget->{"subwidgets"}) if($widget->{"subwidgets"});
+ }
+}
+
+sub dequote
+{
+ my($text);
+ ($text) = @_;
+ $text =~ s/([^\\]|^)\"/$1/g;
+ $text =~ s/\\(.)/$1/g;
+ return $text;
+}
+
+$rootwidgets = [];
+@estack = ($rootwidgets);
+@wstack = ();
+$curwidget = 0;
+$maxstack = 1;
+
+while(<>)
+{
+ chomp;
+ s/(^|\s+)\#.*$//;
+ s/^\s*//;
+ if(/^;\s*(\w+)\s*:\s*(\w.*)/)
+ {
+ $options{$1} = $2;
+ } elsif(/^([:\$])\s*(\w+)/g) {
+ $curwidget = {"type" => $2};
+ push @{$estack[0]}, $curwidget;
+ if((scalar @wstack) > 0) {
+ $curwidget->{"parent"} = $wstack[0];
+ }
+ if($1 eq ":")
+ {
+ unshift @estack, ($curwidget->{"subwidgets"} = []);
+ unshift @wstack, $curwidget;
+ }
+ $maxstack = (scalar @estack) if((scalar @estack) > $maxstack);
+ while(/\G\s*(\S+)\s*:\s*((\w+|\"([^\\\"]+|\\.)*([^\\]|)\"|\\.)+)/g)
+ {
+ $curwidget->{$1} = dequote($2);
+ }
+ } elsif(/^%\s*(\S+)\s*:\s*((\w+|\"([^\\\"]+|\\.)*([^\\]|)\"|\\.)+)/) {
+ $curwidget || die("No current widget\n");
+ $curwidget->{$1} = dequote($2);
+ } elsif(/^end/) {
+ shift @estack;
+ shift @wstack;
+ $curwidget = $wstack[0] if((scalar @wstack) > 0);
+ } elsif(!$_) {
+ } else {
+ print STDERR "Invalid construct: $_\n";
+ }
+}
+
+printvars $rootwidgets;
+print "\n";
+print "GtkWidget *create_" . $options{"prefix"} . "wnd(void)\n";
+print "{\n";
+print " GtkWidget *stack[$maxstack];\n";
+print " GtkAccelGroup *accel_group;\n" if $options{"hasaccels"};
+print " GtkTreeViewColumn *column;\n" if $options{"hascolumns"};
+print " GtkCellRenderer *renderer;\n" if $options{"hasrenderers"};
+print " \n";
+print " accel_group = gtk_accel_group_new();\n" if $options{"hasaccels"};
+printwidgets $rootwidgets, 0;
+print " return(stack[0]);\n";
+print "}\n";
--- /dev/null
+;prefix: pref_
+:table rows: 4 cols: 2
+ $mlbl label: "_Public hub list URL:" tx: 0 ty: 0 mwidget: pubhuburl
+ $text name: pubhuburl var: y expand: y fill: y tx: 1 ty: 0
+ $mlbl label: "_Dolda connect user name:" tx: 0 ty: 1 mwidget: connectas
+ $text name: connectas var: y expand: y fill: y tx: 1 ty: 1
+ $mlbl label: "Dolda Connect _server:" tx: 0 ty: 2 mwidget: dcserver
+ $text name: dcserver var: y expand: y fill: y tx: 1 ty: 2
+ $chk name: autoconn var: y label: "Connect _automatically on startup" tx: 0 ty: 3 tw: 2
+end
--- /dev/null
+/* Taken from the GTK TreeView tutorial on gtk.org.
+ * Slightly modified.
+ */
+
+#include "progressbar.h"
+
+/* This is based mainly on GtkCellRendererProgress
+ * in GAIM, written and (c) 2002 by Sean Egan
+ * (Licensed under the GPL), which in turn is
+ * based on Gtk's GtkCellRenderer[Text|Toggle|Pixbuf]
+ * implementation by Jonathan Blandford */
+
+/* Some boring function declarations: GObject type system stuff */
+
+static void custom_cell_renderer_progress_init (CustomCellRendererProgress *cellprogress);
+
+static void custom_cell_renderer_progress_class_init (CustomCellRendererProgressClass *klass);
+
+static void custom_cell_renderer_progress_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void custom_cell_renderer_progress_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+
+static void custom_cell_renderer_progress_finalize (GObject *gobject);
+
+
+/* These functions are the heart of our custom cell renderer: */
+
+static void custom_cell_renderer_progress_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *cell_area,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height);
+
+static void custom_cell_renderer_progress_render (GtkCellRenderer *cell,
+ GdkWindow *window,
+ GtkWidget *widget,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GdkRectangle *expose_area,
+ guint flags);
+
+
+enum
+{
+ PROP_PERCENTAGE = 1,
+};
+
+static gpointer parent_class;
+
+
+/***************************************************************************
+ *
+ * custom_cell_renderer_progress_get_type: here we register our type with
+ * the GObject type system if we
+ * haven't done so yet. Everything
+ * else is done in the callbacks.
+ *
+ ***************************************************************************/
+
+GType
+custom_cell_renderer_progress_get_type (void)
+{
+ static GType cell_progress_type = 0;
+
+ if (cell_progress_type)
+ return cell_progress_type;
+
+ if (1)
+ {
+ static const GTypeInfo cell_progress_info =
+ {
+ sizeof (CustomCellRendererProgressClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) custom_cell_renderer_progress_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (CustomCellRendererProgress),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) custom_cell_renderer_progress_init,
+ };
+
+ /* Derive from GtkCellRenderer */
+ cell_progress_type = g_type_register_static (GTK_TYPE_CELL_RENDERER,
+ "CustomCellRendererProgress",
+ &cell_progress_info,
+ 0);
+ }
+
+ return cell_progress_type;
+}
+
+
+/***************************************************************************
+ *
+ * custom_cell_renderer_progress_init: set some default properties of the
+ * parent (GtkCellRenderer).
+ *
+ ***************************************************************************/
+
+static void
+custom_cell_renderer_progress_init (CustomCellRendererProgress *cellrendererprogress)
+{
+ GTK_CELL_RENDERER(cellrendererprogress)->mode = GTK_CELL_RENDERER_MODE_INERT;
+ GTK_CELL_RENDERER(cellrendererprogress)->xpad = 2;
+ GTK_CELL_RENDERER(cellrendererprogress)->ypad = 2;
+}
+
+
+/***************************************************************************
+ *
+ * custom_cell_renderer_progress_class_init:
+ *
+ * set up our own get_property and set_property functions, and
+ * override the parent's functions that we need to implement.
+ * And make our new "percentage" property known to the type system.
+ * If you want cells that can be activated on their own (ie. not
+ * just the whole row selected) or cells that are editable, you
+ * will need to override 'activate' and 'start_editing' as well.
+ *
+ ***************************************************************************/
+
+static void
+custom_cell_renderer_progress_class_init (CustomCellRendererProgressClass *klass)
+{
+ GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS(klass);
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+ object_class->finalize = custom_cell_renderer_progress_finalize;
+
+ /* Hook up functions to set and get our
+ * custom cell renderer properties */
+ object_class->get_property = custom_cell_renderer_progress_get_property;
+ object_class->set_property = custom_cell_renderer_progress_set_property;
+
+ /* Override the two crucial functions that are the heart
+ * of a cell renderer in the parent class */
+ cell_class->get_size = custom_cell_renderer_progress_get_size;
+ cell_class->render = custom_cell_renderer_progress_render;
+
+ /* Install our very own properties */
+ g_object_class_install_property (object_class,
+ PROP_PERCENTAGE,
+ g_param_spec_double ("percentage",
+ "Percentage",
+ "The fractional progress to display",
+ 0, 1, 0,
+ G_PARAM_READWRITE));
+}
+
+
+/***************************************************************************
+ *
+ * custom_cell_renderer_progress_finalize: free any resources here
+ *
+ ***************************************************************************/
+
+static void
+custom_cell_renderer_progress_finalize (GObject *object)
+{
+/*
+ CustomCellRendererProgress *cellrendererprogress = CUSTOM_CELL_RENDERER_PROGRESS(object);
+*/
+
+ /* Free any dynamically allocated resources here */
+
+ (* G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+
+/***************************************************************************
+ *
+ * custom_cell_renderer_progress_get_property: as it says
+ *
+ ***************************************************************************/
+
+static void
+custom_cell_renderer_progress_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *psec)
+{
+ CustomCellRendererProgress *cellprogress = CUSTOM_CELL_RENDERER_PROGRESS(object);
+
+ switch (param_id)
+ {
+ case PROP_PERCENTAGE:
+ g_value_set_double(value, cellprogress->progress);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, psec);
+ break;
+ }
+}
+
+
+/***************************************************************************
+ *
+ * custom_cell_renderer_progress_set_property: as it says
+ *
+ ***************************************************************************/
+
+static void
+custom_cell_renderer_progress_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CustomCellRendererProgress *cellprogress = CUSTOM_CELL_RENDERER_PROGRESS (object);
+
+ switch (param_id)
+ {
+ case PROP_PERCENTAGE:
+ cellprogress->progress = g_value_get_double(value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
+ break;
+ }
+}
+
+/***************************************************************************
+ *
+ * custom_cell_renderer_progress_new: return a new cell renderer instance
+ *
+ ***************************************************************************/
+
+GtkCellRenderer *
+custom_cell_renderer_progress_new (void)
+{
+ return g_object_new(CUSTOM_TYPE_CELL_RENDERER_PROGRESS, NULL);
+}
+
+
+/***************************************************************************
+ *
+ * custom_cell_renderer_progress_get_size: crucial - calculate the size
+ * of our cell, taking into account
+ * padding and alignment properties
+ * of parent.
+ *
+ ***************************************************************************/
+
+#define FIXED_WIDTH 100
+#define FIXED_HEIGHT 12
+
+static void
+custom_cell_renderer_progress_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *cell_area,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height)
+{
+ gint calc_width;
+ gint calc_height;
+
+ calc_width = (gint) cell->xpad * 2 + FIXED_WIDTH;
+ calc_height = (gint) cell->ypad * 2 + FIXED_HEIGHT;
+
+ if (width)
+ *width = calc_width;
+
+ if (height)
+ *height = calc_height;
+
+ if (cell_area)
+ {
+ if (x_offset)
+ {
+ *x_offset = cell->xalign * (cell_area->width - calc_width);
+ *x_offset = MAX (*x_offset, 0);
+ }
+
+ if (y_offset)
+ {
+ *y_offset = cell->yalign * (cell_area->height - calc_height);
+ *y_offset = MAX (*y_offset, 0);
+ }
+ }
+}
+
+
+/***************************************************************************
+ *
+ * custom_cell_renderer_progress_render: crucial - do the rendering.
+ *
+ ***************************************************************************/
+
+static void
+custom_cell_renderer_progress_render (GtkCellRenderer *cell,
+ GdkWindow *window,
+ GtkWidget *widget,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GdkRectangle *expose_area,
+ guint flags)
+{
+ CustomCellRendererProgress *cellprogress = CUSTOM_CELL_RENDERER_PROGRESS (cell);
+ GtkStateType state;
+ gint width, height;
+ gint x_offset, y_offset;
+
+ custom_cell_renderer_progress_get_size (cell, widget, cell_area,
+ &x_offset, &y_offset,
+ &width, &height);
+
+ if (GTK_WIDGET_HAS_FOCUS (widget))
+ state = GTK_STATE_ACTIVE;
+ else
+ state = GTK_STATE_NORMAL;
+
+ width -= cell->xpad*2;
+ height -= cell->ypad*2;
+
+ gtk_paint_box (widget->style,
+ window,
+ GTK_STATE_NORMAL, GTK_SHADOW_IN,
+ NULL, widget, "trough",
+ cell_area->x + x_offset + cell->xpad,
+ cell_area->y + y_offset + cell->ypad,
+ width - 1, height - 1);
+
+ gtk_paint_box (widget->style,
+ window,
+ state, GTK_SHADOW_OUT,
+ NULL, widget, "bar",
+ cell_area->x + x_offset + cell->xpad,
+ cell_area->y + y_offset + cell->ypad,
+ width * cellprogress->progress,
+ height - 1);
+}
--- /dev/null
+/* Taken from the GTK TreeView tutorial on gtk.org. */
+
+#ifndef _custom_cell_renderer_progressbar_included_
+#define _custom_cell_renderer_progressbar_included_
+
+#include <gtk/gtk.h>
+
+/* Some boilerplate GObject type check and type cast macros.
+ * 'klass' is used here instead of 'class', because 'class'
+ * is a c++ keyword */
+
+#define CUSTOM_TYPE_CELL_RENDERER_PROGRESS (custom_cell_renderer_progress_get_type())
+#define CUSTOM_CELL_RENDERER_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), CUSTOM_TYPE_CELL_RENDERER_PROGRESS, CustomCellRendererProgress))
+#define CUSTOM_CELL_RENDERER_PROGRESS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CUSTOM_TYPE_CELL_RENDERER_PROGRESS, CustomCellRendererProgressClass))
+#define CUSTOM_IS_CELL_PROGRESS_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CUSTOM_TYPE_CELL_RENDERER_PROGRESS))
+#define CUSTOM_IS_CELL_PROGRESS_PROGRESS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CUSTOM_TYPE_CELL_RENDERER_PROGRESS))
+#define CUSTOM_CELL_RENDERER_PROGRESS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CUSTOM_TYPE_CELL_RENDERER_PROGRESS, CustomCellRendererProgressClass))
+
+typedef struct _CustomCellRendererProgress CustomCellRendererProgress;
+typedef struct _CustomCellRendererProgressClass CustomCellRendererProgressClass;
+
+/* CustomCellRendererProgress: Our custom cell renderer
+ * structure. Extend according to need */
+
+struct _CustomCellRendererProgress
+{
+ GtkCellRenderer parent;
+ gdouble progress;
+};
+
+
+struct _CustomCellRendererProgressClass
+{
+ GtkCellRendererClass parent_class;
+};
+
+
+GType custom_cell_renderer_progress_get_type (void);
+
+GtkCellRenderer *custom_cell_renderer_progress_new (void);
+
+
+#endif /* _custom_cell_renderer_progressbar_included_ */
+
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/poll.h>
+
+#include <doldaconnect/uilib.h>
+#include <doldaconnect/uimisc.h>
+
+void authcallback(int err, wchar_t *reason, void *data)
+{
+ printf("Logged in: %i\n", err);
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+ struct pollfd pfd;
+ int fd, done;
+ struct dc_response *resp;
+ struct dc_intresp *ires;
+
+ dc_init();
+ fd = dc_connect("localhost", -1);
+ done = 0;
+ while(!done)
+ {
+ pfd.fd = fd;
+ pfd.events = POLLIN;
+ if(dc_wantwrite())
+ pfd.events = POLLOUT;
+ if(poll(&pfd, 1, -1) < 0)
+ {
+ perror("poll");
+ exit(1);
+ }
+ if((pfd.revents & POLLIN) && dc_handleread())
+ done = 1;
+ if((pfd.revents & POLLOUT) && dc_handlewrite())
+ done = 1;
+ while((resp = dc_getresp()) != NULL)
+ {
+ if(resp->cmdname == NULL)
+ {
+ printf("Connected\n");
+ dc_loginasync(NULL, 0, NULL, authcallback, NULL);
+ }
+ dc_freeresp(resp);
+ }
+ }
+ dc_cleanup();
+ return(0);
+}
--- /dev/null
+/Makefile.am/1.5/Sun Dec 19 02:58:01 2004//
+/dc-filter/1.2/Wed Dec 15 18:59:51 2004//
+/dc-filtercmd/1.1/Wed Dec 15 19:01:20 2004//
+/doldacond.conf/1.1/Sat Sep 18 14:53:07 2004//
+/locktouch.c/1.1/Sat Sep 18 03:36:41 2004//
+/speedrec.c/1.2/Fri Sep 24 09:48:38 2004//
+D
--- /dev/null
+doldaconnect/config
--- /dev/null
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
--- /dev/null
+bin_PROGRAMS=locktouch
+libexec_PROGRAMS=speedrec
+
+speedrec_SOURCES=speedrec.c
+
+locktouch_SOURCES=locktouch.c
+
+EXTRA_DIST=doldacond.conf dc-filter dc-filtercmd
+
+install-data-local:
+ $(mkinstalldirs) $(DESTDIR)$(sysconfdir); \
+ for file in doldacond.conf; do \
+ if [ ! -e $(DESTDIR)$(sysconfdir)/$$file ]; then \
+ $(INSTALL_DATA) $(srcdir)/$$file $(DESTDIR)$(sysconfdir)/$$file; \
+ fi; \
+ done; \
+ for file in dc-filter dc-filtercmd; do \
+ if [ ! -e $(DESTDIR)$(sysconfdir)/$$file ]; then \
+ $(INSTALL) $(srcdir)/$$file $(DESTDIR)$(sysconfdir)/$$file; \
+ fi; \
+ done
--- /dev/null
+#!/bin/sh
+trap "" SIGHUP SIGPIPE
+if [ ! -d $HOME/dc ]; then mkdir $HOME/dc; fi
+if [ ! -d $HOME/dc/done ]; then mkdir $HOME/dc/done; fi
+if [ ! -d $HOME/dc/resume ]; then mkdir $HOME/dc/resume; fi
+if [ ! -d $HOME/dc/users ]; then mkdir $HOME/dc/users; fi
+exec 2>>$HOME/dc/filterlog
+cd $HOME/dc
+unset speedrec
+for dir in /usr/libexec /usr/local/libexec; do
+ if [ -x "${dir}/speedrec" ]; then
+ speedrec="${dir}/speedrec"
+ fi
+done
+if [ -z "$speedrec" ]; then
+ echo "could not find speedrec - using cat instead" >&2
+fi
+maxsize=0
+unset resfile
+unset infofile
+found=y
+while [ -z "$resfile" -a "$found" = y ]; do
+ unset found
+ for file in resume/*.info; do
+ if [ ! -r "$file" ]; then continue; fi
+ . "$file"
+ if [ "$filesize" -eq "$2" ]; then
+ thisfile="${file%.info}"
+ if [ ! -e "${thisfile}.lock" ]; then
+ size="$(wc -c <"$thisfile")"
+ found=y
+ if [ "$size" -gt "$maxsize" ]; then
+ maxsize="$size"
+ resfile="$thisfile"
+ infofile="$file"
+ fi
+ fi
+ fi
+ done
+ if [ -n "$resfile" ]; then
+ if ! locktouch "${resfile}.lock"; then
+ unset resfile
+ fi
+ fi
+done
+unset "${!dcarg_@}"
+if [ -n "$resfile" ]; then
+ . "${resfile}.info"
+fi
+origname="$1"
+shift
+filesize="$1"
+shift
+peername="$1"
+shift
+while [ $# -gt 1 ]; do
+ rec="$1"
+ shift
+ val="$1"
+ shift
+ declare "dcarg_$rec"="$val"
+done
+if [ -z "$resfile" ]; then
+ resfile="$(mktemp resume/resXXXXXX)"
+ chmod 644 "$resfile"
+ maxsize=0
+ >"${resfile}.lock"
+fi
+declare -p origname filesize "${!dcarg_@}" >"${resfile}.info"
+echo "resume $maxsize"
+if [ -z "$speedrec" ]; then
+ cat >>"$resfile"
+else
+ peerfile="$(tr / _ <<<"$peername")"
+ "$speedrec" "$HOME/dc/users/$peerfile" >>"$resfile"
+fi
+size="$(wc -c <"$resfile")"
+if [ "$size" -eq 0 ]; then
+ rm -f "$resfile" "${resfile}.info" "${resfile}.lock"
+ exit 1
+fi
+rm -f "${resfile}.lock"
+if [ "$size" -lt "$filesize" ]; then
+ exit 1 # Exit code != 0 means restart transfer
+fi
+rm -f "${resfile}.info"
+
+destdir="$HOME/dc/done"
+
+newname="$destdir"/"$origname"
+if [ -e "$newname" ]; then
+ newname="$(mktemp "${newname}XXXXXX")"
+fi
+mv "$resfile" "$newname"
+if [ -x $HOME/dc/complete ]; then
+ export "${!dcarg_@}"
+ exec $HOME/dc/complete "$newname"
+fi
+exit 0
--- /dev/null
+#!/bin/sh
+cmd="$1"
+shift
+
+if [ "$cmd" = userspeeda ]; then
+ while [ $# -gt 0 ]; do
+ peerfile="$(tr / _ <<<"$1")"
+ if [ -r "$HOME/dc/users/$peerfile" ]; then
+ speed="$((read num; read max; read nent; avg=0; for i in $(seq 1 $nent); do read ent; let avg+=$ent; done; let avg/=$nent; echo $avg) < "$HOME/dc/users/$peerfile")"
+ echo $speed
+ else
+ echo -1
+ fi
+ shift
+ done
+fi
+
+if [ "$cmd" = userspeedm ]; then
+ while [ $# -gt 0 ]; do
+ peerfile="$(tr / _ <<<"$1")"
+ if [ -r "$HOME/dc/users/$peerfile" ]; then
+ speed="$((read num; read max; echo $max) < "$HOME/dc/users/$peerfile")"
+ echo $speed
+ else
+ echo -1
+ fi
+ shift
+ done
+fi
+
+if [ "$cmd" = rmtag ]; then
+ for f in "$HOME"/dc/resume/*.info; do
+ if [ ! -e "${f%.info}.lock" ]; then
+ unset dcarg_tag
+ . "$f"
+ if [ "$dcarg_tag" = "$1" ]; then
+ rm "$f"
+ rm "${f%.info}"
+ fi
+ fi
+ done
+fi
--- /dev/null
+# Dolda Connect default configuration file
+
+# Default nick name
+set cli.defnick DoldaConnectUser
+
+# Net mode:
+# 0 - Active
+# 1 - Passive
+# 2 - Will be SOCKS proxy when implemented
+set net.mode 0
+
+# If 1, then accept UI connections only over the loopback interface
+set ui.onlylocal 0
+
+# If 1, then enable authenticationless login (don't use without turning on ui.onlylocal, unless you know what you're doing)
+set auth.authless 0
+
+set transfer.slots 6
+
+
+# Settings specific to the Direct Connect network:
+
+# Valid strings are:
+# 56Kbps
+# Satellite
+# DSL
+# Cable
+# LAN(T1)
+# LAN(T3)
+set dc.speedstring LAN(T1)
+# Description string:
+set dc.desc Dolda Connect User
+
+# Shares: "share sharename sharepath", where sharename is the name of the share as the peers see it, and sharepath is the local path to the shared files
+share Video /home/pub/divx
+share Music /home/pub/MP3
+
+# Allowed users and privileges
+# Syntax is "user username [-]privs..."
+# username can be default to match any user that doesn't match the other entries
+# privs can be any of the following:
+# disallow - The only negative permission, which disallows a user from logging in
+# admin - Involves commands controlling the function of the daemon, such as shutting it down remotely
+# fnetctl - Allows connecting and disconnecting fnetnodes (or "Hubs")
+# trans - Allows queuing of transfers
+# transcu - Allows cancelling of uploads
+# chat - Allows sending and receiving of chat messages
+# srch - Allows submitting of search requests
+# all - Equivalent of specifying all the above permissions, including disallow
+# A minus sign preceding a privilege specification revokes that privilege (or, for "all", revokes all privileges, _including_ "disallow").
+# The privileges are scanned from left to right, so "all -disallow" is not the same as "-disallow all".
+
+# In this default configuration, the user called "youruser" (that is, change it),
+# has all privileges except, of course, "disallow", and all other users are disallowed from logging in.
+user default disallow
+user youruser all -disallow
--- /dev/null
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+
+int main(int argc, char **argv)
+{
+ int fd;
+
+ if(argc < 2)
+ {
+ fprintf(stderr, "usage: locktouch lockfile\n");
+ exit(1);
+ }
+ if((fd = open(argv[1], O_CREAT | O_EXCL, 0666)) < 0)
+ {
+ if(errno != EEXIST)
+ {
+ perror(argv[1]);
+ exit(2);
+ }
+ exit(1);
+ }
+ close(fd);
+ return(0);
+}
--- /dev/null
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/file.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/poll.h>
+#include <signal.h>
+
+char buf[4096];
+volatile int eof;
+
+void sighandler(int sig)
+{
+ eof = 1;
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+ int ret, fd;
+ time_t starttime, endtime;
+ long long numbytes;
+ size_t datalen;
+ struct pollfd pfd[2];
+ FILE *recfile;
+ int thisrec, numrecs, numuses, maxrec;
+ int recs[5];
+
+ if(argc < 2)
+ {
+ fprintf(stderr, "usage: speedrec recfile\n");
+ exit(1);
+ }
+ numbytes = 0;
+ starttime = endtime = 0;
+ datalen = 0;
+ eof = 0;
+ signal(SIGHUP, sighandler);
+ signal(SIGINT, sighandler);
+ signal(SIGTERM, sighandler);
+ while(1)
+ {
+ pfd[0].fd = 0;
+ if(eof || (datalen >= sizeof(buf)))
+ pfd[0].events = 0;
+ else
+ pfd[0].events = POLLIN;
+ pfd[1].fd = 1;
+ if(datalen > 0)
+ pfd[1].events = POLLOUT;
+ else
+ pfd[1].events = 0;
+ pfd[0].revents = pfd[1].revents = 0;
+ ret = poll(pfd, 2, -1);
+ if((ret < 0) && (errno != EINTR))
+ {
+ perror("cannot poll");
+ exit(1);
+ }
+ if(pfd[0].revents & (POLLIN | POLLERR | POLLHUP | POLLNVAL))
+ {
+ ret = read(0, buf + datalen, sizeof(buf) - datalen);
+ if((ret < 0) && (errno != EINTR))
+ {
+ perror("cannot read");
+ exit(1);
+ }
+ if(ret == 0)
+ eof = 1;
+ if(ret > 0)
+ {
+ datalen += ret;
+ if(starttime == 0)
+ starttime = time(NULL);
+ endtime = time(NULL);
+ }
+ numbytes += ret;
+ }
+ if(pfd[1].revents & (POLLOUT | POLLERR | POLLHUP | POLLNVAL))
+ {
+ ret = write(1, buf, datalen);
+ if((ret < 0) && (errno != EINTR))
+ {
+ perror("cannot write");
+ exit(1);
+ }
+ memmove(buf, buf + ret, datalen -= ret);
+ }
+ if(eof && (datalen == 0))
+ break;
+ }
+ if((starttime == 0) || (endtime == 0) || (endtime == starttime))
+ exit(0);
+ if(numbytes == 0)
+ exit(0);
+ thisrec = (int)(numbytes / ((long long)(endtime - starttime)));
+ if((fd = open(argv[1], O_RDWR | O_CREAT, 0666)) < 0)
+ {
+ perror(argv[1]);
+ exit(1);
+ }
+ recfile = fdopen(fd, "r+");
+ close(0);
+ close(1);
+ flock(fd, LOCK_EX);
+ if(fscanf(recfile, "%i\n", &numuses) < 1)
+ numuses = 0;
+ if(fscanf(recfile, "%i\n", &maxrec) < 1)
+ maxrec = 0;
+ if(fscanf(recfile, "%i\n", &numrecs) < 1)
+ numrecs = 0;
+ for(i = 0; i < numrecs; i++)
+ fscanf(recfile, "%i\n", &recs[i]);
+ if(numrecs == 5)
+ {
+ for(i = 0; i < 4; i++)
+ recs[i] = recs[i + 1];
+ numrecs = 4;
+ }
+ recs[numrecs++] = thisrec;
+ rewind(recfile);
+ ftruncate(fd, 0);
+ fprintf(recfile, "%i\n", numuses + 1);
+ fprintf(recfile, "%i\n", (thisrec > maxrec)?thisrec:maxrec);
+ fprintf(recfile, "%i\n", numrecs);
+ for(i = 0; i < numrecs; i++)
+ fprintf(recfile, "%i\n", recs[i]);
+ flock(fd, LOCK_UN);
+ fclose(recfile);
+ return(0);
+}
--- /dev/null
+AC_INIT(daemon/main.c)
+AM_INIT_AUTOMAKE([doldaconnect], [0.1])
+AM_CONFIG_HEADER(config.h)
+
+AC_PROG_CC
+AC_PROG_INSTALL
+
+AM_GNU_GETTEXT_VERSION(0.12.1)
+AM_GNU_GETTEXT([external])
+AM_PROG_LIBTOOL
+
+AC_CHECK_LIB(pam, pam_start, , AC_MSG_ERROR([*** must have PAM]))
+AC_CHECK_LIB(dl, dlopen, , AC_MSG_ERROR([*** PAM requires a working dynamic loader (libdl)]))
+AC_CHECK_LIB(z, deflate, , AC_MSG_ERROR([*** must have zlib]))
+AC_CHECK_LIB(bz2, BZ2_bzWriteOpen, , AC_MSG_ERROR([*** must have bzlib]))
+
+clients=
+extlibs=
+experimental=no
+
+AC_CHECK_PROG([PKG_CONFIG], pkg-config, yes, no)
+
+AC_ARG_WITH(gtk2, [ --with-gtk2 Enable GTK2 support])
+if test "$with_gtk2" = no; then
+ HAS_GTK2=no
+fi
+if test "$HAS_GTK2" != no; then
+ if test "$PKG_CONFIG" = yes; then
+ AC_MSG_CHECKING([for GTK2 package information])
+ if pkg-config --modversion gtk+-2.0 >/dev/null 2>&1; then
+ AC_MSG_RESULT(yes)
+ else
+ AC_MSG_RESULT(no)
+ HAS_GTK2=no
+ fi
+ if test "$HAS_GTK2" != no; then
+ AC_CHECK_LIB(gtk-x11-2.0, gtk_init, [HAS_GTK2=yes], [HAS_GTK2=no], `pkg-config --libs gtk+-2.0`)
+ fi
+ if test "$HAS_GTK2" = yes; then
+ cpp_bak="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS `pkg-config --cflags gtk+-2.0`"
+ AC_CHECK_HEADER(gtk/gtk.h, [], [HAS_GTK2=no])
+ CPPFLAGS="$cpp_bak"
+ fi
+ else
+ HAS_GTK2=no
+ fi
+fi
+if test "$with_gtk2" = yes -a "$HAS_GTK2" = no; then
+ AC_MSG_ERROR([*** cannot find GTK2 on this system])
+fi
+
+gtk2ui_msg=No
+AC_ARG_ENABLE(gtk2ui, [ --enable-gtk2ui Enable the GTK2 user interface])
+if test "$enable_gtk2ui" = yes -a "$HAS_GTK2" = no; then
+ AC_MSG_ERROR([*** cannot build the GTK2 UI without a GTK2 library])
+fi
+if test "$enable_gtk2ui" != no -a "$HAS_GTK2" = yes; then
+ clients="$clients gtk2"
+ gtk2ui_msg=Yes
+fi
+
+gtk2pbar_msg=No
+AH_TEMPLATE(ENABLE_GTK2PBAR, [define to compile GTK2 progress bars (experimental)])
+AC_ARG_ENABLE(gtk2pbar, [ --enable-gtk2pbar Enable GTK2 progress bars (experimental)])
+if test "$enable_gtk2pbar" = yes; then
+ if test "$HAS_GTK2" = no; then
+ AC_MSG_ERROR([*** cannot build GTK2 progress bars without GTK2])
+ fi
+ experimental=yes
+ gtk2pbar_msg=Yes
+ AC_DEFINE(ENABLE_GTK2PBAR)
+fi
+
+gnometrapplet_msg=No
+AC_ARG_ENABLE(gnomeapplet, [ --enable-gnomeapplet Enable GNOME transfer applet (experimental)])
+if test "$enable_gnomeapplet" = yes; then
+ experimental=yes
+ gnometrapplet_msg=Yes
+ clients="$clients gnome-trans-applet"
+fi
+
+guile_msg=No
+if test "$with_guile" = yes; then
+ GUILE_FLAGS
+ extlibs="$extlibs guile"
+ guile_msg=Yes
+fi
+
+krb_msg=No
+AH_TEMPLATE(HAVE_KRB5, [define to compile support for Kerberos 5 (not GSS-API) authentication])
+AC_ARG_WITH(krb5, [ --with-krb5[=PATH] Enable Kerberos 5 (not GSSAPI) authentication])
+if test "$with_krb5" != no; then
+ cpp_bak="$CPPFLAGS"
+ ld_bak="$LDFLAGS"
+ if test "$with_krb5" != yes; then
+ CPPFLAGS="$CPPFLAGS -I${with_krb5}/include"
+ LDFLAGS="$LDFLAGS -L${with_krb5}/lib"
+ fi
+ AC_CHECK_LIB(krb5, krb5_init_context, [HAS_KRB5=yes], [HAS_KRB5=no])
+ if test "$HAS_KRB5" = yes; then
+ AC_CHECK_HEADER(com_err.h, [HAS_COMERR=yes], [HAS_COMERR=no])
+ if test "$HAS_COMERR" = no; then
+ AC_CHECK_HEADER(et/com_err.h, [HAS_COMERR=yes; CPPFLAGS="$CPPFLAGS -I/usr/include/et"], [])
+ fi
+ fi
+ if test "$HAS_COMERR" = no; then
+ HAS_KRB5=no
+ fi
+ if test "$HAS_KRB5" = no; then
+ CPPFLAGS="$cpp_bak"
+ LDFLAGS="$ld_bak"
+ fi
+fi
+KRB5_LDADD=
+if test -n "$with_krb5" -a "$with_krb5" != no -a "$HAS_KRB5" != yes; then
+ AC_MSG_ERROR([*** cannot find Kerberos 5 on this system - try passing --with-krb5=/path/to/kerberos])
+fi
+if test "$with_krb5" != no -a "$HAS_KRB5" = yes; then
+ AC_DEFINE(HAVE_KRB5)
+ KRB5_LDADD=-lkrb5
+ krb_msg=Yes
+fi
+AC_SUBST([KRB5_LDADD])
+
+AC_CHECK_FUNC(vswprintf, , AC_MSG_ERROR([*** must have vswprintf]))
+AH_TEMPLATE(HAVE_WCSCASECMP, [define if your system implements wcscasecmp])
+AC_CHECK_FUNC(wcscasecmp, [ AC_DEFINE(HAVE_WCSCASECMP) ])
+
+AH_TEMPLATE(HAVE_LINUX_SOCKIOS_H, [define if you have linux/sockios.h on your system])
+AC_CHECK_HEADER([linux/sockios.h], [ AC_DEFINE(HAVE_LINUX_SOCKIOS_H) ])
+
+AH_TEMPLATE(HAVE_IPV6, [define if your system supports IPv6 and you wish to compile with support for it])
+AC_CHECK_MEMBER(struct sockaddr_in6.sin6_family, [ AC_DEFINE(HAVE_IPV6) ], , [#include <netinet/in.h>])
+
+AH_TEMPLATE(HAVE_RESOLVER, [define if your system supports the res_* functions to fetch DNS RRs])
+AC_CHECK_LIB(resolv, res_query, [ AC_DEFINE(HAVE_RESOLVER)
+ LDFLAGS="$LDFLAGS -lresolv" ])
+
+AC_HEADER_STDC
+AC_HEADER_DIRENT
+AC_HEADER_SYS_WAIT
+
+AC_TYPE_PID_T
+AC_TYPE_SIZE_T
+AC_HEADER_TIME
+AC_TYPE_SIGNAL
+
+CPPFLAGS="-I\$(top_srcdir)/include $CPPFLAGS"
+
+AC_SUBST([clients extlibs])
+AC_OUTPUT([
+Makefile
+daemon/Makefile
+lib/Makefile
+lib/guile/Makefile
+lib/guile/dolcon/Makefile
+clients/Makefile
+clients/gtk2/Makefile
+clients/gnome-trans-applet/Makefile
+include/Makefile
+po/Makefile.in
+config/Makefile
+autopackage/dolcon.apspec
+])
+
+echo
+echo "Dolda Connect has been configured with the following settings:"
+echo
+echo " Kerberos 5 support: $krb_msg"
+echo " GTK2 user interface: $gtk2ui_msg"
+echo " GTK2 progress bars: $gtk2pbar_msg"
+echo " Guile extension library: $guile_msg"
+echo " GNOME transfer applet: $gnometrapplet_msg"
+echo
+
+if tput bold >/dev/null 2>&1 && tty <&2 >/dev/null 2>&1; then
+ hastput=y
+fi
+if test "$HAS_GTK2" = no -a "$with_gtk2" != no -a "$enable_gtk2ui" != no; then
+ if test "$hastput" = y; then
+ tput bold
+ tput setf 4 2>/dev/null
+ fi
+ echo -n " Warning: " >&2
+ if test "$hastput" = y; then
+ tput sgr0
+ fi
+ echo "Could not find a GTK2 development installation on this system." >&2
+ echo " That means you won't get a UI." >&2
+ echo " Make absolutely sure this is what you want!" >&2
+ if test "$hastput" = y; then
+ tput bel
+ fi
+ sleep 1
+fi
+if test "$experimental" = yes; then
+ if test "$hastput" = y; then
+ tput bold
+ tput setf 4 2>/dev/null
+ fi
+ echo -n " Warning: " >&2
+ if test "$hastput" = y; then
+ tput sgr0
+ fi
+ echo "You have enabled one or more experimental features!" >&2
+ echo " Please don't complain that it doesn't work, unless" >&2
+ echo " you have something constructive to add about the situation." >&2
+ if test "$hastput" = y; then
+ tput bel
+ fi
+ sleep 1
+fi
--- /dev/null
+/Makefile.am/1.8/Fri Oct 14 14:34:30 2005//
+/auth-krb5.c/1.10/Thu Oct 13 03:07:59 2005//
+/auth-pam.c/1.1.1.1/Tue May 11 15:47:16 2004//
+/auth.c/1.2/Fri Aug 6 18:51:28 2004//
+/auth.h/1.2/Fri Aug 6 18:51:45 2004//
+/client.c/1.7/Mon Oct 3 03:13:14 2005//
+/client.h/1.5/Mon Oct 3 03:13:19 2005//
+/conf.c/1.3/Fri Aug 13 11:37:34 2004//
+/conf.h/1.2/Tue Jul 27 23:49:40 2004//
+/emacs-local/1.1.1.1/Tue May 11 15:47:05 2004//
+/filenet.c/1.9/Wed Jul 13 00:08:53 2005//
+/filenet.h/1.7/Tue Oct 26 02:18:27 2004//
+/fnet-dc.c/1.49/Thu Oct 13 15:39:39 2005//
+/log.c/1.2/Thu Sep 30 01:19:08 2004//
+/log.h/1.2/Tue Aug 9 11:06:22 2005//
+/main.c/1.10/Wed Oct 12 20:33:22 2005//
+/module.h/1.1.1.1/Tue May 11 15:47:31 2004//
+/net.c/1.12/Wed Aug 10 12:38:38 2005//
+/net.h/1.6/Wed Sep 22 02:25:29 2004//
+/search.c/1.9/Thu Oct 13 03:08:31 2005//
+/search.h/1.4/Tue Oct 26 02:22:10 2004//
+/sysevents.h/1.3/Thu Sep 30 01:20:23 2004//
+/tiger.c/1.3/Wed Sep 28 19:57:08 2005//
+/tiger.h/1.3/Wed Sep 28 19:57:16 2005//
+/transfer.c/1.18/Thu Oct 13 03:08:42 2005//
+/transfer.h/1.9/Sun Oct 9 02:50:12 2005//
+/ui.c/1.19/Fri Oct 14 23:21:24 2005//
+/uiretref/1.6/Tue Aug 3 16:56:12 2004//
+/utils.c/1.15/Fri Oct 14 23:20:43 2005//
+/utils.h/1.15/Fri Oct 14 23:20:49 2005//
+D
--- /dev/null
+doldaconnect/daemon
--- /dev/null
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
--- /dev/null
+bin_PROGRAMS=doldacond
+doldacond_SOURCES= main.c \
+ search.c \
+ search.h \
+ transfer.c \
+ transfer.h \
+ sysevents.h \
+ module.h \
+ filenet.c \
+ filenet.h \
+ fnet-dc.c \
+ auth.c \
+ auth.h \
+ auth-pam.c \
+ auth-krb5.c \
+ client.c \
+ client.h \
+ net.c \
+ net.h \
+ utils.c \
+ utils.h \
+ log.c \
+ log.h \
+ ui.c \
+ conf.c \
+ conf.h \
+ tiger.c \
+ tiger.h
+EXTRA_DIST=emacs-local
+doldacond_LDADD=@KRB5_LDADD@ -lbz2 -lz
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <pwd.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "auth.h"
+#include "utils.h"
+#include "conf.h"
+#include "log.h"
+#include "module.h"
+#include "sysevents.h"
+
+#ifdef HAVE_KRB5
+
+#include <krb5.h>
+#include <com_err.h>
+
+struct krb5data
+{
+ int state;
+ krb5_auth_context context;
+ krb5_ticket *ticket;
+ krb5_creds *creds;
+ krb5_ccache ccache;
+ int renew;
+ struct timer *renewtimer;
+ char *username, *cname;
+};
+
+static void setrenew(struct krb5data *data);
+
+static krb5_context k5context;
+static krb5_principal myprinc;
+static krb5_keytab keytab;
+
+static void releasekrb5(struct krb5data *data)
+{
+ if(data->renewtimer != NULL)
+ canceltimer(data->renewtimer);
+ if(data->context != NULL)
+ krb5_auth_con_free(k5context, data->context);
+ if(data->ticket != NULL)
+ krb5_free_ticket(k5context, data->ticket);
+ if(data->creds != NULL)
+ krb5_free_creds(k5context, data->creds);
+ if(data->username != NULL)
+ free(data->username);
+ if(data->cname != NULL)
+ free(data->cname);
+ free(data);
+}
+
+static void release(struct authhandle *auth)
+{
+ releasekrb5((struct krb5data *)auth->mechdata);
+}
+
+static struct krb5data *newkrb5data(void)
+{
+ struct krb5data *new;
+
+ new = smalloc(sizeof(*new));
+ memset(new, 0, sizeof(*new));
+ return(new);
+}
+
+static int inithandle(struct authhandle *auth, char *username)
+{
+ int ret;
+ struct krb5data *data;
+
+ data = newkrb5data();
+ if((ret = krb5_auth_con_init(k5context, &data->context)) != 0)
+ {
+ flog(LOG_ERR, "could initialize Kerberos auth context: %s", error_message(ret));
+ releasekrb5(data);
+ return(1);
+ }
+ krb5_auth_con_setflags(k5context, data->context, KRB5_AUTH_CONTEXT_DO_SEQUENCE);
+ data->username = sstrdup(username);
+ data->state = 0;
+ auth->mechdata = data;
+ return(0);
+}
+
+/* Copied from MIT Kerberos 5 1.3.3*/
+static krb5_boolean my_krb5_kuserok(krb5_context context, krb5_principal principal, const char *luser, const char *loginfile, int authbydef)
+{
+ struct stat sbuf;
+ struct passwd *pwd;
+ char pbuf[MAXPATHLEN];
+ krb5_boolean isok = FALSE;
+ FILE *fp;
+ char kuser[65];
+ char *princname;
+ char linebuf[BUFSIZ];
+ char *newline;
+ int gobble;
+
+ /* no account => no access */
+ if ((pwd = getpwnam(luser)) == NULL) {
+ return(FALSE);
+ }
+ (void) strncpy(pbuf, pwd->pw_dir, sizeof(pbuf) - 1);
+ pbuf[sizeof(pbuf) - 1] = '\0';
+ (void) strncat(pbuf, loginfile, sizeof(pbuf) - 1 - strlen(pbuf));
+
+ if (access(pbuf, F_OK)) { /* not accessible */
+ /*
+ * if he's trying to log in as himself, and there is no .k5login file,
+ * let him. To find out, call
+ * krb5_aname_to_localname to convert the principal to a name
+ * which we can string compare.
+ */
+ if (authbydef) {
+ if (!(krb5_aname_to_localname(context, principal,
+ sizeof(kuser), kuser))
+ && (strcmp(kuser, luser) == 0)) {
+ return(TRUE);
+ }
+ } else {
+ return(FALSE);
+ }
+ }
+ if (krb5_unparse_name(context, principal, &princname))
+ return(FALSE); /* no hope of matching */
+
+ /* open ~/.k5login */
+ if ((fp = fopen(pbuf, "r")) == NULL) {
+ free(princname);
+ return(FALSE);
+ }
+ /*
+ * For security reasons, the .k5login file must be owned either by
+ * the user himself, or by root. Otherwise, don't grant access.
+ */
+ if (fstat(fileno(fp), &sbuf)) {
+ fclose(fp);
+ free(princname);
+ return(FALSE);
+ }
+ if ((sbuf.st_uid != pwd->pw_uid) && sbuf.st_uid) {
+ fclose(fp);
+ free(princname);
+ return(FALSE);
+ }
+
+ /* check each line */
+ while (!isok && (fgets(linebuf, BUFSIZ, fp) != NULL)) {
+ /* null-terminate the input string */
+ linebuf[BUFSIZ-1] = '\0';
+ newline = NULL;
+ /* nuke the newline if it exists */
+ if ((newline = strchr(linebuf, '\n')))
+ *newline = '\0';
+ if (!strcmp(linebuf, princname)) {
+ isok = TRUE;
+ continue;
+ }
+ /* clean up the rest of the line if necessary */
+ if (!newline)
+ while (((gobble = getc(fp)) != EOF) && gobble != '\n');
+ }
+ free(princname);
+ fclose(fp);
+ return(isok);
+}
+
+static void renewcreds(int cancelled, struct krb5data *data)
+{
+ int ret;
+ char ccnambuf[50];
+ krb5_ccache tmpcc;
+ krb5_creds newcreds;
+ static int ccserial = 0;
+
+ data->renewtimer = NULL;
+ if(cancelled)
+ return;
+ memset(&newcreds, 0, sizeof(newcreds));
+ snprintf(ccnambuf, sizeof(ccnambuf), "MEMORY:%i", ccserial++);
+ if((ret = krb5_cc_resolve(k5context, ccnambuf, &tmpcc)) != 0)
+ {
+ flog(LOG_ERR, "could not resolve a temporary ccache `%s': %s", ccnambuf, error_message(ret));
+ data->renew = 0;
+ return;
+ }
+ if((ret = krb5_cc_initialize(k5context, tmpcc, data->ticket->enc_part2->client)) != 0)
+ {
+ flog(LOG_ERR, "could not initialize temporary ccache: %s", error_message(ret));
+ krb5_cc_destroy(k5context, tmpcc);
+ data->renew = 0;
+ return;
+ }
+ if((ret = krb5_cc_store_cred(k5context, tmpcc, data->creds)) != 0)
+ {
+ flog(LOG_ERR, "could not store creds into temporary ccache: %s", error_message(ret));
+ krb5_cc_destroy(k5context, tmpcc);
+ data->renew = 0;
+ return;
+ }
+ if((ret = krb5_get_renewed_creds(k5context, &newcreds, data->ticket->enc_part2->client, tmpcc, NULL)) != 0)
+ {
+ flog(LOG_ERR, "could not get renewed tickets for %s: %s", data->username, error_message(ret));
+ krb5_cc_destroy(k5context, tmpcc);
+ data->renew = 0;
+ return;
+ }
+ krb5_free_creds(k5context, data->creds);
+ data->creds = NULL;
+ if((ret = krb5_copy_creds(k5context, &newcreds, &data->creds)) != 0)
+ {
+ flog(LOG_ERR, "could not copy renewed creds: %s", error_message(ret));
+ krb5_cc_destroy(k5context, tmpcc);
+ data->renew = 0;
+ return;
+ }
+ krb5_free_cred_contents(k5context, &newcreds);
+ krb5_cc_destroy(k5context, tmpcc);
+ flog(LOG_ERR, "successfully renewed krb5 creds for %s", data->username);
+ setrenew(data);
+}
+
+static void setrenew(struct krb5data *data)
+{
+ krb5_ticket_times times;
+ time_t now, good;
+
+ times = data->creds->times;
+ if(!times.starttime)
+ times.starttime = times.authtime;
+ now = time(NULL);
+ if(times.endtime < now)
+ {
+ flog(LOG_DEBUG, "tickets already expired, cannot renew");
+ data->renew = 0;
+ return;
+ }
+ good = times.starttime + (((times.endtime - times.starttime) * 9) / 10);
+ data->renewtimer = timercallback(good, (void (*)(int, void *))renewcreds, data);
+}
+
+static int krbauth(struct authhandle *auth, char *passdata)
+{
+ int ret;
+ struct krb5data *data;
+ char *msg;
+ size_t msglen;
+ int authorized;
+ krb5_data k5d;
+ krb5_flags apopt;
+ krb5_creds **fwdcreds;
+
+ data = auth->mechdata;
+ if(passdata == NULL)
+ {
+ auth->prompt = AUTH_PR_AUTO;
+ if(auth->text != NULL)
+ free(auth->text);
+ auth->text = swcsdup(L"Send hex-encoded krb5 data");
+ data->state = 1;
+ return(AUTH_PASS);
+ } else {
+ if((msg = hexdecode(passdata, &msglen)) == NULL)
+ {
+ if(auth->text != NULL)
+ free(auth->text);
+ auth->text = swcsdup(L"Invalid hex encoding");
+ return(AUTH_DENIED);
+ }
+ switch(data->state)
+ {
+ case 1:
+ k5d.length = msglen;
+ k5d.data = msg;
+ if((ret = krb5_rd_req(k5context, &data->context, &k5d, myprinc, keytab, &apopt, &data->ticket)) != 0)
+ {
+ flog(LOG_INFO, "kerberos authentication failed for %s: %s", data->username, error_message(ret));
+ if(auth->text != NULL)
+ free(auth->text);
+ auth->text = icmbstowcs((char *)error_message(ret), NULL);
+ return(AUTH_DENIED);
+ }
+ free(msg);
+ if(apopt & AP_OPTS_MUTUAL_REQUIRED)
+ {
+ if((ret = krb5_mk_rep(k5context, data->context, &k5d)) != 0)
+ {
+ flog(LOG_WARNING, "krb5_mk_rep returned an error: %s", error_message(ret));
+ return(AUTH_ERR);
+ }
+ msg = hexencode(k5d.data, k5d.length);
+ if(auth->text != NULL)
+ free(auth->text);
+ auth->text = icmbstowcs(msg, "us-ascii");
+ free(msg);
+ free(k5d.data);
+ } else {
+ if(auth->text != NULL)
+ free(auth->text);
+ auth->text = swcsdup(L"");
+ }
+ data->state = 2;
+ return(AUTH_PASS);
+ case 2:
+ ret = atoi(msg);
+ free(msg);
+ if(ret == 1)
+ {
+ /* That is, the client has accepted us as a valid
+ * server. Now check if the client is authorized. */
+ if((ret = krb5_unparse_name(k5context, data->ticket->enc_part2->client, &data->cname)) != 0)
+ {
+ flog(LOG_ERR, "krb_unparse_name returned an error: %s", error_message(ret));
+ return(AUTH_ERR);
+ }
+ authorized = 0;
+ if(!authorized && my_krb5_kuserok(k5context, data->ticket->enc_part2->client, data->username, "/.k5login", 1))
+ authorized = 1;
+ /* Allow a seperate ACL for DC principals */
+ if(!authorized && my_krb5_kuserok(k5context, data->ticket->enc_part2->client, data->username, "/.dc-k5login", 0))
+ authorized = 1;
+ if(authorized)
+ {
+ flog(LOG_INFO, "krb5 principal %s successfully authorized as %s", data->cname, data->username);
+ return(AUTH_SUCCESS);
+ } else {
+ flog(LOG_INFO, "krb5 principal %s not authorized as %s", data->cname, data->username);
+ }
+ }
+ if(ret == 2)
+ {
+ if(auth->text != NULL)
+ free(auth->text);
+ auth->text = swcsdup(L"");
+ data->state = 3;
+ return(AUTH_PASS);
+ }
+ return(AUTH_DENIED);
+ case 3:
+ k5d.length = msglen;
+ k5d.data = msg;
+ if((ret = krb5_rd_cred(k5context, data->context, &k5d, &fwdcreds, NULL)) != 0)
+ {
+ flog(LOG_ERR, "krb5_rd_cred returned an error: %s", error_message(ret));
+ return(AUTH_ERR);
+ }
+ if(*fwdcreds == NULL)
+ {
+ flog(LOG_ERR, "forwarded credentials array was empty (from %s)", data->username);
+ krb5_free_tgt_creds(k5context, fwdcreds);
+ return(AUTH_ERR);
+ }
+ flog(LOG_INFO, "received forwarded credentials for %s", data->username);
+ /* Copy only the first credential. (Change this if it becomes a problem) */
+ ret = krb5_copy_creds(k5context, *fwdcreds, &data->creds);
+ krb5_free_tgt_creds(k5context, fwdcreds);
+ if(ret != 0)
+ {
+ flog(LOG_ERR, "could not copy forwarded credentials: %s", error_message(ret));
+ return(AUTH_ERR);
+ }
+ if(confgetint("auth-krb5", "renewcreds"))
+ {
+ data->renew = 1;
+ setrenew(data);
+ }
+ if(auth->text != NULL)
+ free(auth->text);
+ auth->text = swcsdup(L"");
+ data->state = 2;
+ return(AUTH_PASS);
+ default:
+ free(msg);
+ flog(LOG_ERR, "BUG? Invalid state encountered in krbauth: %i", data->state);
+ return(AUTH_ERR);
+ }
+ }
+}
+
+static int opensess(struct authhandle *auth)
+{
+ int ret;
+ struct krb5data *data;
+ char *buf, *buf2;
+ int fd;
+ struct passwd *pwent;
+
+ data = auth->mechdata;
+ if(data->creds != NULL)
+ {
+ if((pwent = getpwnam(data->username)) == NULL)
+ {
+ flog(LOG_ERR, "could not get passwd entry for forwarded tickets (user %s): %s", data->username, strerror(errno));
+ return(AUTH_ERR);
+ }
+ buf = sprintf2("/tmp/krb5cc_dc_%i_XXXXXX", pwent->pw_uid);
+ if((fd = mkstemp(buf)) < 0)
+ {
+ free(buf);
+ flog(LOG_ERR, "could not create temporary file for ccache: %s", strerror(errno));
+ return(AUTH_ERR);
+ }
+ close(fd);
+ buf2 = sprintf2("FILE:%s", buf);
+ if((ret = krb5_cc_resolve(k5context, buf2, &data->ccache)) != 0)
+ {
+ free(buf);
+ free(buf2);
+ flog(LOG_ERR, "could not resolve ccache name \"%s\": %s", buf2, error_message(ret));
+ return(AUTH_ERR);
+ }
+ setenv("KRB5CCNAME", buf2, 1);
+ free(buf2);
+ if((ret = krb5_cc_initialize(k5context, data->ccache, data->ticket->enc_part2->client)) != 0)
+ {
+ free(buf);
+ flog(LOG_ERR, "could not initialize ccache: %s", error_message(ret));
+ return(AUTH_ERR);
+ }
+ if((ret = krb5_cc_store_cred(k5context, data->ccache, data->creds)) != 0)
+ {
+ free(buf);
+ flog(LOG_ERR, "could not store forwarded TGT into ccache: %s", error_message(ret));
+ return(AUTH_ERR);
+ }
+ if(chown(buf, pwent->pw_uid, pwent->pw_gid))
+ {
+ free(buf);
+ flog(LOG_ERR, "could not chown new ccache to %i:%i: %s", pwent->pw_uid, pwent->pw_gid, strerror(errno));
+ return(AUTH_ERR);
+ }
+ free(buf);
+ }
+ return(AUTH_SUCCESS);
+}
+
+static int closesess(struct authhandle *auth)
+{
+ struct krb5data *data;
+
+ data = auth->mechdata;
+ if(data->ccache != NULL)
+ {
+ krb5_cc_destroy(k5context, data->ccache);
+ data->ccache = NULL;
+ }
+ return(AUTH_SUCCESS);
+}
+
+struct authmech authmech_krb5 =
+{
+ .inithandle = inithandle,
+ .release = release,
+ .authenticate = krbauth,
+ .opensess = opensess,
+ .closesess = closesess,
+ .name = L"krb5",
+ .enabled = 1
+};
+
+static int init(int hup)
+{
+ int ret;
+ char *buf;
+ krb5_principal newprinc;
+
+ if(!hup)
+ {
+ regmech(&authmech_krb5);
+ if((ret = krb5_init_context(&k5context)))
+ {
+ flog(LOG_CRIT, "could not initialize Kerberos context: %s", error_message(ret));
+ return(1);
+ }
+ if((buf = icwcstombs(confgetstr("auth-krb5", "service"), NULL)) == NULL)
+ {
+ flog(LOG_CRIT, "could not convert service name (%ls) into local charset: %s", confgetstr("auth-krb5", "service"), strerror(errno));
+ return(1);
+ } else {
+ if((ret = krb5_sname_to_principal(k5context, NULL, buf, KRB5_NT_SRV_HST, &myprinc)) != 0)
+ {
+ flog(LOG_CRIT, "could not get principal for service %s: %s", buf, error_message(ret));
+ free(buf);
+ return(1);
+ }
+ free(buf);
+ }
+ if((buf = icwcstombs(confgetstr("auth-krb5", "keytab"), NULL)) == NULL)
+ {
+ flog(LOG_ERR, "could not convert keytab name (%ls) into local charset: %s, using default keytab instead", confgetstr("auth-krb5", "keytab"), strerror(errno));
+ keytab = NULL;
+ } else {
+ if((ret = krb5_kt_resolve(k5context, buf, &keytab)) != 0)
+ {
+ flog(LOG_ERR, "could not open keytab %s: %s, using default keytab instead", buf, error_message(ret));
+ keytab = NULL;
+ }
+ free(buf);
+ }
+ }
+ if(hup)
+ {
+ if((buf = icwcstombs(confgetstr("auth-krb5", "service"), NULL)) == NULL)
+ {
+ flog(LOG_CRIT, "could not convert service name (%ls) into local charset: %s, not updating principal", confgetstr("auth-krb5", "service"), strerror(errno));
+ } else {
+ if((ret = krb5_sname_to_principal(k5context, NULL, buf, KRB5_NT_SRV_HST, &newprinc)) != 0)
+ {
+ flog(LOG_CRIT, "could not get principal for service %s: %s, not updating principal", buf, error_message(ret));
+ } else {
+ krb5_free_principal(k5context, myprinc);
+ myprinc = newprinc;
+ }
+ free(buf);
+ }
+ if(keytab != NULL)
+ krb5_kt_close(k5context, keytab);
+ if((buf = icwcstombs(confgetstr("auth-krb5", "keytab"), NULL)) == NULL)
+ {
+ flog(LOG_ERR, "could not convert keytab name (%ls) into local charset: %s, using default keytab instead", confgetstr("auth-krb5", "keytab"), strerror(errno));
+ keytab = NULL;
+ } else {
+ if((ret = krb5_kt_resolve(k5context, buf, &keytab)) != 0)
+ {
+ flog(LOG_ERR, "could not open keytab %s: %s, using default keytab instead", buf, error_message(ret));
+ keytab = NULL;
+ }
+ free(buf);
+ }
+ }
+ return(0);
+}
+
+static void terminate(void)
+{
+ if(keytab != NULL)
+ krb5_kt_close(k5context, keytab);
+ krb5_free_principal(k5context, myprinc);
+ krb5_free_context(k5context);
+}
+
+static struct configvar myvars[] =
+{
+ {CONF_VAR_STRING, "service", {.str = L"doldacond"}},
+ {CONF_VAR_STRING, "keytab", {.str = L""}},
+ {CONF_VAR_BOOL, "renewcreds", {.num = 1}},
+ {CONF_VAR_END}
+};
+
+static struct module me =
+{
+ .conf =
+ {
+ .vars = myvars
+ },
+ .init = init,
+ .terminate = terminate,
+ .name = "auth-krb5"
+};
+
+MODULE(me);
+
+#endif /* HAVE_KRB5 */
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+/*
+ * I have decided that I don't like PAM. Maybe I'm inexperienced, so
+ * please correct me if I'm wrong, but is it not so that
+ * pam_authenticate blocks until the user has fully authenticated
+ * herself? That isn't very good in a program that wants to do other
+ * things at the same time. In my mind, pam_authenticate should return
+ * with a conversation struct every time it wants data.
+ *
+ * My solution here, for now, is to use the ucontext context switching
+ * functions to get back and forth from the conversation
+ * function. Ugly? Yes indeed, it most certainly is, but what am I to
+ * do, then? If there actually is a good way to do this that is built
+ * into PAM, _please_, do mail me about it.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ucontext.h>
+#include <security/pam_appl.h>
+#include <errno.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "auth.h"
+#include "utils.h"
+#include "conf.h"
+#include "log.h"
+
+struct pamdata
+{
+ pam_handle_t *pamh;
+ volatile int pamret;
+ ucontext_t mainctxt, pamctxt;
+ void *pamstack;
+ volatile int validctxt;
+ volatile int convdone, converr;
+ volatile char *passdata;
+};
+
+static int pamconv(int nmsg, const struct pam_message **msg, struct pam_response **resp, struct authhandle *auth)
+{
+ int i;
+ struct pamdata *data;
+
+ data = auth->mechdata;
+ *resp = smalloc(sizeof(**resp) * nmsg);
+ for(i = 0; i < nmsg; i++)
+ {
+ switch(msg[i]->msg_style)
+ {
+ case PAM_PROMPT_ECHO_OFF:
+ auth->prompt = AUTH_PR_NOECHO;
+ break;
+ case PAM_PROMPT_ECHO_ON:
+ auth->prompt = AUTH_PR_ECHO;
+ break;
+ case PAM_ERROR_MSG:
+ auth->prompt = AUTH_PR_ERROR;
+ break;
+ case PAM_TEXT_INFO:
+ auth->prompt = AUTH_PR_INFO;
+ break;
+ }
+ if(auth->text != NULL)
+ free(auth->text);
+ if((auth->text = icmbstowcs((char *)msg[i]->msg, NULL)) == NULL)
+ {
+ flog(LOG_ERR, "could not convert PAM error %s into wcs: %s", msg[i]->msg, strerror(errno));
+ free(*resp);
+ *resp = NULL;
+ return(PAM_CONV_ERR);
+ }
+ if(swapcontext(&data->pamctxt, &data->mainctxt))
+ {
+ flog(LOG_CRIT, "could not swap context in PAM conversation: %s", strerror(errno));
+ free(*resp);
+ *resp = NULL;
+ return(PAM_CONV_ERR);
+ }
+ if(data->converr)
+ {
+ for(; i < nmsg; i++)
+ {
+ (*resp)[i].resp = sstrdup("");
+ (*resp)[i].resp_retcode = PAM_SUCCESS;
+ }
+ return(PAM_CONV_ERR);
+ }
+ switch(msg[i]->msg_style)
+ {
+ case PAM_PROMPT_ECHO_OFF:
+ case PAM_PROMPT_ECHO_ON:
+ (*resp)[i].resp = sstrdup((char *)data->passdata);
+ memset((void *)data->passdata, 0, strlen((char *)data->passdata));
+ (*resp)[i].resp_retcode = PAM_SUCCESS;
+ break;
+ }
+ }
+ return(PAM_SUCCESS);
+}
+
+static void releasepam(struct pamdata *data)
+{
+ if(data->pamh != NULL)
+ {
+ if(data->validctxt)
+ {
+ data->converr = 1;
+ if(swapcontext(&data->mainctxt, &data->pamctxt))
+ {
+ flog(LOG_CRIT, "could not switch back to PAM context while releasing: %s", strerror(errno));
+ return;
+ }
+ }
+ pam_end(data->pamh, data->pamret);
+ }
+ if(data->pamstack != NULL)
+ free(data->pamstack);
+ free(data);
+}
+
+static void release(struct authhandle *auth)
+{
+ releasepam((struct pamdata *)auth->mechdata);
+}
+
+static struct pamdata *newpamdata(void)
+{
+ struct pamdata *new;
+
+ new = smalloc(sizeof(*new));
+ new->pamh = NULL;
+ new->pamret = PAM_SUCCESS;
+ new->pamstack = NULL;
+ new->validctxt = 0;
+ new->converr = 0;
+ return(new);
+}
+
+static int inithandle(struct authhandle *auth, char *username)
+{
+ char *buf;
+ struct pamdata *data;
+ struct pam_conv conv;
+
+ data = newpamdata();
+ conv.conv = (int (*)(int, const struct pam_message **, struct pam_response **, void *))pamconv;
+ conv.appdata_ptr = auth;
+ if((buf = icwcstombs(confgetstr("auth", "pamserv"), NULL)) == NULL)
+ {
+ flog(LOG_ERR, "could not initialize pam since auth.pamserv cannot be translated into the current locale: %s", strerror(errno));
+ releasepam(data);
+ return(1);
+ }
+ if((data->pamret = pam_start(buf, username, &conv, &data->pamh)) != PAM_SUCCESS)
+ {
+ flog(LOG_CRIT, "could not pam_start: %s", pam_strerror(NULL, data->pamret));
+ releasepam(data);
+ free(buf);
+ errno = ENOTSUP; /* XXX */
+ return(1);
+ }
+ free(buf);
+ auth->mechdata = data;
+ return(0);
+}
+
+static void pamauththread(struct authhandle *auth)
+{
+ struct pamdata *data;
+
+ data = (struct pamdata *)auth->mechdata;
+ data->validctxt = 1;
+ data->pamret = pam_authenticate(data->pamh, 0);
+ data->validctxt = 0;
+}
+
+static int pamauth(struct authhandle *auth, char *passdata)
+{
+ struct pamdata *data;
+
+ data = auth->mechdata;
+ if(!data->validctxt)
+ {
+ if(getcontext(&data->pamctxt))
+ {
+ flog(LOG_CRIT, "could not get context: %s", strerror(errno));
+ return(AUTH_ERR);
+ }
+ data->pamctxt.uc_link = &data->mainctxt;
+ if(data->pamstack == NULL)
+ data->pamstack = smalloc(65536);
+ data->pamctxt.uc_stack.ss_sp = data->pamstack;
+ data->pamctxt.uc_stack.ss_size = 65536;
+ makecontext(&data->pamctxt, (void (*)(void))pamauththread, 1, auth);
+ if(swapcontext(&data->mainctxt, &data->pamctxt))
+ {
+ flog(LOG_CRIT, "Could not switch to PAM context: %s", strerror(errno));
+ return(AUTH_ERR);
+ }
+ if(!data->validctxt)
+ {
+ if(data->pamret == PAM_AUTHINFO_UNAVAIL)
+ return(AUTH_ERR);
+ else if(data->pamret == PAM_SUCCESS)
+ return(AUTH_SUCCESS);
+ else
+ return(AUTH_DENIED);
+ }
+ return(AUTH_PASS);
+ } else {
+ data->passdata = passdata;
+ if(swapcontext(&data->mainctxt, &data->pamctxt))
+ {
+ flog(LOG_CRIT, "could not switch back to PAM context: %s", strerror(errno));
+ return(AUTH_ERR);
+ }
+ if(!data->validctxt)
+ {
+ if(data->pamret == PAM_AUTHINFO_UNAVAIL)
+ return(AUTH_ERR);
+ else if(data->pamret == PAM_SUCCESS)
+ return(AUTH_SUCCESS);
+ else
+ return(AUTH_DENIED);
+ }
+ return(AUTH_PASS);
+ }
+}
+
+static int renewcred(struct authhandle *auth)
+{
+ struct pamdata *data;
+
+ data = auth->mechdata;
+ if(data->pamh == NULL)
+ return(AUTH_SUCCESS);
+ data->pamret = pam_setcred(data->pamh, PAM_REFRESH_CRED);
+ if(data->pamret != PAM_SUCCESS)
+ {
+ flog(LOG_INFO, "could not refresh credentials: %s", pam_strerror(data->pamh, data->pamret));
+ return(AUTH_ERR);
+ }
+ return(AUTH_SUCCESS);
+}
+
+static int opensess(struct authhandle *auth)
+{
+ struct pamdata *data;
+ char **envp;
+
+ data = auth->mechdata;
+ if(data->pamh == NULL)
+ {
+ flog(LOG_ERR, "bug: in auth-pam.c:opensess: called with NULL pamh");
+ return(AUTH_ERR);
+ }
+ data->pamret = pam_setcred(data->pamh, PAM_ESTABLISH_CRED);
+ if(data->pamret != PAM_SUCCESS)
+ {
+ flog(LOG_INFO, "could not establish credentials: %s", pam_strerror(data->pamh, data->pamret));
+ return(AUTH_ERR);
+ }
+ data->pamret = pam_open_session(data->pamh, 0);
+ if(data->pamret != PAM_SUCCESS)
+ {
+ flog(LOG_INFO, "could not open session: %s", pam_strerror(data->pamh, data->pamret));
+ return(AUTH_ERR);
+ }
+ for(envp = pam_getenvlist(data->pamh); *envp; envp++)
+ putenv(*envp);
+ return(AUTH_SUCCESS);
+}
+
+static int closesess(struct authhandle *auth)
+{
+ int rc;
+ struct pamdata *data;
+
+ data = auth->mechdata;
+ if(data->pamh == NULL)
+ {
+ flog(LOG_ERR, "bug: in auth-pam.c:closesess: called with NULL pamh");
+ return(AUTH_ERR);
+ }
+ rc = AUTH_SUCCESS;
+ data->pamret = pam_close_session(data->pamh, 0);
+ if(data->pamret != PAM_SUCCESS)
+ {
+ flog(LOG_INFO, "could not open session: %s", pam_strerror(data->pamh, data->pamret));
+ rc = AUTH_ERR;
+ }
+ data->pamret = pam_setcred(data->pamh, PAM_DELETE_CRED);
+ if(data->pamret != PAM_SUCCESS)
+ {
+ flog(LOG_INFO, "could not establish credentials: %s", pam_strerror(data->pamh, data->pamret));
+ rc = AUTH_ERR;
+ }
+ return(rc);
+}
+
+struct authmech authmech_pam =
+{
+ .inithandle = inithandle,
+ .release = release,
+ .authenticate = pamauth,
+ .renewcred = renewcred,
+ .opensess = opensess,
+ .closesess = closesess,
+ .name = L"pam",
+ .enabled = 1
+};
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#include <wchar.h>
+#include <errno.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "auth.h"
+#include "utils.h"
+#include "module.h"
+#include "conf.h"
+
+struct authmech *mechs = NULL;
+
+static int authless_inithandle(struct authhandle *auth, char *username)
+{
+ return(0);
+}
+
+static void authless_release(struct authhandle *auth)
+{
+}
+
+static int authless_authenticate(struct authhandle *auth, char *data)
+{
+ return(AUTH_SUCCESS);
+}
+
+static int authless_succeed_1param(struct authhandle *auth)
+{
+ return(AUTH_SUCCESS);
+}
+
+static struct authmech authless =
+{
+ .name = L"authless",
+ .inithandle = authless_inithandle,
+ .release = authless_release,
+ .authenticate = authless_authenticate,
+ .renewcred = authless_succeed_1param,
+ .opensess = authless_succeed_1param,
+ .closesess = authless_succeed_1param
+};
+
+static struct authhandle *newhandle(void)
+{
+ struct authhandle *auth;
+
+ auth = smalloc(sizeof(*auth));
+ auth->refcount = 1;
+ auth->mech = NULL;
+ auth->text = NULL;
+ auth->mechdata = NULL;
+ return(auth);
+}
+
+void authgethandle(struct authhandle *auth)
+{
+ auth->refcount++;
+}
+
+void authputhandle(struct authhandle *auth)
+{
+ if(--auth->refcount)
+ return;
+ if(auth->text != NULL)
+ free(auth->text);
+ if(auth->mechdata != NULL)
+ auth->mech->release(auth);
+ free(auth);
+}
+
+struct authhandle *initauth(wchar_t *mechname, char *username)
+{
+ struct authmech *mech;
+ struct authhandle *auth;
+
+ for(mech = mechs; mech != NULL; mech = mech->next)
+ {
+ if(mech->enabled && !wcscmp(mechname, mech->name))
+ break;
+ }
+ if(mech == NULL)
+ {
+ errno = ENOENT;
+ return(NULL);
+ }
+ auth = newhandle();
+ auth->mech = mech;
+ if(mech->inithandle(auth, username))
+ {
+ authputhandle(auth);
+ return(NULL);
+ }
+ return(auth);
+}
+
+int authenticate(struct authhandle *handle, char *data)
+{
+ if(handle->mech == NULL)
+ return(AUTH_ERR);
+ return(handle->mech->authenticate(handle, data));
+}
+
+int authrenewcred(struct authhandle *handle)
+{
+ if((handle->mech == NULL) || (handle->mech->renewcred == NULL))
+ return(AUTH_SUCCESS);
+ return(handle->mech->renewcred(handle));
+}
+
+int authopensess(struct authhandle *handle)
+{
+ if((handle->mech == NULL) || (handle->mech->opensess == NULL))
+ return(AUTH_SUCCESS);
+ return(handle->mech->opensess(handle));
+}
+
+int authclosesess(struct authhandle *handle)
+{
+ if((handle->mech == NULL) || (handle->mech->closesess == NULL))
+ return(AUTH_SUCCESS);
+ return(handle->mech->closesess(handle));
+}
+
+void regmech(struct authmech *mech)
+{
+ mech->next = mechs;
+ mechs = mech;
+}
+
+static void preinit(int hup)
+{
+ extern struct authmech authmech_pam;
+
+ if(hup)
+ return;
+ regmech(&authless);
+ regmech(&authmech_pam);
+}
+
+static int init(int hup)
+{
+ authless.enabled = confgetint("auth", "authless");
+ return(0);
+}
+
+static struct configvar myvars[] =
+{
+ {CONF_VAR_STRING, "pamserv", {.str = L"doldacond"}},
+ {CONF_VAR_BOOL, "authless", {.num = 1}},
+ {CONF_VAR_END}
+};
+
+static struct module me =
+{
+ .name = "auth",
+ .conf =
+ {
+ .vars = myvars
+ },
+ .preinit = preinit,
+ .init = init
+};
+
+MODULE(me)
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#ifndef _AUTH_H
+#define _AUTH_H
+
+#include <wchar.h>
+
+#define AUTH_SUCCESS 0 /* Authentication successful and done */
+#define AUTH_DENIED 1 /* Ultimately failed - reason in handle->text */
+#define AUTH_PASS 2 /* Pass data - look in handle->prompt */
+#define AUTH_ERR 3 /* An error occurred that */
+
+#define AUTH_PR_AUTO 0
+#define AUTH_PR_NOECHO 1
+#define AUTH_PR_ECHO 2
+#define AUTH_PR_INFO 3
+#define AUTH_PR_ERROR 4
+
+struct authhandle;
+
+struct authmech
+{
+ struct authmech *next;
+ int enabled;
+ wchar_t *name;
+ int (*inithandle)(struct authhandle *handle, char *username);
+ void (*release)(struct authhandle *handle);
+ int (*authenticate)(struct authhandle *handle, char *data);
+ int (*renewcred)(struct authhandle *handle);
+ int (*opensess)(struct authhandle *handle);
+ int (*closesess)(struct authhandle *handle);
+};
+
+struct authhandle
+{
+ int refcount;
+ struct authmech *mech;
+ int prompt;
+ wchar_t *text;
+ void *mechdata;
+};
+
+int authenticate(struct authhandle *handle, char *data);
+struct authhandle *initauth(wchar_t *mechname, char *username);
+void authgethandle(struct authhandle *auth);
+void authputhandle(struct authhandle *auth);
+int authrenewcred(struct authhandle *handle);
+int authopensess(struct authhandle *handle);
+int authclosesess(struct authhandle *handle);
+void regmech(struct authmech *mech);
+
+extern struct authmech *mechs;
+
+#endif
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <malloc.h>
+#include <wchar.h>
+#include <string.h>
+#include <errno.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "client.h"
+#include "conf.h"
+#include "log.h"
+#include "utils.h"
+#include "module.h"
+#include "tiger.h"
+#include "net.h"
+#include "sysevents.h"
+
+struct scanstate
+{
+ struct scanstate *next;
+ struct sharecache *node;
+ DIR *dd;
+};
+
+struct scanqueue
+{
+ struct scanqueue *next;
+ struct scanstate *state;
+};
+
+static int conf_share(int argc, wchar_t **argv);
+static void freecache(struct sharecache *node);
+static void checkhashes(void);
+
+static struct configvar myvars[] =
+{
+ {CONF_VAR_STRING, "defnick", {.str = L"DoldaConnect user"}},
+ {CONF_VAR_INT, "scanfilemask", {.num = 0004}},
+ {CONF_VAR_INT, "scandirmask", {.num = 0005}},
+ {CONF_VAR_STRING, "hashcache", {.str = L"dc-hashcache"}},
+ {CONF_VAR_END}
+};
+
+static struct configcmd mycmds[] =
+{
+ {"share", conf_share},
+ {NULL}
+};
+
+static struct scanstate *scanjob = NULL;
+static struct scanqueue *scanqueue = NULL;
+static struct sharepoint *shares = NULL;
+static struct hashcache *hashcache = NULL;
+static pid_t hashjob = 0;
+struct sharecache *shareroot = NULL;
+unsigned long long sharesize = 0;
+GCBCHAIN(sharechangecb, unsigned long long);
+
+static int conf_share(int argc, wchar_t **argv)
+{
+ struct sharepoint *share;
+ char *b;
+
+ if(argc < 3)
+ {
+ flog(LOG_WARNING, "not enough arguments given for share command");
+ return(1);
+ }
+ if((b = icwcstombs(argv[2], NULL)) == NULL)
+ {
+ flog(LOG_WARNING, "could not convert wcs path (%ls) to current locale's charset: %s", argv[2], strerror(errno));
+ return(1);
+ }
+ for(share = shares; share != NULL; share = share->next)
+ {
+ if(!strcmp(share->path, b) && !wcscmp(share->name, argv[1]))
+ {
+ share->delete = 0;
+ free(b);
+ return(0);
+ }
+ }
+ share = smalloc(sizeof(*share));
+ share->path = b;
+ share->delete = 0;
+ share->name = swcsdup(argv[1]);
+ share->next = shares;
+ share->prev = NULL;
+ if(shares != NULL)
+ shares->prev = share;
+ shares = share;
+ return(0);
+}
+
+static void dumpsharecache(struct sharecache *node, int l)
+{
+ int i;
+
+ for(; node != NULL; node = node->next)
+ {
+ for(i = 0; i < l; i++)
+ putc('\t', stdout);
+ printf("%ls\n", node->name);
+ if(node->f.b.type == FILE_DIR)
+ dumpsharecache(node->child, l + 1);
+ }
+}
+
+static struct hashcache *newhashcache(void)
+{
+ struct hashcache *new;
+
+ new = smalloc(sizeof(*new));
+ memset(new, 0, sizeof(*new));
+ new->next = hashcache;
+ new->prev = NULL;
+ if(hashcache != NULL)
+ hashcache->prev = new;
+ hashcache = new;
+ return(new);
+}
+
+static void freehashcache(struct hashcache *hc)
+{
+ if(hc->next != NULL)
+ hc->next->prev = hc->prev;
+ if(hc->prev != NULL)
+ hc->prev->next = hc->next;
+ if(hc == hashcache)
+ hashcache = hc->next;
+ free(hc);
+}
+
+static char *findhashcachefile(int filldef)
+{
+ static char ret[128];
+ char *hcname;
+
+ if(getenv("HOME") != NULL)
+ {
+ snprintf(ret, sizeof(ret), "%s/.dc-hashcache", getenv("HOME"));
+ if(!access(ret, R_OK))
+ return(ret);
+ }
+ if((hcname = icswcstombs(confgetstr("cli", "hashcache"), NULL, NULL)) == NULL)
+ {
+ flog(LOG_WARNING, "could not convert hash cache name into local charset: %s", strerror(errno));
+ return(NULL);
+ }
+ if(strchr(hcname, '/') != NULL)
+ {
+ if(!access(hcname, R_OK))
+ {
+ strcpy(ret, hcname);
+ return(ret);
+ }
+ } else {
+ snprintf(ret, sizeof(ret), "/etc/%s", hcname);
+ if(!access(ret, R_OK))
+ return(ret);
+ snprintf(ret, sizeof(ret), "/usr/etc/%s", hcname);
+ if(!access(ret, R_OK))
+ return(ret);
+ snprintf(ret, sizeof(ret), "/usr/local/etc/%s", hcname);
+ if(!access(ret, R_OK))
+ return(ret);
+ }
+ if(filldef)
+ {
+ if(getenv("HOME") != NULL)
+ snprintf(ret, sizeof(ret), "%s/.dc-hashcache", getenv("HOME"));
+ else
+ snprintf(ret, sizeof(ret), "/etc/%s", hcname);
+ return(ret);
+ } else {
+ return(NULL);
+ }
+}
+
+static struct hashcache *findhashcache(dev_t dev, ino_t inode)
+{
+ struct hashcache *hc;
+
+ for(hc = hashcache; hc != NULL; hc = hc->next)
+ {
+ if((hc->dev == dev) && (hc->inode == inode))
+ return(hc);
+ }
+ return(NULL);
+}
+
+static void readhashcache(void)
+{
+ int i, wc, line;
+ char *hcname;
+ FILE *stream;
+ char linebuf[256];
+ char *p, *p2, *wv[32], *hash;
+ struct hashcache *hc;
+ size_t len;
+
+ if((hcname = findhashcachefile(0)) == NULL)
+ return;
+ if((stream = fopen(hcname, "r")) == NULL)
+ {
+ flog(LOG_WARNING, "could not open hash cache %s: %s", hcname, strerror(errno));
+ return;
+ }
+ while(hashcache != NULL)
+ freehashcache(hashcache);
+ line = 0;
+ while(!feof(stream))
+ {
+ fgets(linebuf, sizeof(linebuf), stream);
+ line++;
+ for(p = linebuf; *p; p++)
+ {
+ if(*p == '\n')
+ *p = ' ';
+ }
+ if(linebuf[0] == '#')
+ continue;
+ for(wc = 0, p = linebuf; (wc < 32) && ((p2 = strchr(p, ' ')) != NULL); p = p2 + 1)
+ {
+ if(p2 == p)
+ continue;
+ *p2 = 0;
+ wv[wc++] = p;
+ }
+ if(wc < 3)
+ continue;
+ hc = newhashcache();
+ hc->dev = strtoll(wv[0], NULL, 10);
+ hc->inode = strtoll(wv[1], NULL, 10);
+ hc->mtime = strtoll(wv[2], NULL, 10);
+ for(i = 3; i < wc; i++)
+ {
+ if(!strcmp(wv[i], "tth"))
+ {
+ if(++i >= wc)
+ continue;
+ hash = base64decode(wv[i], &len);
+ if(len != 24)
+ {
+ free(hash);
+ continue;
+ }
+ memcpy(hc->tth, hash, 24);
+ free(hash);
+ }
+ }
+ }
+ fclose(stream);
+}
+
+static void writehashcache(void)
+{
+ char *buf;
+ char *hcname;
+ FILE *stream;
+ struct hashcache *hc;
+
+ hcname = findhashcachefile(1);
+ if((stream = fopen(hcname, "w")) == NULL)
+ {
+ flog(LOG_WARNING, "could not write hash cache %s: %s", hcname, strerror(errno));
+ return;
+ }
+ fprintf(stream, "# Dolda Connect hash cache file\n");
+ fprintf(stream, "# Generated automatically, do not edit\n");
+ fprintf(stream, "# Format: DEVICE INODE MTIME [HASH...]\n");
+ fprintf(stream, "# HASH := HASHTYPE HASHVAL\n");
+ fprintf(stream, "# HASHTYPE can currently only be `tth'\n");
+ for(hc = hashcache; hc != NULL; hc = hc->next)
+ {
+ buf = base64encode(hc->tth, 24);
+ fprintf(stream, "%lli %lli %li tth %s\n", hc->dev, (long long)hc->inode, hc->mtime, buf);
+ free(buf);
+ }
+ fclose(stream);
+}
+
+static void hashread(struct socket *sk, void *uudata)
+{
+ static char *hashbuf;
+ static size_t hashbufsize = 0, hashbufdata = 0;
+ char *buf, *p, *p2, *lp;
+ size_t bufsize;
+ char *wv[32];
+ int wc;
+ dev_t dev;
+ ino_t inode;
+ time_t mtime;
+ struct hashcache *hc;
+
+ if((buf = sockgetinbuf(sk, &bufsize)) == NULL)
+ return;
+ bufcat(hashbuf, buf, bufsize);
+ free(buf);
+ while((lp = memchr(hashbuf, '\n', hashbufdata)) != NULL)
+ {
+ *(lp++) = 0;
+ wc = 0;
+ p = hashbuf;
+ while(1)
+ {
+ while((p2 = strchr(p, ' ')) == p)
+ p++;
+ wv[wc++] = p;
+ if(p2 == NULL)
+ {
+ break;
+ } else {
+ *p2 = 0;
+ p = p2 + 1;
+ }
+ }
+ if(wc != 4)
+ {
+ flog(LOG_ERR, "BUG: unexpected number of words (%i) arrived from hashing process", wc);
+ } else {
+ dev = strtoll(wv[0], NULL, 10);
+ inode = strtoll(wv[1], NULL, 10);
+ mtime = strtol(wv[2], NULL, 10);
+ if((hc = findhashcache(dev, inode)) == NULL)
+ {
+ hc = newhashcache();
+ hc->dev = dev;
+ hc->inode = inode;
+ }
+ hc->mtime = mtime;
+ buf = base64decode(wv[3], NULL);
+ memcpy(hc->tth, buf, 24);
+ free(buf);
+ writehashcache();
+ }
+ memmove(hashbuf, lp, hashbufdata -= (lp - hashbuf));
+ }
+}
+
+static void hashexit(pid_t pid, int status, void *uudata)
+{
+ if(pid != hashjob)
+ flog(LOG_ERR, "BUG: hashing process changed PID?! old: %i new %i", hashjob, pid);
+ if(status)
+ flog(LOG_WARNING, "hashing process exited with non-zero status: %i", status);
+ hashjob = 0;
+ checkhashes();
+}
+
+static int hashfile(char *path)
+{
+ int i, ret;
+ int fd;
+ int pfd[2];
+ char buf[4096];
+ struct stat sb;
+ struct tigertreehash tth;
+ char digest[24];
+ struct socket *outsock;
+
+ if((fd = open(path, O_RDONLY)) < 0)
+ {
+ flog(LOG_WARNING, "could not open %s for hashing: %s", path, strerror(errno));
+ return(1);
+ }
+ if(fstat(fd, &sb) < 0)
+ {
+ flog(LOG_WARNING, "could not stat %s while hashing: %s", path, strerror(errno));
+ close(fd);
+ return(1);
+ }
+ if(pipe(pfd) < 0)
+ {
+ flog(LOG_WARNING, "could not create pipe(!): %s", strerror(errno));
+ close(fd);
+ return(1);
+ }
+ hashjob = fork();
+ if(hashjob < 0)
+ {
+ flog(LOG_WARNING, "could not fork(!) hashing process: %s", strerror(errno));
+ close(fd);
+ close(pfd[0]);
+ close(pfd[1]);
+ return(1);
+ }
+ if(hashjob == 0)
+ {
+ nice(10);
+ signal(SIGHUP, SIG_DFL);
+ fd = dup2(fd, 4);
+ pfd[1] = dup2(pfd[1], 3);
+ dup2(fd, 0);
+ dup2(pfd[1], 1);
+ for(i = 3; i < FD_SETSIZE; i++)
+ close(i);
+ initlog();
+ inittigertree(&tth);
+ while((ret = read(0, buf, 4096)) > 0)
+ dotigertree(&tth, buf, ret);
+ if(ret < 0)
+ {
+ flog(LOG_WARNING, "could not read from %s while hashing: %s", path, strerror(errno));
+ exit(1);
+ }
+ synctigertree(&tth);
+ restigertree(&tth, digest);
+ ret = snprintf(buf, sizeof(buf), "%lli %lli %li %s\n", sb.st_dev, (long long)sb.st_ino, sb.st_mtime, base64encode(digest, 24));
+ write(1, buf, ret);
+ exit(0);
+ }
+ close(fd);
+ close(pfd[1]);
+ outsock = wrapsock(pfd[0]);
+ outsock->readcb = hashread;
+ childcallback(hashjob, hashexit, NULL);
+ return(0);
+}
+
+/*
+ * Call only when hashjob == 0
+ */
+static void checkhashes(void)
+{
+ struct sharecache *node;
+ struct hashcache *hc;
+ char *path;
+
+ node = shareroot->child;
+ while(1)
+ {
+ if(node->child != NULL)
+ {
+ node = node->child;
+ continue;
+ }
+ if(!node->f.b.hastth)
+ {
+ if((hc = findhashcache(node->dev, node->inode)) != NULL)
+ {
+ memcpy(node->hashtth, hc->tth, 24);
+ node->f.b.hastth = 1;
+ GCBCHAINDOCB(sharechangecb, sharesize);
+ } else {
+ path = getfspath(node);
+ if(hashfile(path))
+ {
+ flog(LOG_WARNING, "could not hash %s, unsharing it", path);
+ freecache(node);
+ }
+ free(path);
+ return;
+ }
+ }
+ while(node->next == NULL)
+ {
+ if((node = node->parent) == shareroot)
+ break;
+ }
+ if(node == shareroot)
+ break;
+ node = node->next;
+ }
+}
+
+struct sharecache *nextscnode(struct sharecache *node)
+{
+ if(node->child != NULL)
+ return(node->child);
+ while(node->next == NULL)
+ {
+ node = node->parent;
+ if(node == shareroot)
+ return(NULL);
+ }
+ return(node->next);
+}
+
+static void freescan(struct scanstate *job)
+{
+ if(job->dd != NULL)
+ closedir(job->dd);
+ free(job);
+}
+
+/* No need for optimization; lookup isn't really that common */
+struct sharecache *findcache(struct sharecache *parent, wchar_t *name)
+{
+ struct sharecache *node;
+
+ for(node = parent->child; node != NULL; node = node->next)
+ {
+ if(!wcscmp(node->name, name))
+ return(node);
+ }
+ return(NULL);
+}
+
+static void attachcache(struct sharecache *parent, struct sharecache *node)
+{
+ node->parent = parent;
+ node->next = parent->child;
+ if(parent->child != NULL)
+ parent->child->prev = node;
+ parent->child = node;
+}
+
+static void detachcache(struct sharecache *node)
+{
+ if(node->next != NULL)
+ node->next->prev = node->prev;
+ if(node->prev != NULL)
+ node->prev->next = node->next;
+ if((node->parent != NULL) && (node->parent->child == node))
+ node->parent->child = node->next;
+ node->parent = NULL;
+ node->next = NULL;
+ node->prev = NULL;
+}
+
+static void freecache(struct sharecache *node)
+{
+ struct sharecache *cur, *next;
+ struct scanqueue *q, *nq, **fq;
+
+ detachcache(node);
+ fq = &scanqueue;
+ for(q = scanqueue; q != NULL; q = nq)
+ {
+ nq = q->next;
+ if(q->state->node == node)
+ {
+ flog(LOG_DEBUG, "freed node %ls cancelled queued scan", node->name);
+ freescan(q->state);
+ *fq = q->next;
+ free(q);
+ continue;
+ }
+ fq = &q->next;
+ }
+ if(node->child != NULL)
+ {
+ for(cur = node->child; cur != NULL; cur = next)
+ {
+ next = cur->next;
+ freecache(cur);
+ }
+ }
+ CBCHAINDOCB(node, share_delete, node);
+ CBCHAINFREE(node, share_delete);
+ sharesize -= node->size;
+ if(node->path != NULL)
+ free(node->path);
+ if(node->name != NULL)
+ free(node->name);
+ free(node);
+}
+
+static void freesharepoint(struct sharepoint *share)
+{
+ struct sharecache *node;
+
+ if(share->next != NULL)
+ share->next->prev = share->prev;
+ if(share->prev != NULL)
+ share->prev->next = share->next;
+ if(share == shares)
+ shares = share->next;
+ if((node = findcache(shareroot, share->name)) != NULL)
+ freecache(node);
+ free(share->path);
+ free(share->name);
+ free(share);
+}
+
+static struct sharecache *newcache(void)
+{
+ struct sharecache *new;
+
+ new = smalloc(sizeof(*new));
+ memset(new, 0, sizeof(*new));
+ CBCHAININIT(new, share_delete);
+ return(new);
+}
+
+char *getfspath(struct sharecache *node)
+{
+ char *buf, *mbsname;
+ size_t bufsize;
+
+ buf = smalloc(bufsize = 64);
+ *buf = 0;
+ while(node != NULL)
+ {
+ if(node->path != NULL)
+ {
+ if(bufsize < strlen(node->path) + strlen(buf) + 1)
+ buf = srealloc(buf, strlen(node->path) + strlen(buf) + 1);
+ memmove(buf + strlen(node->path), buf, strlen(buf) + 1);
+ memcpy(buf, node->path, strlen(node->path));
+ return(buf);
+ }
+ if((mbsname = icwcstombs(node->name, NULL)) == NULL)
+ {
+ flog(LOG_WARNING, "could not map unicode share name (%ls) into filesystem charset: %s", node->name, strerror(errno));
+ free(buf);
+ return(NULL);
+ }
+ while(bufsize < strlen(mbsname) + 1 + strlen(buf) + 1)
+ buf = srealloc(buf, bufsize *= 2);
+ memmove(buf + strlen(mbsname) + 1, buf, strlen(buf) + 1);
+ memcpy(buf + 1, mbsname, strlen(mbsname));
+ *buf = '/';
+ free(mbsname);
+ node = node->parent;
+ }
+ buf = srealloc(buf, strlen(buf) + 1);
+ return(buf);
+}
+
+static int checknode(struct sharecache *node)
+{
+ char *path;
+ struct stat sb;
+
+ if(node->parent == NULL)
+ {
+ return(1);
+ } else {
+ if(!checknode(node->parent))
+ return(0);
+ path = getfspath(node);
+ if(stat(path, &sb) < 0)
+ {
+ flog(LOG_INFO, "%s was found to be broken (%s); scheduling rescan of parent", path, strerror(errno));
+ queuescan(node->parent);
+ return(0);
+ } else {
+ return(1);
+ }
+ }
+}
+
+int opensharecache(struct sharecache *node)
+{
+ char *path;
+ int fd, errbak;
+
+ path = getfspath(node);
+ fd = open(path, O_RDONLY);
+ errbak = errno;
+ if(fd < 0)
+ {
+ flog(LOG_WARNING, "could not open %s: %s", path, strerror(errbak));
+ checknode(node);
+ }
+ free(path);
+ errno = errbak;
+ return(fd);
+}
+
+static struct scanstate *newscan(struct sharecache *node)
+{
+ struct scanstate *new;
+
+ new = smalloc(sizeof(*new));
+ new->next = NULL;
+ new->node = node;
+ new->dd = NULL;
+ return(new);
+}
+
+void queuescan(struct sharecache *node)
+{
+ struct scanqueue *new;
+
+ new = smalloc(sizeof(*new));
+ new->state = newscan(node);
+ new->next = scanqueue;
+ scanqueue = new;
+}
+
+/* For internal use in doscan() */
+static void removestale(struct sharecache *node)
+{
+ struct sharecache *cur, *next;
+
+ for(cur = node->child; cur != NULL; cur = next)
+ {
+ next = cur->next;
+ if(!cur->f.b.found)
+ freecache(cur);
+ }
+}
+
+/* For internal use in doscan() */
+static void jobdone(void)
+{
+ struct scanstate *jbuf;
+
+ jbuf = scanjob;
+ scanjob = jbuf->next;
+ freescan(jbuf);
+ if(scanjob != NULL)
+ fchdir(dirfd(scanjob->dd));
+}
+
+int doscan(int quantum)
+{
+ char *path;
+ wchar_t *wcs;
+ int type;
+ struct sharecache *n;
+ struct scanstate *jbuf;
+ struct scanqueue *qbuf;
+ struct dirent *de;
+ struct stat sb;
+ struct hashcache *hc;
+ int dmask, fmask;
+ static int busybefore = 0;
+
+ dmask = confgetint("cli", "scandirmask");
+ fmask = confgetint("cli", "scanfilemask");
+ if((scanjob != NULL) && (scanjob->dd != NULL))
+ {
+ while(fchdir(dirfd(scanjob->dd)) < 0)
+ {
+ flog(LOG_WARNING, "could not fchdir to fd %i: %s", dirfd(scanjob->dd), strerror(errno));
+ removestale(scanjob->node);
+ jobdone();
+ }
+ }
+ while(quantum-- > 0)
+ {
+ if(scanjob != NULL)
+ {
+ busybefore = 1;
+ } else {
+ while(scanjob == NULL)
+ {
+ if(scanqueue == NULL)
+ {
+ if(busybefore)
+ {
+ flog(LOG_INFO, "sharing %lli bytes", sharesize);
+ busybefore = 0;
+ GCBCHAINDOCB(sharechangecb, sharesize);
+ if(hashjob == 0)
+ checkhashes();
+ }
+ return(0);
+ }
+ busybefore = 1;
+ scanjob = scanqueue->state;
+ qbuf = scanqueue;
+ scanqueue = qbuf->next;
+ free(qbuf);
+ for(n = scanjob->node->child; n != NULL; n = n->next)
+ n->f.b.found = 0;
+ }
+ }
+ if(scanjob->dd == NULL)
+ {
+ path = getfspath(scanjob->node);
+ if((scanjob->dd = opendir(path)) == NULL)
+ {
+ flog(LOG_WARNING, "cannot open directory %s for scanning: %s, deleting from share", path, strerror(errno));
+ freecache(scanjob->node);
+ free(path);
+ jobdone();
+ continue;
+ }
+ free(path);
+ if(fchdir(dirfd(scanjob->dd)) < 0)
+ {
+ flog(LOG_WARNING, "could not fchdir to fd %i: %s", dirfd(scanjob->dd), strerror(errno));
+ jobdone();
+ continue;
+ }
+ }
+ if((de = readdir(scanjob->dd)) == NULL)
+ {
+ removestale(scanjob->node);
+ jobdone();
+ continue;
+ }
+ if(*de->d_name == '.')
+ continue;
+ if((wcs = icmbstowcs(de->d_name, NULL)) == NULL)
+ {
+ flog(LOG_WARNING, "file name %s has cannot be converted to wchar: %s", de->d_name, strerror(errno));
+ continue;
+ }
+ n = findcache(scanjob->node, wcs);
+ if(stat(de->d_name, &sb) < 0)
+ {
+ free(wcs);
+ if(n != NULL)
+ {
+ flog(LOG_WARNING, "could not stat %s: %s, deleting from share", de->d_name, strerror(errno));
+ freecache(n);
+ } else {
+ flog(LOG_WARNING, "could not stat %s: %s", de->d_name, strerror(errno));
+ }
+ continue;
+ }
+ if(S_ISDIR(sb.st_mode))
+ {
+ if(~sb.st_mode & dmask)
+ {
+ free(wcs);
+ continue;
+ }
+ type = FILE_DIR;
+ } else if(S_ISREG(sb.st_mode)) {
+ if(~sb.st_mode & fmask)
+ {
+ free(wcs);
+ continue;
+ }
+ type = FILE_REG;
+ } else {
+ flog(LOG_WARNING, "unhandled file type: %i", sb.st_mode);
+ free(wcs);
+ continue;
+ }
+ if(n != NULL)
+ {
+ if((n->f.b.type != type) || (n->mtime != sb.st_mtime) || ((type == FILE_REG) && (n->size != sb.st_size)))
+ {
+ freecache(n);
+ n = NULL;
+ }
+ }
+ if(n == NULL)
+ {
+ n = newcache();
+ n->name = wcs;
+ if(S_ISREG(sb.st_mode))
+ {
+ sharesize += (n->size = sb.st_size);
+ } else {
+ n->size = 0;
+ }
+ n->mtime = sb.st_mtime;
+ n->dev = sb.st_dev;
+ n->inode = sb.st_ino;
+ n->f.b.type = type;
+ attachcache(scanjob->node, n);
+ } else {
+ free(wcs);
+ }
+ n->f.b.found = 1;
+ if(n->f.b.type == FILE_DIR)
+ {
+ jbuf = newscan(n);
+ jbuf->next = scanjob;
+ scanjob = jbuf;
+ } else if(n->f.b.type == FILE_REG) {
+ if(n->f.b.hastth && (n->mtime != sb.st_mtime))
+ n->f.b.hastth = 0;
+ if(!n->f.b.hastth)
+ {
+ if((hc = findhashcache(sb.st_dev, sb.st_ino)) != NULL)
+ {
+ if(hc->mtime == n->mtime)
+ {
+ n->f.b.hastth = 1;
+ memcpy(n->hashtth, hc->tth, 24);
+ } else {
+ freehashcache(hc);
+ }
+ }
+ }
+ }
+ }
+ return(1);
+}
+
+void scanshares(void)
+{
+ struct sharepoint *cur;
+ struct sharecache *node;
+ struct stat sb;
+
+ for(cur = shares; cur != NULL; cur = cur->next)
+ {
+ if((node = findcache(shareroot, cur->name)) == NULL)
+ {
+ if(stat(cur->path, &sb))
+ {
+ flog(LOG_WARNING, "could not stat share \"%ls\": %s", cur->name, strerror(errno));
+ continue;
+ }
+ if(!S_ISDIR(sb.st_mode))
+ {
+ flog(LOG_WARNING, "%s is not a directory; won't share it", cur->path);
+ continue;
+ }
+ node = newcache();
+ node->name = swcsdup(cur->name);
+ node->path = sstrdup(cur->path);
+ if(node->path[strlen(node->path) - 1] == '/')
+ node->path[strlen(node->path) - 1] = 0;
+ node->f.b.type = FILE_DIR;
+ attachcache(shareroot, node);
+ }
+ queuescan(node);
+ }
+}
+
+static void preinit(int hup)
+{
+ struct sharepoint *cur;
+
+ if(hup)
+ {
+ for(cur = shares; cur != NULL; cur = cur->next)
+ cur->delete = 1;
+ } else {
+ shareroot = newcache();
+ shareroot->name = swcsdup(L"");
+ shareroot->f.b.type = FILE_DIR;
+ }
+}
+
+static int init(int hup)
+{
+ struct sharepoint *cur, *next;
+
+ readhashcache();
+ for(cur = shares; cur != NULL; cur = next)
+ {
+ next = cur->next;
+ if(cur->delete)
+ freesharepoint(cur);
+ }
+ scanshares();
+ if(!hup)
+ while(doscan(100));
+ return(0);
+}
+
+static int run(void)
+{
+ return(doscan(10));
+}
+
+static void terminate(void)
+{
+ if(hashjob != 0)
+ kill(hashjob, SIGHUP);
+ while(shares != NULL)
+ freesharepoint(shares);
+ freecache(shareroot);
+}
+
+static struct module me =
+{
+ .name = "cli",
+ .conf =
+ {
+ .vars = myvars,
+ .cmds = mycmds
+ },
+ .preinit = preinit,
+ .init = init,
+ .run = run,
+ .terminate = terminate
+};
+
+MODULE(me)
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#ifndef _CLIENT_H
+#define _CLIENT_H
+
+#include <sys/types.h>
+
+#include <utils.h>
+
+#define FILE_REG 0
+#define FILE_DIR 1
+#define FILE_INT 2
+
+#define HASHHASHSIZE 12
+
+struct sharepoint
+{
+ struct sharepoint *prev, *next;
+ char *path;
+ wchar_t *name;
+ int delete;
+};
+
+struct hashcache
+{
+ struct hashcache *next, *prev;
+ dev_t dev;
+ ino_t inode;
+ time_t mtime;
+ char tth[24];
+};
+
+struct sharecache
+{
+ struct sharecache *next, *prev, *child, *parent;
+ char *path;
+ wchar_t *name;
+ size_t size;
+ time_t mtime;
+ dev_t dev;
+ ino_t inode;
+ char hashtth[24];
+ union
+ {
+ struct
+ {
+ int type:3;
+ int hastth:1;
+ int found:1;
+ } b;
+ int w;
+ } f;
+ CBCHAIN(share_delete, struct sharecache *);
+};
+
+void clientpreinit(void);
+int clientinit(void);
+int doscan(int quantum);
+int opensharecache(struct sharecache *node);
+struct sharecache *findcache(struct sharecache *parent, wchar_t *name);
+void queuescan(struct sharecache *node);
+char *getfspath(struct sharecache *node);
+struct sharecache *nextscnode(struct sharecache *node);
+
+extern struct sharecache *shareroot;
+extern unsigned long long sharesize;
+EGCBCHAIN(sharechangecb, unsigned long long);
+
+#endif
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#include <langinfo.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <malloc.h>
+#include <string.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <wctype.h>
+#include <stddef.h>
+#include <wchar.h>
+#include <iconv.h>
+#include <arpa/inet.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "conf.h"
+#include "log.h"
+#include "utils.h"
+
+static struct configmod *modules = NULL;
+
+#if 0
+static void dumpconfig(void)
+{
+ struct configmod *mod;
+ struct configvar *var;
+
+ for(mod = modules; mod != NULL; mod = mod->next)
+ {
+ printf("%s:\n", mod->name);
+ for(var = mod->vars; var->type != CONF_VAR_END; var++)
+ {
+ switch(var->type)
+ {
+ case CONF_VAR_BOOL:
+ printf("\t%s: %s\n", var->name, var->val.num?"t":"f");
+ break;
+ case CONF_VAR_INT:
+ printf("\t%s: %i\n", var->name, var->val.num);
+ break;
+ case CONF_VAR_STRING:
+ printf("\t%s: \"%ls\" (%i)\n", var->name, var->val.str, wcslen(var->val.str));
+ break;
+ case CONF_VAR_IPV4:
+ printf("\t%s: %s\n", var->name, inet_ntoa(var->val.ipv4));
+ break;
+ }
+ }
+ }
+}
+#endif
+
+struct configvar *confgetvar(char *modname, char *varname)
+{
+ struct configmod *m;
+ struct configvar *v;
+
+ for(m = modules; m != NULL; m = m->next)
+ {
+ if(!strcmp(m->name, modname))
+ {
+ for(v = m->vars; v->type != CONF_VAR_END; v++)
+ {
+ if(!strcmp(v->name, varname))
+ return(v);
+ }
+ break;
+ }
+ }
+ return(NULL);
+}
+
+void confregmod(struct configmod *mod)
+{
+ struct configvar *var;
+
+ for(var = mod->vars; var->type != CONF_VAR_END; var++)
+ {
+ switch(var->type)
+ {
+ case CONF_VAR_BOOL:
+ case CONF_VAR_INT:
+ var->val.num = var->defaults.num;
+ break;
+ case CONF_VAR_STRING:
+ if(var->defaults.str != NULL)
+ {
+ var->val.str = swcsdup(var->defaults.str);
+ } else {
+ var->val.str = NULL;
+ }
+ break;
+ case CONF_VAR_IPV4:
+ var->val.ipv4.s_addr = var->defaults.ipv4.s_addr;
+ break;
+ }
+ CBCHAININIT(var, conf_update);
+ }
+ mod->next = modules;
+ modules = mod;
+}
+
+int runconfcmd(int argc, wchar_t **argv)
+{
+ struct configmod *module;
+ struct configvar *var;
+ struct configcmd *cmd;
+ int ret, handled;
+ wchar_t *p;
+ char *cmdn, *buf, *buf2, *valbuf;
+ long num;
+ struct in_addr newipv4;
+ int cb;
+
+ if(argc < 1)
+ return(0);
+ if((cmdn = icwcstombs(argv[0], "us-ascii")) == NULL)
+ {
+ flog(LOG_WARNING, "could not convert %ls to us-ascii", argv[0]);
+ return(1);
+ }
+ ret = 1;
+ handled = 0;
+ if(!strcmp(cmdn, "set"))
+ {
+ handled = 1;
+ ret = 0;
+ if((p = wcschr(argv[1], L'.')) == NULL)
+ {
+ flog(LOG_WARNING, "illegal configuration variable format: %ls", argv[1]);
+ errno = EINVAL;
+ free(cmdn);
+ return(1);
+ }
+ *(p++) = L'\0';
+ if((buf = icwcstombs(argv[1], "us-ascii")) == NULL)
+ {
+ flog(LOG_WARNING, "could not convert %ls to us-ascii", argv[1]);
+ free(cmdn);
+ return(1);
+ }
+ if((buf2 = icwcstombs(p, "us-ascii")) == NULL)
+ {
+ free(buf);
+ flog(LOG_WARNING, "could not convert %ls to us-ascii", p);
+ free(cmdn);
+ return(1);
+ }
+ for(module = modules; module != NULL; module = module->next)
+ {
+ if(!strcmp(module->name, buf) && (module->vars != NULL))
+ {
+ for(var = module->vars; var->type != CONF_VAR_END; var++)
+ {
+ if(!strcmp(var->name, buf2))
+ {
+ cb = 0;
+ switch(var->type)
+ {
+ case CONF_VAR_BOOL:
+ wcstolower(argv[2]);
+ if(!wcscmp(argv[2], L"off") ||
+ !wcscmp(argv[2], L"false") ||
+ !wcscmp(argv[2], L"no") ||
+ !wcscmp(argv[2], L"0"))
+ {
+ if(var->val.num)
+ cb = 1;
+ var->val.num = 0;
+ } else if(!wcscmp(argv[2], L"on") ||
+ !wcscmp(argv[2], L"true") ||
+ !wcscmp(argv[2], L"yes") ||
+ !wcscmp(argv[2], L"1")) {
+ if(!var->val.num)
+ cb = 1;
+ var->val.num = 1;
+ } else {
+ flog(LOG_WARNING, "unrecognized boolean: %ls", argv[2]);
+ }
+ break;
+ case CONF_VAR_INT:
+ num = wcstol(argv[2], &p, 0);
+ if(p == argv[2])
+ {
+ flog(LOG_WARNING, "%ls: not a number, ignoring", argv[2]);
+ ret = 1;
+ } else {
+ if(*p != L'\0')
+ flog(LOG_WARNING, "%ls: could not entirely parse as a number, ignoring trailing garbage", argv[2]);
+ if(num != var->val.num)
+ cb = 1;
+ var->val.num = num;
+ }
+ break;
+ case CONF_VAR_STRING:
+ if(wcscmp(var->val.str, argv[2]))
+ cb = 1;
+ free(var->val.str);
+ var->val.str = swcsdup(argv[2]);
+ break;
+ case CONF_VAR_IPV4:
+ if((valbuf = icwcstombs(argv[2], "us-ascii")) == NULL)
+ {
+ flog(LOG_WARNING, "could not convert IPv4 address to as-ascii in var %s, ignoring", buf2);
+ } else {
+ if(!inet_aton(valbuf, &newipv4))
+ {
+ flog(LOG_WARNING, "could not parse IPv4 address (%s), ignoring", valbuf);
+ memcpy(&var->val.ipv4, &var->defaults.ipv4, sizeof(var->val.ipv4));
+ } else {
+ if(memcmp(&newipv4, &var->val.ipv4, sizeof(newipv4)))
+ cb = 1;
+ memcpy(&var->val.ipv4, &newipv4, sizeof(newipv4));
+ }
+ free(valbuf);
+ }
+ break;
+ }
+ if(cb)
+ CBCHAINDOCB(var, conf_update, var);
+ break;
+ }
+ }
+ if(var == NULL)
+ flog(LOG_WARNING, "variable %s not found, ignoring set command", buf2);
+ break;
+ }
+ }
+ if(module == NULL)
+ flog(LOG_WARNING, "module %s not found, ignoring set command", buf);
+ free(buf2);
+ free(buf);
+ }
+ for(module = modules; !handled && (module != NULL); module = module->next)
+ {
+ if(module->cmds != NULL)
+ {
+ for(cmd = module->cmds; cmd->name != NULL; cmd++)
+ {
+ if(!strcmp(cmd->name, cmdn))
+ {
+ handled = 1;
+ ret = cmd->handler(argc, argv);
+ break;
+ }
+ }
+ }
+ }
+ if(!handled)
+ flog(LOG_WARNING, "command not found: %s", cmdn);
+ free(cmdn);
+ return(ret);
+}
+
+char *findconfigfile(void)
+{
+ static char pathbuf[128];
+ char *p, *p2;
+
+ if(getenv("HOME") != NULL)
+ {
+ snprintf(pathbuf, sizeof(pathbuf), "%s/.doldacond", getenv("HOME"));
+ if(!access(pathbuf, R_OK))
+ return(pathbuf);
+ }
+ p = CONFIG_PATH;
+ do
+ {
+ p2 = strchr(p, ':');
+ if(p2 != NULL)
+ {
+ memcpy(pathbuf, p, p2 - p);
+ pathbuf[p2 - p] = 0;
+ if(!access(pathbuf, R_OK))
+ return(pathbuf);
+ } else {
+ if(!access(p, R_OK))
+ return(p);
+ }
+ p = p2 + 1;
+ } while(p2 != NULL);
+ return(NULL);
+}
+
+void readconfig(FILE *stream)
+{
+ int state;
+ wint_t c;
+ wchar_t *words[16];
+ wchar_t *buf, *p, *p2;
+ int w;
+ int line;
+
+ buf = smalloc(sizeof(wchar_t) * 1024);
+ state = 0;
+ c = getwc(stream);
+ w = 0;
+ line = 1;
+ p = buf;
+ while(c != WEOF)
+ {
+ if(c == '#')
+ {
+ do
+ c = getwc(stream);
+ while((c != WEOF) && (c != L'\n'));
+ continue;
+ }
+ switch(state)
+ {
+ case 0:
+ if(iswspace(c))
+ {
+ if(c == L'\n')
+ {
+ line++;
+ if(runconfcmd(w, words))
+ flog(LOG_WARNING, "ignoring this command on line %i", line);
+ w = 0;
+ }
+ c = getwc(stream);
+ } else {
+ state = 1;
+ p2 = p;
+ }
+ break;
+ case 1:
+ if(c == L'\"')
+ {
+ state = 2;
+ c = getwc(stream);
+ } else if(iswspace(c)) {
+ if(w >= 16)
+ {
+ flog(LOG_WARNING, "too many words on config line %i, ignoring rest", line);
+ } else {
+ *(p++) = L'\0';
+ words[w++] = p2;
+ }
+ state = 0;
+ } else {
+ if(c == L'\\')
+ c = getwc(stream);
+ if(p - buf < 1023)
+ *(p++) = c;
+ else
+ flog(LOG_WARNING, "too many characters on config line %i, ignoring rest", line);
+ c = getwc(stream);
+ }
+ break;
+ case 2:
+ if(c == L'\"')
+ {
+ c = getwc(stream);
+ state = 1;
+ } else {
+ if(c == L'\\')
+ c = getwc(stream);
+ if(p - buf < 1023)
+ *(p++) = c;
+ else
+ flog(LOG_WARNING, "too many characters on config line %i, ignoring rest", line);
+ c = getwc(stream);
+ }
+ break;
+ }
+ }
+ free(buf);
+ if(ferror(stream))
+ flog(LOG_WARNING, "error on configuration stream: %s", strerror(errno));
+ if(state != 0)
+ flog(LOG_WARNING, "unexpected end of file");
+}
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#ifndef _CONF_H
+#define _CONF_H
+
+#include <stddef.h>
+#include <stdio.h>
+#include <netinet/in.h>
+#include <utils.h>
+
+#define CONFIG_PATH "/usr/local/etc/doldacond.conf:/usr/etc/doldacond.conf:/etc/doldacond.conf"
+
+#define CONF_VAR_END -1
+#define CONF_VAR_BOOL 0
+#define CONF_VAR_INT 1
+#define CONF_VAR_STRING 2
+#define CONF_VAR_IPV4 3
+
+struct configvar
+{
+ int type;
+ char *name;
+ union
+ {
+ int num;
+ wchar_t *str;
+ struct in_addr ipv4;
+ } defaults;
+ union
+ {
+ int num;
+ wchar_t *str;
+ struct in_addr ipv4;
+ } val;
+ CBCHAIN(conf_update, struct configvar *);
+};
+
+struct configcmd
+{
+ char *name;
+ int (*handler)(int argc, wchar_t **argv);
+};
+
+struct configmod
+{
+ struct configmod *next;
+ char *name;
+ struct configvar *vars;
+ struct configcmd *cmds;
+};
+
+struct configvar *confgetvar(char *modname, char *varname);
+#define confgetint(m, v) (confgetvar((m), (v))->val.num)
+#define confgetstr(m, v) (confgetvar((m), (v))->val.str)
+void confregmod(struct configmod *mod);
+void readconfig(FILE *stream);
+char *findconfigfile(void);
+
+#endif
--- /dev/null
+; -*-Lisp-*-
+
+; Use with:
+; (add-hook 'find-file-hooks
+; (lambda ()
+; (load (concat default-directory "emacs-local") t)))
+
+(if
+ (string-match "\\.[ch]$" (buffer-file-name (current-buffer)))
+ (progn
+ (make-local-variable 'compile-command)
+ (setq compile-command "make -k 'CFLAGS=-g -Wall'")
+))
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#include <string.h>
+#include <wchar.h>
+#include <sys/socket.h>
+#include <errno.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "filenet.h"
+#include "search.h"
+#include "module.h"
+#include "utils.h"
+#include "net.h"
+
+static struct fnet *networks = NULL;
+struct fnetnode *fnetnodes = NULL;
+int numfnetnodes = 0;
+GCBCHAIN(newfncb, struct fnetnode *);
+
+static struct fnetnode *newfn(struct fnet *fnet)
+{
+ static int curid = 0;
+ struct fnetnode *new;
+
+ new = smalloc(sizeof(*new));
+ memset(new, 0, sizeof(*new));
+ new->fnet = fnet;
+ new->refcount = 1;
+ new->id = curid++;
+ new->mynick = swcsdup(confgetstr("cli", "defnick"));
+ new->srchwait = confgetint("fnet", "srchwait");
+ new->state = FNN_SYN;
+ CBCHAININIT(new, fnetnode_ac);
+ CBCHAININIT(new, fnetnode_chat);
+ CBCHAININIT(new, fnetnode_unlink);
+ CBCHAININIT(new, fnetnode_destroy);
+ new->next = NULL;
+ new->prev = NULL;
+ numfnetnodes++;
+ return(new);
+}
+
+void killfnetnode(struct fnetnode *fn)
+{
+ fnetsetstate(fn, FNN_DEAD);
+ if(fn->sk != NULL)
+ {
+ fn->sk->close = 1;
+ if(fn->sk->data == fn)
+ putfnetnode(fn);
+ putsock(fn->sk);
+ fn->sk = NULL;
+ }
+}
+
+void getfnetnode(struct fnetnode *fn)
+{
+ fn->refcount++;
+#ifdef DEBUG
+ fprintf(stderr, "getfnetnode on id %i at %p, refcount=%i\n", fn->id, fn, fn->refcount);
+#endif
+}
+
+void putfnetnode(struct fnetnode *fn)
+{
+ struct fnetnode *cur;
+
+#ifdef DEBUG
+ fprintf(stderr, "putfnetnode on id %i at %p, refcount=%i\n", fn->id, fn, fn->refcount - 1);
+#endif
+ if(--fn->refcount)
+ return;
+ for(cur = fnetnodes; cur != NULL; cur = cur->next)
+ {
+ if(cur == fn)
+ flog(LOG_CRIT, "BUG: fnetnode reached refcount 0 while still in list - id %i", fn->id);
+ }
+ CBCHAINDOCB(fn, fnetnode_destroy, fn);
+ CBCHAINFREE(fn, fnetnode_ac);
+ CBCHAINFREE(fn, fnetnode_chat);
+ CBCHAINFREE(fn, fnetnode_unlink);
+ CBCHAINFREE(fn, fnetnode_destroy);
+ if(fn->fnet->destroy != NULL)
+ fn->fnet->destroy(fn);
+ while(fn->peers != NULL)
+ fnetdelpeer(fn->peers);
+ if(fn->mynick != NULL)
+ free(fn->mynick);
+ if(fn->name != NULL)
+ free(fn->name);
+ if(fn->sk != NULL)
+ putsock(fn->sk);
+ free(fn);
+ numfnetnodes--;
+}
+
+struct fnetnode *findfnetnode(int id)
+{
+ struct fnetnode *fn;
+
+ for(fn = fnetnodes; (fn != NULL) && (fn->id != id); fn = fn->next);
+ return(fn);
+}
+
+void linkfnetnode(struct fnetnode *fn)
+{
+ if(fn->linked)
+ return;
+ getfnetnode(fn);
+ fn->next = fnetnodes;
+ if(fnetnodes != NULL)
+ fnetnodes->prev = fn;
+ fnetnodes = fn;
+ fn->linked = 1;
+ GCBCHAINDOCB(newfncb, fn);
+}
+
+void unlinkfnetnode(struct fnetnode *fn)
+{
+ if(!fn->linked)
+ return;
+ if(fnetnodes == fn)
+ fnetnodes = fn->next;
+ if(fn->next != NULL)
+ fn->next->prev = fn->prev;
+ if(fn->prev != NULL)
+ fn->prev->next = fn->next;
+ fn->linked = 0;
+ CBCHAINDOCB(fn, fnetnode_unlink, fn);
+ putfnetnode(fn);
+}
+
+static void conncb(struct socket *sk, int err, struct fnetnode *data)
+{
+ if(err != 0)
+ {
+ killfnetnode(data);
+ putfnetnode(data);
+ return;
+ }
+ data->sk = sk;
+ fnetsetstate(data, FNN_HS);
+ socksettos(sk, confgetint("fnet", "fntos"));
+ data->fnet->connect(data);
+ putfnetnode(data);
+}
+
+static void resolvecb(struct sockaddr *addr, int addrlen, struct fnetnode *data)
+{
+ if(addr == NULL)
+ {
+ killfnetnode(data);
+ putfnetnode(data);
+ } else {
+ netcsconn(addr, addrlen, (void (*)(struct socket *, int, void *))conncb, data);
+ }
+}
+
+static struct fnetpeerdatum *finddatum(struct fnetnode *fn, wchar_t *id)
+{
+ struct fnetpeerdatum *datum;
+
+ for(datum = fn->peerdata; datum != NULL; datum = datum->next)
+ {
+ if(!wcscmp(datum->id, id))
+ break;
+ }
+ return(datum);
+}
+
+static struct fnetpeerdatum *adddatum(struct fnetnode *fn, wchar_t *id, int datatype)
+{
+ struct fnetpeerdatum *new;
+
+ new = smalloc(sizeof(*new));
+ new->refcount = 0;
+ new->id = swcsdup(id);
+ new->datatype = datatype;
+ new->prev = NULL;
+ new->next = fn->peerdata;
+ if(fn->peerdata != NULL)
+ fn->peerdata->prev = new;
+ fn->peerdata = new;
+ return(new);
+}
+
+static struct fnetpeerdi *difindoradd(struct fnetpeer *peer, struct fnetpeerdatum *datum)
+{
+ int i;
+
+ for(i = 0; i < peer->dinum; i++)
+ {
+ if(peer->peerdi[i].datum == datum)
+ break;
+ }
+ if(i >= peer->dinum)
+ {
+ peer->peerdi = srealloc(peer->peerdi, sizeof(struct fnetpeerdi) * (peer->dinum + 1));
+ memset(&peer->peerdi[peer->dinum], 0, sizeof(struct fnetpeerdi));
+ peer->peerdi[peer->dinum].datum = datum;
+ datum->refcount++;
+ return(&peer->peerdi[peer->dinum++]);
+ } else {
+ return(&peer->peerdi[i]);
+ }
+}
+
+void fnetpeersetstr(struct fnetpeer *peer, wchar_t *id, wchar_t *value)
+{
+ struct fnetpeerdatum *datum;
+ struct fnetpeerdi *di;
+
+ if((datum = finddatum(peer->fn, id)) == NULL)
+ datum = adddatum(peer->fn, id, FNPD_STR);
+ di = difindoradd(peer, datum);
+ if(di->data.str != NULL)
+ free(di->data.str);
+ di->data.str = swcsdup(value);
+}
+
+void fnetpeersetnum(struct fnetpeer *peer, wchar_t *id, int value)
+{
+ struct fnetpeerdatum *datum;
+ struct fnetpeerdi *di;
+
+ if((datum = finddatum(peer->fn, id)) == NULL)
+ datum = adddatum(peer->fn, id, FNPD_INT);
+ di = difindoradd(peer, datum);
+ di->data.num = value;
+}
+
+void fnetpeersetlnum(struct fnetpeer *peer, wchar_t *id, long long value)
+{
+ struct fnetpeerdatum *datum;
+ struct fnetpeerdi *di;
+
+ if((datum = finddatum(peer->fn, id)) == NULL)
+ datum = adddatum(peer->fn, id, FNPD_LL);
+ di = difindoradd(peer, datum);
+ di->data.lnum = value;
+}
+
+static void putdatum(struct fnetpeer *peer, struct fnetpeerdatum *datum)
+{
+ if(--datum->refcount > 0)
+ return;
+ if(datum->next != NULL)
+ datum->next->prev = datum->prev;
+ if(datum->prev != NULL)
+ datum->prev->next = datum->next;
+ if(datum == peer->fn->peerdata)
+ peer->fn->peerdata = datum->next;
+ free(datum->id);
+ free(datum);
+}
+
+void fnetpeerunset(struct fnetpeer *peer, wchar_t *id)
+{
+ int i;
+ struct fnetpeerdatum *datum;
+
+ if((datum = finddatum(peer->fn, id)) == NULL)
+ return;
+ for(i = 0; i < peer->dinum; i++)
+ {
+ if(peer->peerdi[i].datum == datum)
+ break;
+ }
+ if(i >= peer->dinum)
+ return;
+ if((datum->datatype == FNPD_STR) && (peer->peerdi[i].data.str != NULL))
+ free(peer->peerdi[i].data.str);
+ peer->dinum--;
+ memmove(&peer->peerdi[i], &peer->peerdi[i + 1], sizeof(struct fnetpeerdi) * (peer->dinum - i));
+ putdatum(peer, datum);
+}
+
+struct fnetpeer *fnetaddpeer(struct fnetnode *fn, wchar_t *id, wchar_t *nick)
+{
+ struct fnetpeer *new;
+
+ new = smalloc(sizeof(*new));
+ new->fn = fn;
+ new->id = swcsdup(id);
+ new->nick = swcsdup(nick);
+ new->flags.w = 0;
+ new->dinum = 0;
+ new->peerdi = NULL;
+ new->next = fn->peers;
+ new->prev = NULL;
+ if(fn->peers != NULL)
+ fn->peers->prev = new;
+ fn->peers = new;
+ fn->numpeers++;
+ CBCHAINDOCB(fn, fnetnode_ac, fn, L"numpeers");
+ return(new);
+}
+
+void fnetdelpeer(struct fnetpeer *peer)
+{
+ int i;
+
+ if(peer->next != NULL)
+ peer->next->prev = peer->prev;
+ if(peer->prev != NULL)
+ peer->prev->next = peer->next;
+ if(peer->fn->peers == peer)
+ peer->fn->peers = peer->next;
+ peer->fn->numpeers--;
+ CBCHAINDOCB(peer->fn, fnetnode_ac, peer->fn, L"numpeers");
+ free(peer->id);
+ free(peer->nick);
+ for(i = 0; i < peer->dinum; i++)
+ {
+ if((peer->peerdi[i].datum->datatype == FNPD_STR) && (peer->peerdi[i].data.str != NULL))
+ free(peer->peerdi[i].data.str);
+ putdatum(peer, peer->peerdi[i].datum);
+ }
+ if(peer->peerdi != NULL)
+ free(peer->peerdi);
+ free(peer);
+}
+
+struct fnetpeer *fnetfindpeer(struct fnetnode *fn, wchar_t *id)
+{
+ struct fnetpeer *cur;
+
+ for(cur = fn->peers; (cur != NULL) && wcscmp(cur->id, id); cur = cur->next);
+ return(cur);
+}
+
+int fnetsetnick(struct fnetnode *fn, wchar_t *newnick)
+{
+ int ret;
+
+ if(fn->fnet->setnick != NULL)
+ ret = fn->fnet->setnick(fn, newnick);
+ else
+ ret = 0;
+ if(!ret)
+ {
+ if(fn->mynick != NULL)
+ free(fn->mynick);
+ fn->mynick = swcsdup(newnick);
+ }
+ return(ret);
+}
+
+int fnetsendchat(struct fnetnode *fn, int public, wchar_t *to, wchar_t *string)
+{
+ if(fn->fnet->sendchat == NULL)
+ {
+ errno = ENOTSUP;
+ return(-1);
+ }
+ return(fn->fnet->sendchat(fn, public, to, string));
+}
+
+int fnetsearch(struct fnetnode *fn, struct search *srch, struct srchfnnlist *ln)
+{
+ if(fn->fnet->search == NULL)
+ {
+ errno = ENOTSUP;
+ return(-1);
+ }
+ return(fn->fnet->search(fn, srch, ln));
+}
+
+void fnetsetname(struct fnetnode *fn, wchar_t *newname)
+{
+ if(fn->name != NULL)
+ free(fn->name);
+ fn->name = swcsdup(newname);
+ CBCHAINDOCB(fn, fnetnode_ac, fn, L"name");
+}
+
+void fnetsetstate(struct fnetnode *fn, int newstate)
+{
+ fn->state = newstate;
+ CBCHAINDOCB(fn, fnetnode_ac, fn, L"state");
+}
+
+struct fnet *findfnet(wchar_t *name)
+{
+ struct fnet *fnet;
+
+ for(fnet = networks; fnet != NULL; fnet = fnet->next)
+ {
+ if(!wcscmp(name, fnet->name))
+ break;
+ }
+ return(fnet);
+}
+
+struct fnetnode *fnetinitconnect(wchar_t *name, char *addr)
+{
+ struct fnet *fnet;
+ struct fnetnode *fn;
+
+ if((fnet = findfnet(name)) == NULL)
+ {
+ errno = EPROTONOSUPPORT;
+ return(NULL);
+ }
+ fn = newfn(fnet);
+ getfnetnode(fn);
+ if(netresolve(addr, (void (*)(struct sockaddr *, int, void *))resolvecb, fn) < 0)
+ return(NULL);
+ return(fn);
+}
+
+void regfnet(struct fnet *fnet)
+{
+ fnet->next = networks;
+ networks = fnet;
+}
+
+/*
+ * Note on the chat string: Must be in UNIX text file format - that
+ * is, LF line endings. The filenet-specific code must see to it that
+ * any other kind of format is converted into that. In the future,
+ * certain control characters and escape sequences will be parsed by
+ * the client. Make sure that any filenet-specific code strips any
+ * such that aren't supposed to be in the protocol.
+ *
+ * Note on "name": This is supposed to be an identifier for the
+ * source. If the chat is a public message, set "public" to non-zero
+ * and "name" to whatever "chat room" name is appropriate for the
+ * fnetnode, but not NULL. If there is a "default" channel in this
+ * filenet, set "name" to the empty string. If the chat is a private
+ * message, name is ignored.
+ */
+void fnethandlechat(struct fnetnode *fn, int public, wchar_t *name, wchar_t *peer, wchar_t *chat)
+{
+ CBCHAINDOCB(fn, fnetnode_chat, fn, public, name, peer, chat);
+}
+
+static struct configvar myvars[] =
+{
+ {CONF_VAR_INT, "srchwait", {.num = 15}},
+ {CONF_VAR_INT, "fntos", {.num = 0}},
+ {CONF_VAR_INT, "fnptos", {.num = 0}},
+ {CONF_VAR_END}
+};
+
+static struct module me =
+{
+ .conf =
+ {
+ .vars = myvars
+ },
+ .name = "fnet"
+};
+
+MODULE(me)
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#ifndef _FILENET_H
+#define _FILENET_H
+
+#include <wchar.h>
+#include "net.h"
+#include "utils.h"
+#include "search.h"
+
+#define FNN_SYN 0
+#define FNN_HS 1
+#define FNN_EST 2
+#define FNN_DEAD 3
+
+#define FNPD_INT 0
+#define FNPD_LL 1
+#define FNPD_STR 2
+
+struct fnetnode;
+struct fnetpeer;
+
+struct fnet
+{
+ struct fnet *next;
+ wchar_t *name;
+ void (*connect)(struct fnetnode *fn);
+ void (*destroy)(struct fnetnode *fn);
+ int (*setnick)(struct fnetnode *fn, wchar_t *newnick);
+ int (*reqconn)(struct fnetpeer *peer);
+ int (*sendchat)(struct fnetnode *fn, int public, wchar_t *to, wchar_t *string);
+ int (*search)(struct fnetnode *fn, struct search *srch, struct srchfnnlist *ln);
+ wchar_t *(*filebasename)(wchar_t *filename);
+};
+
+struct fnetpeerdatum
+{
+ struct fnetpeerdatum *next, *prev;
+ int refcount;
+ wchar_t *id;
+ int datatype;
+};
+
+struct fnetpeerdi
+{
+ struct fnetpeerdatum *datum;
+ union
+ {
+ int num;
+ long long lnum;
+ wchar_t *str;
+ } data;
+};
+
+struct fnetpeer
+{
+ struct fnetpeer *next, *prev;
+ struct fnetnode *fn;
+ wchar_t *id;
+ wchar_t *nick;
+ union
+ {
+ struct
+ {
+ int delete:1;
+ int op:1;
+ } b;
+ int w;
+ } flags;
+ int dinum;
+ struct fnetpeerdi *peerdi;
+};
+
+struct fnetnode
+{
+ struct fnetnode *next, *prev;
+ int refcount;
+ int id;
+ int state;
+ int linked;
+ time_t srchwait, lastsrch;
+ wchar_t *name;
+ wchar_t *mynick;
+ struct fnet *fnet;
+ struct socket *sk;
+ struct fnetpeerdatum *peerdata;
+ struct fnetpeer *peers;
+ CBCHAIN(fnetnode_ac, struct fnetnode *fn, wchar_t *attrib);
+ CBCHAIN(fnetnode_chat, struct fnetnode *fn, int public, wchar_t *name, wchar_t *peer, wchar_t *string);
+ CBCHAIN(fnetnode_unlink, struct fnetnode *fn);
+ CBCHAIN(fnetnode_destroy, struct fnetnode *fn);
+ int numpeers;
+ void *data;
+};
+
+void regfnet(struct fnet *fnet);
+void fnetsetname(struct fnetnode *fn, wchar_t *newname);
+void fnetsetstate(struct fnetnode *fn, int newstate);
+int fnetsetnick(struct fnetnode *fn, wchar_t *newnick);
+struct fnet *findfnet(wchar_t *name);
+struct fnetnode *fnetinitconnect(wchar_t *name, char *addr);
+void linkfnetnode(struct fnetnode *fn);
+void unlinkfnetnode(struct fnetnode *fn);
+void getfnetnode(struct fnetnode *fn);
+void putfnetnode(struct fnetnode *fn);
+void killfnetnode(struct fnetnode *fn);
+struct fnetpeer *fnetaddpeer(struct fnetnode *fn, wchar_t *id, wchar_t *nick);
+void fnetdelpeer(struct fnetpeer *peer);
+struct fnetpeer *fnetfindpeer(struct fnetnode *fn, wchar_t *id);
+void fnetpeersetstr(struct fnetpeer *peer, wchar_t *id, wchar_t *value);
+void fnetpeersetnum(struct fnetpeer *peer, wchar_t *id, int value);
+void fnetpeersetlnum(struct fnetpeer *peer, wchar_t *id, long long value);
+void fnetpeerunset(struct fnetpeer *peer, wchar_t *id);
+struct fnetnode *findfnetnode(int id);
+void fnethandlechat(struct fnetnode *fn, int public, wchar_t *name, wchar_t *peer, wchar_t *chat);
+int fnetsendchat(struct fnetnode *fn, int public, wchar_t *to, wchar_t *string);
+int fnetsearch(struct fnetnode *fn, struct search *srch, struct srchfnnlist *ln);
+
+extern struct fnetnode *fnetnodes;
+EGCBCHAIN(newfncb, struct fnetnode *);
+
+#endif
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <malloc.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <alloca.h>
+#include <wctype.h>
+#include <time.h>
+#include <errno.h>
+#include <bzlib.h>
+#include <zlib.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "filenet.h"
+#include "log.h"
+#include "module.h"
+#include "utils.h"
+#include "client.h"
+#include "transfer.h"
+#include "sysevents.h"
+#include "net.h"
+
+/*
+ * The Direct Connect protocol is extremely ugly. Thus, this code must
+ * also be a bit ugly in certain places. Please forgive me. =/
+ *
+ * This also means that there might be some pieces of the code that
+ * look completely illogical to you, and you think that you may make
+ * them much neater/better. However, in many cases it might just be
+ * that it's required to cope with some oddity of the protocol, such
+ * as the amazingly ugly delayed key response in the peer protocol.
+ */
+
+/* I assume this is the correct character set to use for DC,
+ * considering it was developed without i18n support under Windows */
+#define DCCHARSET "windows-1252"
+
+#ifdef DCPP_MASQUERADE
+/*
+ * I honestly don't want to pretend being a client that I'm not, but
+ * there are so many hubs that simply do not accept any clients
+ * outside their whitelists, for no obvious reasons, so I feel that I
+ * am left with little choice. Anyhow, as long as I actually support
+ * all the features that my faked DC++ version does, there should be
+ * very little harm done.
+ */
+#define DCIDTAG "++"
+#define DCIDTAGV "0.674"
+#define DCIDFULL "DC++ 0.674"
+#else
+#define DCIDTAG "Dolda"
+#define DCIDTAGV VERSION
+#define DCIDFULL "DoldaConnect " VERSION
+#endif
+
+#define PEER_CMD 0
+#define PEER_TRNS 1
+#define PEER_SYNC 2
+
+#define CPRS_NONE 0
+#define CPRS_ZLIB 1
+
+struct command
+{
+ char *name;
+ void (*handler)(struct socket *sk, void *data, char *cmd, char *args);
+};
+
+struct qcommand
+{
+ struct qcommand *next;
+ char *string;
+};
+
+struct dchub
+{
+ char *inbuf;
+ size_t inbufdata, inbufsize;
+ struct qcommand *queue;
+ int extended;
+ char *nativename;
+ char *nativenick;
+};
+
+struct dcexppeer
+{
+ struct dcexppeer *next, *prev;
+ char *nick;
+ struct fnetnode *fn;
+ struct timer *expire;
+};
+
+struct dcpeer
+{
+ struct dcpeer *next, *prev;
+ struct socket *sk;
+ struct fnetnode *fn;
+ char *inbuf;
+ size_t inbufdata, inbufsize;
+ int freeing;
+ struct qcommand *queue;
+ struct transfer *transfer;
+ int state;
+ int ptclose; /* Close after transfer is complete */
+ int accepted; /* If false, we connected, otherwise, we accepted */
+ int extended;
+ int direction; /* Using the constants from transfer.h */
+ int compress;
+ void *cprsdata;
+ char *mbspath;
+ char *key;
+ char *nativename;
+ char **supports;
+ wchar_t *wcsname;
+};
+
+static struct fnet dcnet;
+static struct transferiface dctransfer;
+static struct socket *udpsock = NULL;
+static struct socket *tcpsock = NULL;
+static struct dcpeer *peers = NULL;
+int numdcpeers = 0;
+static struct dcexppeer *expected = NULL;
+static char *hmlistname = NULL;
+static char *xmllistname = NULL;
+static char *xmlbz2listname = NULL;
+
+static void peerconnect(struct socket *sk, int err, struct fnetnode *fn);
+static void freedcpeer(struct dcpeer *peer);
+static void transread(struct socket *sk, struct dcpeer *peer);
+static void transerr(struct socket *sk, int err, struct dcpeer *peer);
+static void transwrite(struct socket *sk, struct dcpeer *peer);
+static void updatehmlist(void);
+static void updatexmllist(void);
+static void updatexmlbz2list(void);
+
+static int reservedchar(unsigned char c)
+{
+ return((c == 0) || (c == 5) || (c == 124) || (c == 96) || (c == 126) || (c == 36));
+}
+
+/* Oh, how I despise having to do this... */
+static char *dcmakekey(char *lock)
+{
+ int i, len, offset;
+ char *buf, *key;
+ char save;
+
+ buf = smalloc(strlen(lock));
+ save = 5;
+ len = 0;
+ for(i = 0; lock[i]; i++)
+ {
+ buf[i] = lock[i] ^ save;
+ buf[i] = ((buf[i] & 0x0F) << 4) | ((buf[i] & 0xF0) >> 4);
+ save = lock[i];
+ if((i != 0) && reservedchar(buf[i]))
+ len += 10;
+ else
+ len++;
+ }
+ buf[0] ^= buf[i - 1];
+ if(reservedchar(buf[0]))
+ len += 10;
+ else
+ len++;
+ key = smalloc(len + 1);
+ offset = 0;
+ for(i = 0; lock[i] != 0; i++)
+ {
+ if(reservedchar(buf[i]))
+ offset += sprintf(key + offset, "/%%DCN%03i%%/", buf[i]);
+ else
+ key[offset++] = buf[i];
+ }
+ key[offset] = 0;
+ free(buf);
+ return(key);
+}
+
+static void endcompress(struct dcpeer *peer)
+{
+ if(peer->compress == CPRS_ZLIB)
+ {
+ deflateEnd(peer->cprsdata);
+ free(peer->cprsdata);
+ }
+ peer->compress = CPRS_NONE;
+}
+
+static void initcompress(struct dcpeer *peer, int algo)
+{
+ int ret;
+
+ endcompress(peer);
+ peer->compress = algo;
+ if(algo == CPRS_ZLIB)
+ {
+ peer->cprsdata = smalloc(sizeof(z_stream));
+ memset(peer->cprsdata, 0, sizeof(z_stream));
+ if((ret = deflateInit(peer->cprsdata, 3)) != Z_OK)
+ {
+ flog(LOG_CRIT, "Aiya! zlib refuses to init (%i)!", ret);
+ abort();
+ }
+ }
+}
+
+static void unquote(wchar_t *in)
+{
+ wchar_t *p, *p2, nc;
+
+ for(p = in; *p != L'\0'; p++)
+ {
+ if(*p == L'&')
+ {
+ for(p2 = p + 1; (*p2 != L'\0') && (*p2 != L';') && (*p2 != L'&'); p2++);
+ if(*p2 == L'&')
+ continue;
+ if(*p2 == L'\0')
+ return;
+ *p2 = L'\0';
+ nc = L'\0';
+ if(!wcscmp(p + 1, L"amp"))
+ {
+ nc = L'&';
+ } else if(p[1] == L'#') {
+ nc = ucptowc(wcstol(p + 2, NULL, 10));
+ }
+ if(nc == L'\0')
+ {
+ *p2 = L';';
+ p = p2;
+ continue;
+ }
+ *p = nc;
+ memmove(p + 1, p2 + 1, (wcslen(p2 + 1) + 1) * sizeof(wchar_t));
+ }
+ }
+}
+
+static void freeexppeer(struct dcexppeer *ep)
+{
+ if(ep->next != NULL)
+ ep->next->prev = ep->prev;
+ if(ep->prev != NULL)
+ ep->prev->next = ep->next;
+ if(ep == expected)
+ expected = ep->next;
+ free(ep->nick);
+ putfnetnode(ep->fn);
+ if(ep->expire != NULL)
+ canceltimer(ep->expire);
+ free(ep);
+}
+
+static void exppeerexpire(int cancelled, struct dcexppeer *ep)
+{
+ ep->expire = NULL;
+ if(!cancelled)
+ freeexppeer(ep);
+}
+
+static struct dcexppeer *expectpeer(char *nick, struct fnetnode *fn)
+{
+ struct dcexppeer *ep;
+
+ ep = smalloc(sizeof(*ep));
+ ep->nick = sstrdup(nick);
+ getfnetnode(ep->fn = fn);
+ ep->expire = timercallback(ntime() + 300, (void (*)(int, void *))exppeerexpire, ep);
+ ep->next = expected;
+ ep->prev = NULL;
+ if(expected != NULL)
+ expected->prev = ep;
+ expected = ep;
+ return(ep);
+}
+
+static struct qcommand *newqcmd(struct qcommand **queue, char *string)
+{
+ struct qcommand *new;
+
+ while(*queue != NULL)
+ queue = &(*queue)->next;
+ new = smalloc(sizeof(*new));
+ new->string = sstrdup(string);
+ new->next = *queue;
+ *queue = new;
+ return(new);
+}
+
+static struct qcommand *ulqcmd(struct qcommand **queue)
+{
+ struct qcommand *qcmd;
+
+ if((qcmd = *queue) == NULL)
+ return(NULL);
+ *queue = qcmd->next;
+ return(qcmd);
+}
+
+static void freeqcmd(struct qcommand *qcmd)
+{
+ free(qcmd->string);
+ free(qcmd);
+}
+
+static void hubrecvchat(struct socket *sk, struct fnetnode *fn, char *from, char *string)
+{
+ wchar_t *chat, *wfrom, *wpeer;
+ char *p, *end;
+ struct fnetpeer *peer;
+
+ end = string + strlen(string);
+ while((p = strchr(string, 13)) != NULL)
+ memmove(p, p + 1, (end-- - p));
+ if(from != NULL)
+ {
+ if((strlen(string) > strlen(from) + 2) && (*string == '<') && !memcmp(string + 1, from, strlen(from)) && (*(string + strlen(from) + 1) == '>'))
+ string += strlen(from) + 2;
+ if((wfrom = icmbstowcs(from, DCCHARSET)) == NULL)
+ return;
+ wpeer = swcsdup(wfrom);
+ } else {
+ wfrom = NULL;
+ wpeer = NULL;
+ if(*string == '<')
+ {
+ for(p = string + 1; *p; p++)
+ {
+ if((*p == ' ') || (*p == '>'))
+ break;
+ }
+ if(*p == '>')
+ {
+ *(p++) = 0;
+ if(*p == ' ')
+ p++;
+ if((wpeer = icmbstowcs(string + 1, DCCHARSET)) == NULL)
+ return;
+ string = p;
+ }
+ }
+ if(wpeer == NULL)
+ wpeer = swcsdup(L"");
+ }
+ if((chat = icmbstowcs(string, DCCHARSET)) == NULL)
+ {
+ if(wfrom != NULL)
+ free(wfrom);
+ free(wpeer);
+ return;
+ }
+ unquote(chat);
+ if(wfrom != NULL)
+ {
+ if((peer = fnetfindpeer(fn, wfrom)) == NULL) /* Assume public chat */
+ fnethandlechat(fn, 1, wfrom, wpeer, chat);
+ else
+ fnethandlechat(fn, 0, wfrom, wpeer, chat);
+ } else {
+ fnethandlechat(fn, 1, L"", wpeer, chat);
+ }
+ if(wfrom != NULL)
+ free(wfrom);
+ free(wpeer);
+ free(chat);
+}
+
+static void sendadc(struct socket *sk, char *arg)
+{
+ char *buf;
+ size_t bufsize, bufdata;
+
+ buf = NULL;
+ bufsize = bufdata = 0;
+ addtobuf(buf, ' ');
+ for(; *arg; arg++)
+ {
+ if(*arg == ' ')
+ {
+ bufcat(buf, "\\s", 2);
+ } else if(*arg == '\n') {
+ bufcat(buf, "\\n", 2);
+ } else if(*arg == '\\') {
+ bufcat(buf, "\\\\", 2);
+ } else {
+ addtobuf(buf, *arg);
+ }
+ }
+ sockqueue(sk, buf, bufdata);
+ free(buf);
+}
+
+static void sendadcf(struct socket *sk, char *arg, ...)
+{
+ char *buf;
+ va_list args;
+
+ va_start(args, arg);
+ buf = vsprintf2(arg, args);
+ va_end(args);
+ if(buf == NULL)
+ return;
+ sendadc(sk, buf);
+ free(buf);
+}
+
+static char **parseadc(char *args)
+{
+ char **retbuf;
+ size_t retbufsize, retbufdata;
+ char *buf;
+ size_t bufsize, bufdata;
+ int state;
+
+ retbuf = NULL;
+ buf = NULL;
+ retbufsize = retbufdata = bufsize = bufdata = 0;
+ state = 0;
+ while(state != 3)
+ {
+ switch(state)
+ {
+ case 0:
+ if(*args == 0)
+ state = 3;
+ else if(*args != ' ')
+ state = 1;
+ break;
+ case 1:
+ if((*args == ' ') || (*args == 0))
+ {
+ addtobuf(buf, 0);
+ addtobuf(retbuf, buf);
+ buf = NULL;
+ bufsize = bufdata = 0;
+ if(*args == 0)
+ state = 3;
+ } else if(*args == '\\') {
+ state = 2;
+ } else {
+ addtobuf(buf, *args);
+ }
+ args++;
+ break;
+ case 2:
+ if(*args == 0)
+ {
+ if(buf != NULL)
+ free(buf);
+ addtobuf(retbuf, NULL);
+ freeparr(retbuf);
+ return(NULL);
+ } else if(*args == 's') {
+ addtobuf(buf, ' ');
+ } else if(*args == 'n') {
+ addtobuf(buf, '\n');
+ } else if(*args == '\\') {
+ addtobuf(buf, '\\');
+ }
+ state = 1;
+ }
+ }
+ if(buf != NULL)
+ free(buf);
+ addtobuf(retbuf, NULL);
+ return(retbuf);
+}
+
+/* Macros useful in command handlers */
+#define skipspace(s) ({if(((s) = strchr((s), ' ')) == NULL) return; else (s)++;})
+#define qstr(sk, str) sockqueue(sk, str, strlen(str))
+#define qstrf(sk, strandargs...) \
+do { \
+ char *__buf__; \
+ if((__buf__ = sprintf2(strandargs)) != NULL) { \
+ sockqueue(sk, __buf__, strlen(__buf__)); \
+ free(__buf__); \
+ } \
+} while(0)
+
+static char *tr(char *str, char *trans)
+{
+ char *p;
+
+ for(; *trans; trans += 2)
+ {
+ for(p = strchr(str, trans[0]); p != NULL; p = strchr(p, trans[0]))
+ *p = trans[1];
+ }
+ return(str);
+}
+
+static int trresumecb(struct transfer *transfer, wchar_t *cmd, wchar_t *arg, struct dcpeer *peer)
+{
+ if(!wcscmp(cmd, L"resume"))
+ {
+ if(arg == NULL)
+ {
+ flog(LOG_WARNING, "filter returned no position for \"resume\" on transfer %i", transfer->id);
+ freedcpeer(peer);
+ } else {
+ transfer->curpos = wcstol(arg, NULL, 10);
+ qstrf(peer->sk, "$Get %s$%i|", peer->mbspath, transfer->curpos + 1);
+ }
+ free(peer->mbspath);
+ peer->mbspath = NULL;
+ return(1);
+ }
+ return(0);
+}
+
+static void peerhandleaction(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
+{
+ struct dchub *hub;
+ struct dcexppeer *expect;
+ struct transfer *transfer;
+ wchar_t tbuf[128];
+ char *mbsbuf;
+
+ hub = NULL;
+ if(peer->fn != NULL)
+ {
+ if(peer->fn->fnet != &dcnet)
+ {
+ peer->sk->close = 1;
+ return;
+ }
+ hub = peer->fn->data;
+ }
+ if(peer->transfer != NULL)
+ {
+ swprintf(tbuf, 128, L"hs: dc-%s", cmd);
+ transfersetactivity(peer->transfer, tbuf);
+ }
+ if(peer->accepted)
+ {
+ if(cmd == NULL) /* Connect event */
+ {
+ } else if(!strcmp(cmd, "$MyNick")) {
+ for(expect = expected; expect != NULL; expect = expect->next)
+ {
+ if(!strcmp(expect->nick, args))
+ break;
+ }
+ if(expect == NULL)
+ {
+ peer->fn = NULL;
+ } else {
+ peer->fn = expect->fn;
+ getfnetnode(peer->fn);
+ freeexppeer(expect);
+ }
+ } else if(!strcmp(cmd, "$Lock")) {
+ if(peer->wcsname == NULL)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ if(hub == NULL)
+ qstrf(sk, "$MyNick %s|", icswcstombs(confgetstr("cli", "defnick"), DCCHARSET, "DoldaConnectUser-IN"));
+ else
+ qstrf(sk, "$MyNick %s|", hub->nativenick);
+#ifdef DCPP_MASQUERADE
+ qstrf(sk, "$Lock EXTENDEDPROTOCOLABCABCABCABCABCABC Pk=DCPLUSPLUS0.674ABCABC|");
+#else
+ qstrf(sk, "$Lock EXTENDEDPROTOCOLABCABCABCABCABCABC Pk=DOLDA%sABCABCABC|", VERSION);
+#endif
+ if(peer->extended)
+ {
+#ifdef DCPP_MASQUERADE
+ qstr(sk, "$Supports MiniSlots XmlBZList ADCGet TTHL TTHF GetZBlock ZLIG |");
+#else
+ qstr(sk, "$Supports MiniSlots XmlBZList ADCGet TTHL TTHF GetZBlock ZLIG|");
+#endif
+ }
+ for(transfer = transfers; transfer != NULL; transfer = transfer->next)
+ {
+ if((transfer->dir == TRNSD_DOWN) && (transfer->iface == NULL) && !wcscmp(peer->wcsname, transfer->peerid))
+ break;
+ }
+ if(transfer == NULL)
+ {
+ peer->direction = TRNSD_UP;
+ transfer = newupload(peer->fn, &dcnet, peer->wcsname, &dctransfer, peer);
+ transfersetnick(transfer, peer->wcsname);
+ peer->transfer = transfer;
+ } else {
+ peer->direction = TRNSD_DOWN;
+ peer->transfer = transfer;
+ transferattach(transfer, &dctransfer, peer);
+ transfersetnick(transfer, peer->wcsname);
+ transfersetstate(transfer, TRNS_HS);
+ }
+ qstrf(sk, "$Direction %s %i|", (peer->direction == TRNSD_UP)?"Upload":"Download", rand() % 10000);
+ if(peer->key != NULL)
+ {
+ /* I hate the DC protocol so much... */
+ qstrf(sk, "$Key %s|", peer->key);
+ free(peer->key);
+ peer->key = NULL;
+ }
+ if(peer->direction == TRNSD_DOWN)
+ {
+ if((mbsbuf = icwcstombs(peer->transfer->path, DCCHARSET)) == NULL)
+ {
+ /* I believe that NOTFOUND should be used
+ * since giving a path that cannot be
+ * represented in the protocol's charset is
+ * literally the same as giving a path that
+ * the client doesn't have. */
+ transferseterror(peer->transfer, TRNSE_NOTFOUND);
+ freedcpeer(peer);
+ return;
+ }
+ if(peer->transfer->size == -1)
+ {
+ /* The transfer will be restarted later from
+ * cmd_filelength when it detects that the sizes
+ * don't match. */
+ qstrf(sk, "$Get %s$1|", mbsbuf);
+ } else {
+ if(forkfilter(transfer))
+ {
+ flog(LOG_WARNING, "could not fork filter for transfer %i: %s", transfer->id, strerror(errno));
+ freedcpeer(peer);
+ free(mbsbuf);
+ return;
+ }
+ peer->mbspath = sstrdup(mbsbuf);
+ CBREG(transfer, trans_filterout, (int (*)(struct transfer *, wchar_t *, wchar_t *, void *))trresumecb, NULL, peer);
+ }
+ free(mbsbuf);
+ }
+ } else if(!strcmp(cmd, "$FileLength")) {
+ if(peer->transfer == NULL)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ transfersetstate(peer->transfer, TRNS_MAIN);
+ socksettos(peer->sk, confgetint("transfer", "dltos"));
+ peer->state = PEER_TRNS;
+ peer->sk->readcb = (void (*)(struct socket *, void *))transread;
+ peer->sk->errcb = (void (*)(struct socket *, int, void *))transerr;
+ qstr(peer->sk, "$Send|");
+ }
+ } else {
+ if(cmd == NULL) /* Connect event */
+ {
+ if(hub == NULL)
+ qstrf(sk, "$MyNick %s|", icswcstombs(confgetstr("cli", "defnick"), DCCHARSET, "DoldaConnectUser-IN"));
+ else
+ qstrf(sk, "$MyNick %s|", hub->nativenick);
+#ifdef DCPP_MASQUERADE
+ qstrf(sk, "$Lock EXTENDEDPROTOCOLABCABCABCABCABCABC Pk=DCPLUSPLUS0.674ABCABC|");
+#else
+ qstrf(sk, "$Lock EXTENDEDPROTOCOLABCABCABCABCABCABC Pk=DOLDA%sABCABCABC|", VERSION);
+#endif
+ } else if(!strcmp(cmd, "$Direction")) {
+ if(peer->wcsname == NULL)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ if(peer->direction == TRNSD_UP)
+ {
+ transfer = newupload(peer->fn, &dcnet, peer->wcsname, &dctransfer, peer);
+ transfersetnick(transfer, peer->wcsname);
+ peer->transfer = transfer;
+ } else {
+ for(transfer = transfers; transfer != NULL; transfer = transfer->next)
+ {
+ if((transfer->dir == TRNSD_DOWN) && (transfer->state == TRNS_WAITING) && !wcscmp(peer->wcsname, transfer->peerid))
+ break;
+ }
+ if(transfer == NULL)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ peer->transfer = transfer;
+ transferattach(transfer, &dctransfer, peer);
+ transfersetnick(transfer, peer->wcsname);
+ transfersetstate(transfer, TRNS_HS);
+ }
+ if(peer->extended)
+ {
+#ifdef DCPP_MASQUERADE
+ qstr(sk, "$Supports MiniSlots XmlBZList ADCGet TTHL TTHF GetZBlock ZLIG |");
+#else
+ qstr(sk, "$Supports MiniSlots XmlBZList ADCGet TTHL TTHF GetZBlock ZLIG|");
+#endif
+ }
+ qstrf(sk, "$Direction %s %i|", (peer->direction == TRNSD_UP)?"Upload":"Download", rand() % 10000);
+ if(peer->key != NULL)
+ qstrf(sk, "$Key %s|", peer->key);
+ if(peer->direction == TRNSD_DOWN)
+ {
+ if((mbsbuf = icwcstombs(peer->transfer->path, DCCHARSET)) == NULL)
+ {
+ /* I believe that NOTFOUND should be used
+ * since giving a path that cannot be
+ * represented in the protocol's charset is
+ * literally the same as giving a path that
+ * the client doesn't have. */
+ transferseterror(peer->transfer, TRNSE_NOTFOUND);
+ freedcpeer(peer);
+ return;
+ }
+ if(peer->transfer->size == -1)
+ {
+ /* The transfer will be restarted later from
+ * cmd_filelength when it detects that the sizes
+ * don't match. */
+ qstrf(sk, "$Get %s$1|", mbsbuf);
+ } else {
+ if(forkfilter(transfer))
+ {
+ flog(LOG_WARNING, "could not fork filter for transfer %i: %s", transfer->id, strerror(errno));
+ freedcpeer(peer);
+ free(mbsbuf);
+ return;
+ }
+ peer->mbspath = sstrdup(mbsbuf);
+ CBREG(transfer, trans_filterout, (int (*)(struct transfer *, wchar_t *, wchar_t *, void *))trresumecb, NULL, peer);
+ }
+ free(mbsbuf);
+ }
+ } else if(!strcmp(cmd, "$FileLength")) {
+ if(peer->transfer == NULL)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ transfersetstate(peer->transfer, TRNS_MAIN);
+ socksettos(peer->sk, confgetint("transfer", "dltos"));
+ peer->state = PEER_TRNS;
+ peer->sk->readcb = (void (*)(struct socket *, void *))transread;
+ peer->sk->errcb = (void (*)(struct socket *, int, void *))transerr;
+ qstr(peer->sk, "$Send|");
+ }
+ }
+}
+
+static void sendmyinfo(struct socket *sk, struct fnetnode *fn)
+{
+ struct dchub *hub;
+ char *buf;
+ struct fnetnode *cfn;
+ int numhubs;
+
+ hub = fn->data;
+ qstrf(sk, "$MyINFO $ALL %s ", hub->nativenick);
+ buf = tr(icswcstombs(confgetstr("dc", "desc"), DCCHARSET, "Charset_conv_failure"), "$_|_");
+ qstrf(sk, "%s", buf);
+ numhubs = 0;
+ for(cfn = fnetnodes; cfn != NULL; cfn = cfn->next)
+ {
+ if((cfn->state == FNN_EST) || (cfn->state == FNN_HS))
+ numhubs++;
+ }
+ qstrf(sk, "<%s V:%s,M:%c,H:%i/0/0,S:%i>",
+ DCIDTAG,
+ DCIDTAGV,
+ (tcpsock == NULL)?'P':'A',
+ numhubs,
+ confgetint("transfer", "slots")
+ );
+ qstrf(sk, "$ $");
+ buf = tr(icswcstombs(confgetstr("dc", "speedstring"), DCCHARSET, "Charset_conv_failure"), "$_|_");
+ qstrf(sk, "%s\x01$", buf);
+ buf = tr(icswcstombs(confgetstr("dc", "email"), DCCHARSET, "Charset_conv_failure"), "$_|_");
+ qstrf(sk, "%s$", buf);
+ qstrf(sk, "%llu$|", sharesize);
+}
+
+static void hubhandleaction(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+ struct dchub *hub;
+
+ hub = fn->data;
+ if(!strcmp(cmd, "$Lock"))
+ {
+ qstrf(sk, "$ValidateNick %s|", hub->nativenick);
+ } else if(!strcmp(cmd, "$Hello")) {
+ if(fn->state == FNN_HS)
+ {
+ qstrf(sk, "$Version 1,0091|");
+ qstrf(sk, "$GetNickList|");
+ sendmyinfo(sk, fn);
+ fnetsetstate(fn, FNN_EST);
+ } else {
+ qstrf(sk, "$GetINFO %s %s|", args, hub->nativenick);
+ }
+ }
+}
+
+static void cmd_lock(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+ struct dchub *hub;
+ char *key;
+ char *p;
+
+ hub = fn->data;
+ if(!strncmp(args, "EXTENDEDPROTOCOL", 16))
+ hub->extended = 1;
+ if((p = strchr(args, ' ')) != NULL)
+ *(p++) = 0;
+ if(hub->extended)
+ {
+#ifdef DCPP_MASQUERADE
+ qstrf(sk, "$Supports UserCommand NoGetINFO NoHello UserIP2 TTHSearch GetZBlock |");
+#else
+ qstrf(sk, "$Supports UserCommand NoGetINFO NoHello UserIP2 TTHSearch GetZBlock|");
+#endif
+ }
+ key = dcmakekey(args);
+ qstrf(sk, "$Key %s|", key);
+ free(key);
+ hubhandleaction(sk, fn, cmd, args);
+}
+
+static void cmd_hubname(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+ wchar_t *buf;
+ struct dchub *hub;
+
+ hub = fn->data;
+ if(hub->nativename == NULL)
+ free(hub->nativename);
+ hub->nativename = sstrdup(args);
+ buf = icmbstowcs(args, DCCHARSET);
+ fnetsetname(fn, (buf == NULL)?L"Hubname conv error":buf);
+ if(buf != NULL)
+ free(buf);
+ hubhandleaction(sk, fn, cmd, args);
+}
+
+static void cmd_hello(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+ wchar_t *nick;
+ struct dchub *hub;
+
+ hub = fn->data;
+ if((nick = icmbstowcs(args, DCCHARSET)) == NULL)
+ return;
+ if(strcmp(args, hub->nativenick) && (fnetfindpeer(fn, nick) == NULL))
+ fnetaddpeer(fn, nick, nick);
+ free(nick);
+ hubhandleaction(sk, fn, cmd, args);
+}
+
+static void cmd_quit(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+ wchar_t *nick;
+ struct fnetpeer *peer;
+ struct dchub *hub;
+
+ hub = fn->data;
+ if((nick = icmbstowcs(args, DCCHARSET)) == NULL)
+ return;
+ if((peer = fnetfindpeer(fn, nick)) != NULL)
+ fnetdelpeer(peer);
+ free(nick);
+ hubhandleaction(sk, fn, cmd, args);
+}
+
+static void cmd_nicklist(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+ struct dchub *hub;
+ char *p;
+ wchar_t *buf;
+ struct fnetpeer *peer, *npeer;
+
+ hub = fn->data;
+ for(peer = fn->peers; peer != NULL; peer = peer->next)
+ peer->flags.b.delete = 1;
+ while((p = strstr(args, "$$")) != NULL)
+ {
+ *p = 0;
+ if((buf = icmbstowcs(args, DCCHARSET)) != NULL)
+ {
+ if((peer = fnetfindpeer(fn, buf)) == NULL)
+ peer = fnetaddpeer(fn, buf, buf);
+ else
+ peer->flags.b.delete = 0;
+ free(buf);
+ qstrf(sk, "$GetINFO %s %s|", args, hub->nativenick);
+ }
+ args = p + 2;
+ }
+ for(peer = fn->peers; peer != NULL; peer = npeer)
+ {
+ npeer = peer->next;
+ if(peer->flags.b.delete)
+ fnetdelpeer(peer);
+ }
+ hubhandleaction(sk, fn, cmd, args);
+}
+
+static void cmd_oplist(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+ struct dchub *hub;
+ char *p;
+ wchar_t *buf;
+ struct fnetpeer *peer;
+
+ hub = fn->data;
+ for(peer = fn->peers; peer != NULL; peer = peer->next)
+ peer->flags.b.op = 0;
+ while((p = strstr(args, "$$")) != NULL)
+ {
+ *p = 0;
+ if((buf = icmbstowcs(args, DCCHARSET)) != NULL)
+ {
+ if((peer = fnetfindpeer(fn, buf)) != NULL)
+ peer->flags.b.op = 1;
+ free(buf);
+ }
+ args = p + 2;
+ }
+ hubhandleaction(sk, fn, cmd, args);
+}
+
+static void cmd_myinfo(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+ char *p, *p2;
+ wchar_t *buf, *wp, *wp2;
+ wchar_t abuf[10];
+ struct fnetpeer *peer;
+ struct dchub *hub;
+
+ hub = fn->data;
+ p = args;
+ if(strncmp(p, "$ALL ", 5))
+ return;
+ p += 5;
+ if((p2 = strchr(p, ' ')) == NULL)
+ return;
+ *p2 = 0;
+ if((buf = icmbstowcs(p, DCCHARSET)) == NULL)
+ return;
+ if((peer = fnetfindpeer(fn, buf)) == NULL)
+ peer = fnetaddpeer(fn, buf, buf);
+ free(buf);
+ p = p2 + 1;
+ if((p2 = strstr(p, "$ $")) == NULL)
+ return;
+ *p2 = 0;
+ if((buf = icmbstowcs(p, DCCHARSET)) == NULL)
+ return;
+ if((wcslen(buf) > 0) && (buf[wcslen(buf) - 1] == L'>') && ((wp = wcschr(buf, L'<')) != NULL))
+ {
+ buf[wcslen(buf) - 1] = L'\0';
+ *(wp++) = L'\0';
+ if((wp2 = wcschr(wp, L' ')) != NULL)
+ {
+ *(wp2++) = L'\0';
+ fnetpeersetstr(peer, L"dc-client", wp);
+ wp = wp2;
+ do
+ {
+ if((wp2 = wcschr(wp, L',')) != NULL)
+ *(wp2++) = L'\0';
+ if(wp[1] != L':')
+ continue;
+ swprintf(abuf, 10, L"dc-tag-%lc", wp[0]);
+ fnetpeersetstr(peer, abuf, wp + 2);
+ wp = wp2;
+ } while(wp2 != NULL);
+ }
+ }
+ fnetpeersetstr(peer, L"descr", buf);
+ free(buf);
+ p = p2 + 3;
+ if((p2 = strchr(p, '$')) == NULL)
+ return;
+ *(p2 - 1) = 0;
+ if((buf = icmbstowcs(p, DCCHARSET)) == NULL)
+ return;
+ fnetpeersetstr(peer, L"dc-speed", buf);
+ free(buf);
+ p = p2 + 1;
+ if((p2 = strchr(p, '$')) == NULL)
+ return;
+ *p2 = 0;
+ if((buf = icmbstowcs(p, DCCHARSET)) == NULL)
+ return;
+ fnetpeersetstr(peer, L"email", buf);
+ free(buf);
+ p = p2 + 1;
+ if(strlen(p) < 1)
+ return;
+ fnetpeersetlnum(peer, L"share", strtoll(p, NULL, 10));
+ hubhandleaction(sk, fn, cmd, args);
+}
+
+/* I do not implement the fully in that I do not disconnect from the
+ * old hub. I do this since I believe that if the hub owner really
+ * wants to get rid of us, then it is his responsibility to disconnect
+ * us. */
+static void cmd_forcemove(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+ struct dchub *hub;
+ struct fnetnode *newfn;
+ int freeargs;
+
+ hub = fn->data;
+ if(strchr(args, ':') == NULL)
+ {
+ args = strcpy(smalloc(strlen(args) + 5), args);
+ strcat(args, ":411");
+ freeargs = 1;
+ } else {
+ freeargs = 0;
+ }
+ if((newfn = fnetinitconnect(L"dc", args)) != NULL)
+ {
+ linkfnetnode(newfn);
+ putfnetnode(newfn);
+ }
+ hubhandleaction(sk, fn, cmd, args);
+ if(freeargs)
+ free(args);
+}
+
+static char *getdcpath(struct sharecache *node, size_t *retlen)
+{
+ char *buf, *buf2;
+ size_t len, len2;
+
+ if(node->parent == NULL)
+ return(NULL);
+ if(node->parent == shareroot)
+ {
+ if((buf = icwcstombs(node->name, DCCHARSET)) == NULL)
+ return(NULL);
+ if(retlen != NULL)
+ *retlen = strlen(buf);
+ return(buf);
+ } else {
+ if((buf2 = icwcstombs(node->name, DCCHARSET)) == NULL)
+ return(NULL);
+ if((buf = getdcpath(node->parent, &len)) == NULL)
+ {
+ free(buf2);
+ return(NULL);
+ }
+ len2 = strlen(buf2);
+ buf = srealloc(buf, len + 1 + len2 + 1);
+ buf[len++] = '\\';
+ strcpy(buf + len, buf2);
+ buf[len + len2] = 0;
+ free(buf2);
+ if(retlen != NULL)
+ *retlen = len + len2;
+ return(buf);
+ }
+}
+
+/*
+ * This is the main share searching function for Direct Connect
+ * peers. Feel free to optimize it if you feel the need for it. I
+ * haven't ever seen it take any noticable CPU time anyway, so I
+ * haven't felt that need.
+ *
+ * It may feel dubious to just return the directory when all terms are
+ * satisfied rather than all the files in it, but it really benefits
+ * everyone: Less cycles used for us, less UDP squelching for active
+ * searchers, and less bandwidth waste for hubs when serving passive
+ * searchers.
+ */
+static void cmd_search(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+ int i, done;
+ struct dchub *hub;
+ char *p, *p2;
+ char *prefix, *infix, *postfix, *buf, *buf2;
+ struct socket *dsk;
+ struct sockaddr_in addr;
+ struct sharecache *node;
+ int minsize, maxsize;
+ int dotth, buflen;
+ int termnum, satisfied, skipcheck;
+ int level, tersat[32];
+ wchar_t *terms[32];
+ char hashtth[24];
+
+ hub = fn->data;
+ if((p = strchr(args, ' ')) == NULL)
+ return;
+ *(p++) = 0;
+
+ memset(terms, 0, sizeof(terms));
+ prefix = infix = postfix = NULL;
+ dsk = NULL;
+ dotth = 0;
+
+ if(!strncmp(args, "Hub:", 4))
+ {
+ if(!strcmp(cmd, "$MultiSearch"))
+ goto out;
+ if(!strcmp(args + 4, hub->nativenick))
+ goto out;
+ prefix = sprintf2("$SR %s ", hub->nativenick);
+ infix = sprintf2(" %i/%i\005", slotsleft(), confgetint("transfer", "slots"));
+ postfix = sprintf2(" (%s)\005%s|", formataddress(fn->sk->remote, fn->sk->remotelen), args + 4);
+ dsk = sk;
+ getsock(dsk);
+ } else {
+ if((p2 = strchr(args, ':')) == NULL)
+ goto out;
+ *(p2++) = 0;
+ addr.sin_family = AF_INET;
+ if(!inet_aton(args, &addr.sin_addr))
+ goto out;
+ addr.sin_port = htons(atoi(p2));
+ prefix = sprintf2("$SR %s ", hub->nativenick);
+ infix = sprintf2(" %i/%i\005", slotsleft(), confgetint("transfer", "slots"));
+ postfix = sprintf2(" (%s)|", formataddress(fn->sk->remote, fn->sk->remotelen));
+ netdgramconn(dsk = netdupsock(udpsock), (struct sockaddr *)&addr, sizeof(addr));
+ }
+
+ minsize = maxsize = -1;
+ if(*p == 0)
+ goto out;
+ if(*(p++) == 'T')
+ minsize = 0;
+ if(*(p++) != '?')
+ goto out;
+ if(*p == 0)
+ goto out;
+ if(*(p++) == 'T')
+ {
+ maxsize = 0;
+ minsize = -1;
+ }
+ if(*(p++) != '?')
+ goto out;
+ if((p2 = strchr(p, '?')) == NULL)
+ goto out;
+ *(p2++) = 0;
+ if(minsize == 0)
+ minsize = atoi(p);
+ if(maxsize == 0)
+ maxsize = atoi(p);
+ p = p2 + 1;
+ if(*(p++) != '?')
+ goto out;
+ termnum = 0;
+ p2 = p;
+ done = 0;
+ while(!done)
+ {
+ if((*p2 == 0) || (*p2 == '$'))
+ {
+ if(*p2 == 0)
+ done = 1;
+ else
+ *p2 = 0;
+ if(*p)
+ {
+ if(!dotth && !strncmp(p, "TTH:", 4))
+ {
+ dotth = 1;
+ if((buf = base32decode(p + 4, &buflen)) == NULL)
+ goto out;
+ if(buflen != 24)
+ goto out;
+ memcpy(hashtth, buf, 24);
+ free(buf);
+ } else {
+ if((terms[termnum] = icmbstowcs(p, DCCHARSET)) != NULL)
+ termnum++;
+ }
+ }
+ p = p2 + 1;
+ if(termnum == 32)
+ break;
+ }
+ p2++;
+ }
+
+ node = shareroot->child;
+ level = 0;
+ for(i = 0; i < termnum; i++)
+ tersat[i] = -1;
+ satisfied = 0;
+ while(1)
+ {
+ skipcheck = 0;
+ if(node->f.b.type == FILE_REG)
+ {
+ if((minsize >= 0) && (node->size < minsize))
+ skipcheck = 1;
+ if((maxsize >= 0) && (node->size > maxsize))
+ skipcheck = 1;
+ }
+ if(!skipcheck && dotth)
+ {
+ if((node->f.b.type != FILE_REG) || (node->f.b.hastth && memcmp(hashtth, node->hashtth, 24)))
+ skipcheck = 1;
+ }
+ if(!skipcheck)
+ {
+ for(i = 0; i < termnum; i++)
+ {
+ if(tersat[i] >= 0)
+ continue;
+ if(wcsexists(node->name, terms[i]))
+ {
+ tersat[i] = level;
+ satisfied++;
+ } else if(node->child == NULL) {
+ break;
+ }
+ }
+ }
+ if(!skipcheck && (satisfied == termnum))
+ {
+ if((buf = getdcpath(node, NULL)) != NULL)
+ {
+ if(node->f.b.hastth)
+ {
+ buf2 = base32encode(node->hashtth, 24);
+ qstrf(dsk, "%s%s\005%i%sTTH:%.39s%s", prefix, buf, node->size, infix, buf2, postfix);
+ free(buf2);
+ } else {
+ qstrf(dsk, "%s%s\005%i%s%s%s", prefix, buf, node->size, infix, hub->nativename, postfix);
+ }
+ free(buf);
+ }
+ }
+ if((!skipcheck && (satisfied == termnum)) || (node->child == NULL))
+ {
+ while(node->next == NULL)
+ {
+ if((node = node->parent) == shareroot)
+ break;
+ level--;
+ }
+ if(node == shareroot)
+ break;
+ for(i = 0; i < termnum; i++)
+ {
+ if(tersat[i] >= level)
+ {
+ tersat[i] = -1;
+ satisfied--;
+ }
+ }
+ node = node->next;
+ } else {
+ node = node->child;
+ level++;
+ }
+ }
+
+ hubhandleaction(sk, fn, cmd, args);
+
+ out:
+ if(dsk != NULL)
+ putsock(dsk);
+ if(prefix != NULL)
+ free(prefix);
+ if(infix != NULL)
+ free(infix);
+ if(postfix != NULL)
+ free(postfix);
+ for(i = 0; (i < 32) && (terms[i] != NULL); i++)
+ free(terms[i]);
+}
+
+static void cmd_connecttome(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+ char *p;
+ struct dchub *hub;
+ struct socket *newsk;
+ struct sockaddr_in addr;
+
+ hub = fn->data;
+ if((p = strchr(args, ' ')) == NULL)
+ return;
+ *(p++) = 0;
+ if(strcmp(args, hub->nativenick))
+ return;
+ addr.sin_family = AF_INET;
+ args = p;
+ if((p = strchr(args, ':')) == NULL)
+ return;
+ *(p++) = 0;
+ addr.sin_port = htons(atoi(p));
+ if(!inet_aton(args, &addr.sin_addr))
+ return;
+ newsk = netcsconn((struct sockaddr *)&addr, sizeof(addr), (void (*)(struct socket *, int, void *))peerconnect, fn);
+ getfnetnode(fn);
+ hubhandleaction(sk, fn, cmd, args);
+}
+
+static void sendctm(struct socket *sk, char *nick)
+{
+ struct sockaddr *addr;
+ socklen_t addrlen;
+
+ if(tcpsock == NULL)
+ return;
+ if(sockgetremotename(tcpsock, &addr, &addrlen) < 0)
+ return;
+ if(addr->sa_family == AF_INET)
+ qstrf(sk, "$ConnectToMe %s %s|", nick, formataddress(addr, addrlen));
+ else
+ flog(LOG_WARNING, "Direct Connect TCP socket is suddenly not AF_INET, but %i", addr->sa_family);
+ free(addr);
+}
+
+static void cmd_revconnecttome(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+ struct dchub *hub;
+ char *p;
+
+ hub = fn->data;
+ if((p = strchr(args, ' ')) == NULL)
+ return;
+ *(p++) = 0;
+ if(strcmp(p, hub->nativenick))
+ return;
+ sendctm(sk, args);
+ expectpeer(args, fn);
+ hubhandleaction(sk, fn, cmd, args);
+}
+
+static void cmd_getnetinfo(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+ struct dchub *hub;
+ struct fnetnode *node;
+ int numhubs;
+
+ hub = fn->data;
+ numhubs = 0;
+ for(node = fnetnodes; node != NULL; node = node->next)
+ {
+ if(node->state == FNN_EST)
+ numhubs++;
+ }
+ qstrf(sk, "$NetInfo %i$%i$%c$0|", confgetint("transfer", "slots"), numhubs, (tcpsock == NULL)?'P':'A');
+ hubhandleaction(sk, fn, cmd, args);
+}
+
+static void cmd_to(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+ struct dchub *hub;
+ char *p, *p2;
+
+ hub = fn->data;
+ p = args;
+ if((p2 = strchr(p, ' ')) == NULL)
+ return;
+ *(p2++) = 0;
+ p = p2;
+ if((p2 = strchr(p, ' ')) == NULL)
+ return;
+ *(p2++) = 0;
+ if(strcmp(p, "From:"))
+ return;
+ p = p2;
+ if((p2 = strstr(p, " $")) == NULL)
+ return;
+ *p2 = 0;
+ p2 += 2;
+ hubrecvchat(fn->sk, fn, p, p2);
+ hubhandleaction(sk, fn, cmd, args);
+}
+
+static void cmd_sr(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+ struct dchub *hub;
+ char *p, *p2;
+ char *nick, *filename, *hubname;
+ int size, slots;
+ struct srchres *sr;
+ wchar_t *wnick, *wfile;
+
+ hub = fn->data;
+ nick = p = args;
+ if((p2 = strchr(p, ' ')) == NULL)
+ return;
+ *p2 = 0;
+ p = p2 + 1;
+ filename = p;
+ if((p2 = strchr(p, 5)) == NULL)
+ return;
+ *p2 = 0;
+ p = p2 + 1;
+ if((p2 = strchr(p, ' ')) == NULL)
+ return;
+ *p2 = 0;
+ size = atoi(p);
+ p = p2 + 1;
+ if((p2 = strchr(p, '/')) == NULL)
+ return;
+ *p2 = 0;
+ slots = atoi(p);
+ p = p2 + 1;
+ if((p2 = strchr(p, 5)) == NULL)
+ return;
+ p = p2 + 1;
+ hubname = p;
+ if((p2 = strstr(p, " (")) == NULL)
+ return;
+ *p2 = 0;
+ if((wnick = icmbstowcs(nick, DCCHARSET)) == NULL)
+ return;
+ if((wfile = icmbstowcs(filename, DCCHARSET)) == NULL)
+ {
+ free(wnick);
+ return;
+ }
+ sr = newsrchres(&dcnet, wfile, wnick);
+ if(sr->peernick != NULL)
+ free(sr->peernick);
+ sr->peernick = swcsdup(wnick);
+ sr->size = size;
+ sr->slots = slots;
+ free(wfile);
+ free(wnick);
+ getfnetnode(sr->fn = fn);
+ submitsrchres(sr);
+ freesrchres(sr);
+ hubhandleaction(sk, fn, cmd, args);
+}
+
+static void cmd_usercommand(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+ /* Do nothing for now. */
+}
+
+static void cmd_mynick(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
+{
+ if(peer->nativename != NULL)
+ free(peer->nativename);
+ peer->nativename = sstrdup(args);
+ if(peer->wcsname != NULL)
+ free(peer->wcsname);
+ if((peer->wcsname = icmbstowcs(peer->nativename, DCCHARSET)) == NULL)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ peerhandleaction(sk, peer, cmd, args);
+}
+
+static void cmd_direction(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
+{
+ char *p;
+
+ if((p = strchr(args, ' ')) == NULL)
+ return;
+ *p = 0;
+ if(!strcmp(args, "Upload"))
+ peer->direction = TRNSD_DOWN;
+ if(!strcmp(args, "Download"))
+ peer->direction = TRNSD_UP;
+ peerhandleaction(sk, peer, cmd, args);
+}
+
+static void cmd_peerlock(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
+{
+ char *p;
+
+ if((p = strchr(args, ' ')) == NULL)
+ return;
+ *p = 0;
+ if(!strncmp(args, "EXTENDEDPROTOCOL", 16))
+ peer->extended = 1;
+ if(peer->key != NULL)
+ free(peer->key);
+ peer->key = dcmakekey(args);
+ peerhandleaction(sk, peer, cmd, args);
+}
+
+static void cmd_key(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
+{
+ peerhandleaction(sk, peer, cmd, args);
+}
+
+static void cmd_filelength(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
+{
+ int size;
+
+ if(peer->transfer == NULL)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ size = atoi(args);
+ if(peer->transfer->size == size)
+ {
+
+ } else {
+ /* Will have to restart, then... I really hate this, but what
+ * am I to do then, considering the DC protocol requires the
+ * resume offset before it sends the filesize? */
+ transfersetsize(peer->transfer, size);
+ freedcpeer(peer);
+ return;
+ }
+ peerhandleaction(sk, peer, cmd, args);
+}
+
+static void cmd_error(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
+{
+ if((peer->transfer != NULL) && (peer->transfer->dir == TRNSD_DOWN))
+ {
+ transferseterror(peer->transfer, TRNSE_NOTFOUND);
+ resettransfer(peer->transfer);
+ return;
+ }
+ freedcpeer(peer);
+}
+
+static void cmd_maxedout(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
+{
+ if((peer->transfer != NULL) && (peer->transfer->dir == TRNSD_DOWN))
+ {
+ transferseterror(peer->transfer, TRNSE_NOSLOTS);
+ resettransfer(peer->transfer);
+ return;
+ }
+ freedcpeer(peer);
+}
+
+static struct
+{
+ char *name;
+ char **file;
+ void (*func)(void);
+} lists[] =
+{
+ {"MyList.DcLst", &hmlistname, updatehmlist},
+ {"files.xml", &xmllistname, updatexmllist},
+ {"files.xml.bz2", &xmlbz2listname, updatexmlbz2list},
+ {NULL, NULL}
+};
+
+static int openfilelist(char *name)
+{
+ int i, fd;
+ int errnobak;
+
+ for(i = 0; lists[i].name != NULL; i++)
+ {
+ if(!strcmp(name, lists[i].name))
+ break;
+ }
+ errno = 0;
+ if(lists[i].name == NULL)
+ return(-1);
+ fd = -1;
+ if((*lists[i].file == NULL) || ((fd = open(*lists[i].file, O_RDONLY)) < 0))
+ lists[i].func();
+ if((fd < 0) && ((*lists[i].file == NULL) || ((fd = open(*lists[i].file, O_RDONLY)) < 0)))
+ {
+ errnobak = errno;
+ flog(LOG_ERR, "could not open filelist tempfile: %s", strerror(errno));
+ errno = errnobak;
+ return(-1);
+ }
+ return(fd);
+}
+
+static struct sharecache *findbytth(char *tth32)
+{
+ char *buf;
+ size_t buflen;
+ struct sharecache *node;
+
+ if((buf = base32decode(tth32, &buflen)) == NULL)
+ return(NULL);
+ if(buflen != 24)
+ {
+ free(buf);
+ return(NULL);
+ }
+ for(node = shareroot->child; node != NULL; node = nextscnode(node))
+ {
+ if(node->f.b.hastth && !memcmp(node->hashtth, buf, 24))
+ break;
+ }
+ free(buf);
+ return(node);
+}
+
+static struct sharecache *resdcpath(char *path, char *charset, char sep)
+{
+ struct sharecache *node;
+ char *p, *p2;
+
+ node = shareroot;
+ p = path;
+ while(p != NULL)
+ {
+ if((p2 = strchr(p, sep)) != NULL)
+ *(p2++) = 0;
+ if((node = findcache(node, icsmbstowcs(p, charset, L""))) == NULL)
+ return(NULL);
+ p = p2;
+ }
+ return(node);
+}
+
+static void cmd_get(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
+{
+ int offset;
+ char *p, *buf;
+ wchar_t *buf2;
+ struct sharecache *node;
+ struct socket *lesk;
+ int fd;
+ struct stat sb;
+
+ if(peer->transfer == NULL)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ if((p = strchr(args, '$')) == NULL)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ *(p++) = 0;
+ if((offset = (atoi(p) - 1)) < 0)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ if(((fd = openfilelist(args)) < 0) && (errno != 0))
+ {
+ qstr(sk, "$Error Could not send file list|");
+ freedcpeer(peer);
+ return;
+ } else if(fd >= 0) {
+ if((buf2 = icsmbstowcs(args, DCCHARSET, NULL)) != NULL)
+ transfersetpath(peer->transfer, buf2);
+ }
+ if(fd < 0)
+ {
+ if(slotsleft() < 1)
+ {
+ qstr(sk, "$MaxedOut|");
+ freedcpeer(peer);
+ return;
+ }
+ if((node = resdcpath(args, DCCHARSET, '\\')) == NULL)
+ {
+ qstrf(sk, "$Error File not in share|");
+ freedcpeer(peer);
+ return;
+ }
+ if((fd = opensharecache(node)) < 0)
+ {
+ qstrf(sk, "$Error %s|", strerror(errno));
+ freedcpeer(peer);
+ return;
+ }
+ buf = getfspath(node);
+ if((buf2 = icsmbstowcs(buf, NULL, NULL)) == NULL)
+ flog(LOG_WARNING, "could not convert native path into a wcs (%s): %s", buf, strerror(errno));
+ else
+ transfersetpath(peer->transfer, buf2);
+ free(buf);
+ }
+ if(fstat(fd, &sb) < 0)
+ {
+ close(fd);
+ flog(LOG_WARNING, "could not stat file %ls: %s", node->name, strerror(errno));
+ qstrf(sk, "$Error|");
+ freedcpeer(peer);
+ return;
+ }
+ if((offset != 0) && (lseek(fd, offset, SEEK_SET) < 0))
+ {
+ close(fd);
+ qstrf(sk, "$Error Offset out of range|");
+ freedcpeer(peer);
+ return;
+ }
+ lesk = wrapsock(fd);
+ transferprepul(peer->transfer, sb.st_size, offset, -1, lesk);
+ putsock(lesk);
+ qstrf(sk, "$FileLength %i|", peer->transfer->size);
+ peerhandleaction(sk, peer, cmd, args);
+}
+
+static void starttrans(struct dcpeer *peer)
+{
+ peer->state = PEER_TRNS;
+ transferstartul(peer->transfer, peer->sk);
+ peer->sk->writecb = (void (*)(struct socket *, void *))transwrite;
+}
+
+static void cmd_send(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
+{
+ if(peer->transfer == NULL)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ if(peer->transfer->localend == NULL)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ peer->ptclose = 1;
+ starttrans(peer);
+ peerhandleaction(sk, peer, cmd, args);
+}
+
+static void cmd_supports(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
+{
+ int i;
+ char *p, *p2;
+ char **arr;
+ size_t arrsize, arrdata;
+
+ if(peer->supports != NULL)
+ {
+ for(i = 0; peer->supports[i] != NULL; i++)
+ free(peer->supports[i]);
+ free(peer->supports);
+ }
+ arr = NULL;
+ arrsize = arrdata = 0;
+ p = args;
+ do
+ {
+ if((p2 = strchr(p, ' ')) != NULL)
+ *(p2++) = 0;
+ if(*p == 0)
+ continue;
+ addtobuf(arr, sstrdup(p));
+ } while((p = p2) != NULL);
+ addtobuf(arr, NULL);
+ peer->supports = arr;
+ peerhandleaction(sk, peer, cmd, args);
+}
+
+static void cmd_getblock(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
+{
+ int fd;
+ char *p, *p2;
+ int start, numbytes;
+ char *charset, *buf;
+ wchar_t *buf2;
+ struct sharecache *node;
+ struct stat sb;
+ struct socket *lesk;
+
+ if(peer->transfer == NULL)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ p = args;
+ if((p2 = strchr(p, ' ')) == NULL)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ *(p2++) = 0;
+ start = atoi(p);
+ p = p2;
+ if((p2 = strchr(p, ' ')) == NULL)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ *(p2++) = 0;
+ numbytes = atoi(p);
+ p = p2;
+ if(!strcmp(cmd, "$UGetBlock") || !strcmp(cmd, "$UGetZBlock"))
+ charset = "UTF-8";
+ else
+ charset = DCCHARSET;
+ if(!strcmp(cmd, "$GetZBlock") || !strcmp(cmd, "$UGetZBlock"))
+ initcompress(peer, CPRS_ZLIB);
+ if(((fd = openfilelist(p)) < 0) && (errno != 0))
+ {
+ qstr(sk, "$Error Could not send file list|");
+ return;
+ } else if(fd >= 0) {
+ if((buf2 = icsmbstowcs(args, charset, NULL)) != NULL)
+ transfersetpath(peer->transfer, buf2);
+ }
+ if(fd < 0)
+ {
+ if(slotsleft() < 1)
+ {
+ qstr(sk, "$MaxedOut|");
+ return;
+ }
+ if((node = resdcpath(p, charset, '\\')) == NULL)
+ {
+ qstr(sk, "$Error File not in cache|");
+ return;
+ }
+ if((fd = opensharecache(node)) < 0)
+ {
+ qstrf(sk, "$Error %s|", strerror(errno));
+ return;
+ }
+ buf = getfspath(node);
+ if((buf2 = icsmbstowcs(buf, NULL, NULL)) == NULL)
+ flog(LOG_WARNING, "could not convert native path into a wcs (%s): %s", buf, strerror(errno));
+ else
+ transfersetpath(peer->transfer, buf2);
+ free(buf);
+ }
+ if(fstat(fd, &sb) < 0)
+ {
+ close(fd);
+ flog(LOG_WARNING, "could not stat file %ls: %s", node->name, strerror(errno));
+ qstr(sk, "$Error|");
+ return;
+ }
+ if((start != 0) && ((start >= sb.st_size) || (lseek(fd, start, SEEK_SET) < 0)))
+ {
+ close(fd);
+ qstr(sk, "$Error Offset out of range|");
+ return;
+ }
+ if((numbytes < 0) || (start + numbytes > sb.st_size))
+ numbytes = sb.st_size - start;
+ lesk = wrapsock(fd);
+ transferprepul(peer->transfer, sb.st_size, start, start + numbytes, lesk);
+ putsock(lesk);
+ qstrf(sk, "$Sending %i|", numbytes);
+ starttrans(peer);
+ peerhandleaction(sk, peer, cmd, args);
+}
+
+static void cmd_adcget(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
+{
+ int i;
+ char **argv, *buf;
+ int start, numbytes;
+ struct sharecache *node;
+ struct stat sb;
+ struct socket *lesk;
+ wchar_t *wbuf;
+ int fd;
+
+ if(peer->transfer == NULL)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ if((argv = parseadc(args)) == NULL)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ if(parrlen(argv) < 4)
+ {
+ freedcpeer(peer);
+ goto out;
+ }
+ start = atoi(argv[2]);
+ numbytes = atoi(argv[3]);
+ node = NULL;
+ fd = -1;
+ if(((fd = openfilelist(argv[1])) < 0) && (errno != 0))
+ {
+ qstr(sk, "$Error Could not send file list|");
+ goto out;
+ } else if(fd >= 0) {
+ if((wbuf = icsmbstowcs(argv[1], "UTF-8", NULL)) != NULL)
+ transfersetpath(peer->transfer, wbuf);
+ }
+ if(fd < 0)
+ {
+ if(slotsleft() < 1)
+ {
+ qstr(sk, "$MaxedOut|");
+ goto out;
+ }
+ if(!strncmp(argv[1], "TTH/", 4))
+ {
+ if((node = findbytth(argv[1] + 4)) == NULL)
+ {
+ qstr(sk, "$Error File not in cache|");
+ goto out;
+ }
+ } else {
+ if((node = resdcpath(argv[1], "UTF-8", '/')) == NULL)
+ {
+ qstr(sk, "$Error File not in cache|");
+ goto out;
+ }
+ }
+ if((fd = opensharecache(node)) < 0)
+ {
+ qstrf(sk, "$Error %s|", strerror(errno));
+ goto out;
+ }
+ buf = getfspath(node);
+ if((wbuf = icsmbstowcs(buf, NULL, NULL)) == NULL)
+ flog(LOG_WARNING, "could not convert native path into a wcs (%s): %s", buf, strerror(errno));
+ else
+ transfersetpath(peer->transfer, wbuf);
+ free(buf);
+ }
+ if(!strcmp(argv[0], "file"))
+ {
+ for(i = 4; argv[i] != NULL; i++)
+ {
+ if(!strcmp(argv[i], "ZL1"))
+ initcompress(peer, CPRS_ZLIB);
+ }
+ if(fstat(fd, &sb) < 0)
+ {
+ flog(LOG_WARNING, "could not stat file %ls: %s", node->name, strerror(errno));
+ qstr(sk, "$Error|");
+ goto out;
+ }
+ if((start != 0) && ((start >= sb.st_size) || (lseek(fd, start, SEEK_SET) < 0)))
+ {
+ qstr(sk, "$Error Offset out of range|");
+ goto out;
+ }
+ if((numbytes < 0) || (start + numbytes > sb.st_size))
+ numbytes = sb.st_size - start;
+ lesk = wrapsock(fd);
+ transferprepul(peer->transfer, sb.st_size, start, start + numbytes, lesk);
+ putsock(lesk);
+ fd = -1;
+ qstr(sk, "$ADCSND");
+ sendadc(sk, "file");
+ sendadc(sk, argv[1]);
+ sendadcf(sk, "%i", start);
+ sendadcf(sk, "%i", numbytes);
+ if(peer->compress == CPRS_ZLIB)
+ sendadc(sk, "ZL1");
+ qstr(sk, "|");
+ starttrans(peer);
+ } else if(!strcmp(argv[0], "tthl")) {
+ /*
+ * XXX: Implement full TTHL support.
+ *
+ * In the meantime, this is better than nothing, since it at
+ * least allows fetching of the TTH root if it isn't already
+ * known.
+ */
+ if(node == NULL)
+ {
+ qstr(sk, "$Error no TTHL data for virtual files|");
+ goto out;
+ }
+ qstr(sk, "$ADCSND");
+ sendadc(sk, "tthl");
+ sendadc(sk, argv[1]);
+ sendadc(sk, "0");
+ sendadc(sk, "24");
+ qstr(sk, "|");
+ sockqueue(sk, node->hashtth, 24);
+ } else {
+ qstr(sk, "$Error Namespace not implemented|");
+ goto out;
+ }
+ peerhandleaction(sk, peer, cmd, args);
+
+ out:
+ if(fd >= 0)
+ close(fd);
+ freeparr(argv);
+}
+
+/*
+Hub skeleton:
+static void cmd_(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+ struct dchub *hub;
+
+ hub = fn->data;
+ hubhandleaction(sk, fn, cmd, args);
+}
+
+Peer skeleton:
+static void cmd_(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
+{
+ peerhandleaction(sk, peer, cmd, args);
+}
+
+*/
+
+static int hubreqconn(struct fnetpeer *peer)
+{
+ struct dchub *hub;
+ char *mbsnick;
+
+ if((peer->fn->state != FNN_EST) || (peer->fn->fnet != &dcnet))
+ {
+ errno = EINVAL;
+ return(1);
+ }
+ if((hub = peer->fn->data) == NULL)
+ {
+ errno = EFAULT;
+ return(1);
+ }
+ if((mbsnick = icwcstombs(peer->id, DCCHARSET)) == NULL)
+ return(1); /* Shouldn't happen, of course, but who knows... */
+ if(tcpsock != NULL)
+ {
+ sendctm(peer->fn->sk, mbsnick);
+ expectpeer(mbsnick, peer->fn);
+ } else {
+ qstrf(peer->fn->sk, "$RevConnectToMe %s %s|", hub->nativenick, mbsnick);
+ }
+ free(mbsnick);
+ return(0);
+}
+
+static int hubsendchat(struct fnetnode *fn, int public, wchar_t *to, wchar_t *string)
+{
+ struct dchub *hub;
+ char *mbsstring, *mbsto;
+
+ hub = fn->data;
+ if((mbsto = icwcstombs(to, DCCHARSET)) == NULL)
+ {
+ errno = EILSEQ;
+ return(1);
+ }
+ if((mbsstring = icwcstombs(string, DCCHARSET)) == NULL)
+ {
+ errno = EILSEQ;
+ return(1);
+ }
+ if((strchr(mbsto, '|') != NULL) || (strchr(mbsto, ' ') != NULL))
+ {
+ free(mbsto);
+ free(mbsstring);
+ errno = ESRCH;
+ return(1);
+ }
+ if(strchr(mbsstring, '|') != NULL)
+ {
+ free(mbsto);
+ free(mbsstring);
+ errno = EILSEQ;
+ return(1);
+ }
+ if(public)
+ {
+ if(*to == L'\0')
+ {
+ qstrf(fn->sk, "<%s> %s|", hub->nativenick, mbsstring);
+ } else {
+ qstrf(fn->sk, "$To: %s From: %s $<%s> %s|", mbsto, hub->nativenick, hub->nativenick, mbsstring);
+ }
+ } else {
+ qstrf(fn->sk, "$To: %s From: %s $<%s> %s|", mbsto, hub->nativenick, hub->nativenick, mbsstring);
+ }
+ free(mbsto);
+ free(mbsstring);
+ return(0);
+}
+
+static void findsizelimit(struct sexpr *sexpr, int *min, int *max)
+{
+ int minl, maxl, minr, maxr, retmin, retmax;
+
+ switch(sexpr->op)
+ {
+ case SOP_AND:
+ findsizelimit(sexpr->l, &minl, &maxl);
+ findsizelimit(sexpr->r, &minr, &maxr);
+ retmin = (minl > minr)?minl:minr;
+ if((maxl != -1) && (maxr != -1))
+ retmax = (maxl < maxr)?maxl:maxr;
+ else if(maxl != -1)
+ retmax = maxl;
+ else if(maxr != -1)
+ retmax = maxr;
+ else
+ retmax = -1;
+ break;
+ case SOP_OR:
+ findsizelimit(sexpr->l, &minl, &maxl);
+ findsizelimit(sexpr->r, &minr, &maxr);
+ retmin = (minl < minr)?minl:minr;
+ if((maxl == -1) || (maxr == -1))
+ retmax = -1;
+ else
+ retmax = (maxl > maxr)?maxl:maxr;
+ break;
+ case SOP_NOT:
+ findsizelimit(sexpr->l, &minl, &maxl);
+ if((minl == 0) && (maxl == -1)) /* Interval is unspecified */
+ {
+ retmin = 0;
+ retmax = -1;
+ } else if((minl == 0) && (maxl != -1)) {
+ retmin = maxl + 1;
+ retmax = -1;
+ } else if((minl != 0) && (maxl == -1)) {
+ retmin = 0;
+ retmax = minl - 1;
+ } else { /* This would yield two seperate intervals, which DC cannot handle */
+ retmin = 0;
+ retmax = -1;
+ }
+ case SOP_SIZELT:
+ retmin = 0;
+ retmax = sexpr->d.n - 1;
+ break;
+ case SOP_SIZEEQ:
+ retmin = sexpr->d.n;
+ retmax = sexpr->d.n;
+ break;
+ case SOP_SIZEGT:
+ retmin = sexpr->d.n + 1;
+ retmax = -1;
+ break;
+ default:
+ retmin = 0;
+ retmax = -1;
+ break;
+ }
+ if(min != NULL)
+ *min = retmin;
+ if(max != NULL)
+ *max = retmax;
+}
+
+static int hubsearch(struct fnetnode *fn, struct search *srch, struct srchfnnlist *ln)
+{
+ struct dchub *hub;
+ struct wcslist *list, *cur;
+ char *sstr, *buf, *p;
+ size_t sstrsize, sstrdata;
+ struct sockaddr *name;
+ socklen_t namelen;
+ int minsize, maxsize;
+ char sizedesc[32];
+
+ hub = fn->data;
+ if((fn->state != FNN_EST) || (fn->sk == NULL) || (fn->sk->state != SOCK_EST))
+ return(1);
+ list = findsexprstrs(srch->sexpr);
+ findsizelimit(srch->sexpr, &minsize, &maxsize);
+ if((minsize != 0) && (maxsize != -1))
+ {
+ /* Choose either minsize or maxsize by trying to determine
+ * which choice will be most restrictive. The result can be
+ * approximative at best anyway, so don't try too hard... */
+ if((50000000 - maxsize) > (minsize - 50000000))
+ minsize = 0;
+ else
+ maxsize = -1;
+ }
+ if(minsize != 0)
+ {
+ snprintf(sizedesc, sizeof(sizedesc), "T?F?%i", minsize);
+ } else if(maxsize != -1) {
+ snprintf(sizedesc, sizeof(sizedesc), "T?T?%i", maxsize);
+ } else {
+ strcpy(sizedesc, "F?F?0");
+ }
+ sstr = NULL;
+ sstrsize = sstrdata = 0;
+ if(list != NULL)
+ {
+ for(cur = list; cur != NULL; cur = cur->next)
+ {
+ if((buf = icwcstombs(cur->str, DCCHARSET)) == NULL)
+ {
+ /* Can't find anything anyway if the search expression
+ * requires characters outside DC's charset. There's
+ * nothing technically wrong with the search itself,
+ * however, so return success. This should be
+ * considered as an optimization. */
+ freesl(&list);
+ if(sstr != NULL)
+ free(sstr);
+ return(0);
+ }
+ if(cur != list)
+ addtobuf(sstr, '$');
+ /*
+ * It probably doesn't hurt if buf contains any extra
+ * dollar signs - it will just result in extra search
+ * terms, and the extraneous results will be filtered by
+ * the search layer anyway. It hurts if it contains any
+ * pipes, though, so let's sell them for money.
+ */
+ for(p = buf; *p; p++)
+ {
+ if(*p == '|')
+ *p = '$';
+ }
+ bufcat(sstr, buf, strlen(buf));
+ free(buf);
+ }
+ } else {
+ /* Will match all files... :-/ */
+ addtobuf(sstr, '.');
+ }
+ addtobuf(sstr, 0);
+ if(tcpsock != NULL)
+ {
+ if(sockgetremotename(udpsock, &name, &namelen) < 0)
+ {
+ flog(LOG_WARNING, "cannot get address of UDP socket");
+ } else {
+ qstrf(fn->sk, "$Search %s %s?1?%s|", formataddress(name, namelen), sizedesc, sstr);
+ free(name);
+ }
+ } else {
+ qstrf(fn->sk, "$Search Hub:%s %s?1?%s|", hub->nativenick, sizedesc, sstr);
+ }
+ free(sstr);
+ freesl(&list);
+ fn->lastsrch = time(NULL);
+ return(0);
+}
+
+#undef skipcmd
+#undef qstr
+#undef qstrf
+
+#define cc(c) ((void (*)(struct socket *, void *, char *, char *))(c))
+struct command hubcmds[] =
+{
+ {"$Lock", cc(cmd_lock)},
+ {"$HubName", cc(cmd_hubname)},
+ {"$Hello", cc(cmd_hello)},
+ {"$Quit", cc(cmd_quit)},
+ {"$NickList", cc(cmd_nicklist)},
+ {"$OpList", cc(cmd_oplist)},
+ {"$MyINFO", cc(cmd_myinfo)},
+ {"$ForceMove", cc(cmd_forcemove)},
+ {"$Search", cc(cmd_search)},
+ {"$MultiSearch", cc(cmd_search)},
+ {"$ConnectToMe", cc(cmd_connecttome)},
+ {"$RevConnectToMe", cc(cmd_revconnecttome)},
+ {"$GetNetInfo", cc(cmd_getnetinfo)},
+ {"$To:", cc(cmd_to)},
+ {"$SR", cc(cmd_sr)},
+ {"$UserCommand", cc(cmd_usercommand)},
+ {NULL, NULL}
+};
+
+struct command peercmds[] =
+{
+ {"$MyNick", cc(cmd_mynick)},
+ {"$Lock", cc(cmd_peerlock)},
+ {"$Direction", cc(cmd_direction)},
+ {"$Key", cc(cmd_key)},
+ {"$FileLength", cc(cmd_filelength)},
+ {"$Error", cc(cmd_error)},
+ {"$MaxedOut", cc(cmd_maxedout)},
+ {"$Get", cc(cmd_get)},
+ {"$Send", cc(cmd_send)},
+ {"$Supports", cc(cmd_supports)},
+ {"$GetBlock", cc(cmd_getblock)},
+ {"$UGetBlock", cc(cmd_getblock)},
+ {"$GetZBlock", cc(cmd_getblock)},
+ {"$UGetZBlock", cc(cmd_getblock)},
+ {"$ADCGET", cc(cmd_adcget)},
+ {NULL, NULL}
+};
+#undef cc
+
+static void dctransdetach(struct transfer *transfer, struct dcpeer *peer)
+{
+ CBUNREG(transfer, trans_filterout, peer);
+ if(peer->freeing)
+ return;
+ peer->transfer = NULL;
+ freedcpeer(peer);
+}
+
+static void dctransgotdata(struct transfer *transfer, struct dcpeer *peer)
+{
+ int ret;
+ void *buf;
+ char outbuf[1024];
+ z_stream *cstr;
+ size_t bufsize;
+
+ if((peer->state == PEER_TRNS) || (peer->state == PEER_SYNC))
+ {
+ if(sockqueuesize(peer->sk) < 65536)
+ {
+ if((buf = transfergetdata(transfer, &bufsize)) != NULL)
+ {
+ if(peer->compress == CPRS_NONE)
+ {
+ sockqueue(peer->sk, buf, bufsize);
+ } else if(peer->compress == CPRS_ZLIB) {
+ cstr = peer->cprsdata;
+ cstr->next_in = buf;
+ cstr->avail_in = bufsize;
+ while(cstr->avail_in > 0)
+ {
+ cstr->next_out = outbuf;
+ cstr->avail_out = sizeof(outbuf);
+ if((ret = deflate(cstr, 0)) != Z_OK)
+ {
+ flog(LOG_WARNING, "bug? deflate() did not return Z_OK (but rather %i)", ret);
+ freedcpeer(peer);
+ return;
+ }
+ sockqueue(peer->sk, outbuf, sizeof(outbuf) - cstr->avail_out);
+ }
+ }
+ free(buf);
+ }
+ if(peer->state == PEER_SYNC)
+ {
+ if(peer->compress == CPRS_ZLIB)
+ {
+ cstr = peer->cprsdata;
+ cstr->next_in = NULL;
+ cstr->avail_in = 0;
+ do
+ {
+ cstr->next_out = outbuf;
+ cstr->avail_out = sizeof(outbuf);
+ ret = deflate(cstr, Z_FINISH);
+ if((ret != Z_OK) && (ret != Z_STREAM_END))
+ {
+ flog(LOG_WARNING, "bug? deflate(Z_FINISH) did not return Z_OK (but rather %i)", ret);
+ freedcpeer(peer);
+ return;
+ }
+ sockqueue(peer->sk, outbuf, sizeof(outbuf) - cstr->avail_out);
+ } while(ret != Z_STREAM_END);
+ }
+ if(peer->ptclose)
+ {
+ freedcpeer(peer);
+ } else {
+ peer->state = PEER_CMD;
+ endcompress(peer);
+ transfersetstate(transfer, TRNS_HS);
+ socksettos(peer->sk, confgetint("fnet", "fnptos"));
+ peer->sk->writecb = NULL;
+ }
+ }
+ }
+ }
+}
+
+static void dctransendofdata(struct transfer *transfer, struct dcpeer *peer)
+{
+ peer->state = PEER_SYNC;
+ dctransgotdata(transfer, peer);
+}
+
+static void dcwantdata(struct transfer *transfer, struct dcpeer *peer)
+{
+ if(transferdatasize(transfer) < 65536)
+ peer->sk->ignread = 0;
+}
+
+static void transread(struct socket *sk, struct dcpeer *peer)
+{
+ void *buf;
+ size_t bufsize;
+ struct transfer *transfer;
+
+ if((buf = sockgetinbuf(sk, &bufsize)) == NULL)
+ return;
+ if(peer->transfer == NULL)
+ {
+ free(buf);
+ freedcpeer(peer);
+ return;
+ }
+ transferputdata(peer->transfer, buf, bufsize);
+ free(buf);
+ if(peer->transfer->curpos >= peer->transfer->size)
+ {
+ transfer = peer->transfer;
+ transferdetach(transfer);
+ transferendofdata(transfer);
+ return;
+ }
+ if(transferdatasize(peer->transfer) > 65535)
+ sk->ignread = 1;
+}
+
+static void transerr(struct socket *sk, int err, struct dcpeer *peer)
+{
+ struct transfer *transfer;
+
+ if((transfer = peer->transfer) == NULL)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ transferdetach(transfer);
+ transferendofdata(transfer);
+}
+
+static void transwrite(struct socket *sk, struct dcpeer *peer)
+{
+ if((peer->state != PEER_TRNS) && (peer->state != PEER_SYNC))
+ return;
+ if(peer->transfer == NULL)
+ {
+ freedcpeer(peer);
+ return;
+ }
+ dctransgotdata(peer->transfer, peer);
+}
+
+static void udpread(struct socket *sk, void *data)
+{
+ char *buf, *p, *p2;
+ size_t buflen;
+ char *nick, *filename, *hubname;
+ int size, slots;
+ struct fnetnode *fn, *myfn;
+ struct dchub *hub;
+ struct srchres *sr;
+ wchar_t *wnick, *wfile;
+
+ if((buf = sockgetinbuf(sk, &buflen)) == NULL)
+ return;
+ buf = srealloc(buf, buflen + 1);
+ buf[buflen] = 0;
+ if(!strncmp(buf, "$SR ", 4))
+ {
+ p = buf + 4;
+ nick = p;
+ if((p2 = strchr(p, ' ')) == NULL)
+ {
+ free(buf);
+ return;
+ }
+ *p2 = 0;
+ p = p2 + 1;
+ filename = p;
+ if((p2 = strchr(p, 5)) == NULL)
+ {
+ free(buf);
+ return;
+ }
+ *p2 = 0;
+ p = p2 + 1;
+ if((p2 = strchr(p, ' ')) == NULL)
+ {
+ free(buf);
+ return;
+ }
+ *p2 = 0;
+ size = atoi(p);
+ p = p2 + 1;
+ if((p2 = strchr(p, '/')) == NULL)
+ {
+ free(buf);
+ return;
+ }
+ *p2 = 0;
+ slots = atoi(p);
+ p = p2 + 1;
+ if((p2 = strchr(p, 5)) == NULL)
+ {
+ free(buf);
+ return;
+ }
+ p = p2 + 1;
+ hubname = p;
+ if((p2 = strstr(p, " (")) == NULL)
+ {
+ free(buf);
+ return;
+ }
+ *p2 = 0;
+ if((wnick = icmbstowcs(nick, DCCHARSET)) == NULL)
+ {
+ free(buf);
+ return;
+ }
+ if((wfile = icmbstowcs(filename, DCCHARSET)) == NULL)
+ {
+ free(wnick);
+ free(buf);
+ return;
+ }
+ myfn = NULL;
+ for(fn = fnetnodes; fn != NULL; fn = fn->next)
+ {
+ if((fn->fnet == &dcnet) && ((hub = fn->data) != NULL))
+ {
+ if((hub->nativename != NULL) && !strcmp(hub->nativename, hubname))
+ {
+ if(myfn == NULL)
+ {
+ myfn = fn;
+ } else {
+ myfn = NULL;
+ break;
+ }
+ }
+ }
+ }
+ sr = newsrchres(&dcnet, wfile, wnick);
+ if(sr->peernick != NULL)
+ free(sr->peernick);
+ sr->peernick = swcsdup(wnick);
+ sr->size = size;
+ sr->slots = slots;
+ free(wfile);
+ free(wnick);
+ if(myfn != NULL)
+ getfnetnode(sr->fn = myfn);
+ submitsrchres(sr);
+ freesrchres(sr);
+ }
+ free(buf);
+}
+
+static void hubread(struct socket *sk, struct fnetnode *fn)
+{
+ struct dchub *hub;
+ char *newbuf;
+ size_t datalen;
+ char *p;
+
+ hub = (struct dchub *)fn->data;
+ if((newbuf = sockgetinbuf(sk, &datalen)) == NULL)
+ return;
+ if(hub->inbufdata > 500000) /* Discard possible malicious data */
+ hub->inbufdata = 0;
+ sizebuf2(hub->inbuf, hub->inbufdata + datalen, 1);
+ memcpy(hub->inbuf + hub->inbufdata, newbuf, datalen);
+ free(newbuf);
+ p = hub->inbuf + hub->inbufdata;
+ hub->inbufdata += datalen;
+ while((datalen > 0) && ((p = memchr(p, '|', datalen)) != NULL))
+ {
+ *(p++) = 0;
+ newqcmd(&hub->queue, hub->inbuf);
+ memmove(hub->inbuf, p, hub->inbufdata -= p - hub->inbuf);
+ datalen = hub->inbufdata;
+ p = hub->inbuf;
+ }
+}
+
+static void huberr(struct socket *sk, int err, struct fnetnode *fn)
+{
+ killfnetnode(fn);
+}
+
+static int hubsetnick(struct fnetnode *fn, wchar_t *newnick)
+{
+ struct dchub *hub;
+ char *buf;
+
+ hub = fn->data;
+ if((buf = icwcstombs(newnick, DCCHARSET)) == NULL)
+ return(1);
+ if((strchr(buf, ' ') != NULL) || (strchr(buf, '|') != NULL) || (strchr(buf, '$') != NULL))
+ {
+ free(buf);
+ return(1);
+ }
+ if(hub == NULL) /* Not yet connected */
+ {
+ free(buf);
+ return(0);
+ }
+ if(hub->nativenick != NULL)
+ free(hub->nativenick);
+ hub->nativenick = buf;
+ return(0);
+}
+
+static struct dchub *newdchub(struct fnetnode *fn)
+{
+ struct dchub *new;
+
+ new = smalloc(sizeof(*new));
+ memset(new, 0, sizeof(*new));
+ fn->data = new;
+ if(hubsetnick(fn, fn->mynick))
+ fnetsetnick(fn, L"DoldaConnectUser-IN");
+ /* IN as in Invalid Nick */
+ return(new);
+}
+
+static struct dcpeer *newdcpeer(struct socket *sk)
+{
+ struct dcpeer *new;
+
+ new = smalloc(sizeof(*new));
+ memset(new, 0, sizeof(*new));
+ new->transfer = NULL;
+ getsock(sk);
+ new->sk = sk;
+ new->next = peers;
+ new->prev = NULL;
+ if(peers != NULL)
+ peers->prev = new;
+ peers = new;
+ numdcpeers++;
+ return(new);
+}
+
+static void freedcpeer(struct dcpeer *peer)
+{
+ int i;
+ struct qcommand *qcmd;
+
+ peer->freeing = 1;
+ if(peers == peer)
+ peers = peer->next;
+ if(peer->next != NULL)
+ peer->next->prev = peer->prev;
+ if(peer->prev != NULL)
+ peer->prev->next = peer->next;
+ if(peer->transfer != NULL)
+ {
+ if(peer->transfer->dir == TRNSD_UP)
+ peer->transfer->close = 1;
+ if(peer->transfer->dir == TRNSD_DOWN)
+ resettransfer(peer->transfer);
+ transferdetach(peer->transfer);
+ }
+ if(peer->sk->data == peer)
+ peer->sk->data = NULL;
+ peer->sk->readcb = NULL;
+ peer->sk->writecb = NULL;
+ peer->sk->errcb = NULL;
+ putsock(peer->sk);
+ endcompress(peer);
+ if(peer->supports != NULL)
+ {
+ for(i = 0; peer->supports[i] != NULL; i++)
+ free(peer->supports[i]);
+ free(peer->supports);
+ }
+ if(peer->mbspath != NULL)
+ free(peer->mbspath);
+ if(peer->inbuf != NULL)
+ free(peer->inbuf);
+ if(peer->key != NULL)
+ free(peer->key);
+ if(peer->wcsname != NULL)
+ free(peer->wcsname);
+ if(peer->nativename != NULL)
+ free(peer->nativename);
+ if(peer->fn != NULL)
+ putfnetnode(peer->fn);
+ while((qcmd = ulqcmd(&peer->queue)) != NULL)
+ freeqcmd(qcmd);
+ free(peer);
+ numdcpeers--;
+}
+
+static void hubconnect(struct fnetnode *fn)
+{
+ fn->sk->readcb = (void (*)(struct socket *, void *))hubread;
+ fn->sk->errcb = (void (*)(struct socket *, int, void *))huberr;
+ getfnetnode(fn);
+ fn->data = newdchub(fn);
+ fn->sk->data = fn;
+ return;
+}
+
+static void hubdestroy(struct fnetnode *fn)
+{
+ struct dchub *hub;
+ struct qcommand *qcmd;
+
+ hub = (struct dchub *)fn->data;
+ if(fn->sk != NULL)
+ {
+ if(fn->sk->data == fn)
+ {
+ fn->sk->data = NULL;
+ putfnetnode(fn);
+ }
+ }
+ if(hub == NULL)
+ return;
+ while((qcmd = ulqcmd(&hub->queue)) != NULL)
+ freeqcmd(qcmd);
+ if(hub->nativename != NULL)
+ free(hub->nativename);
+ if(hub->nativenick != NULL)
+ free(hub->nativenick);
+ if(hub->inbuf != NULL)
+ free(hub->inbuf);
+ free(hub);
+}
+
+static wchar_t *dcbasename(wchar_t *filename)
+{
+ wchar_t *ret;
+
+ if((ret = wcsrchr(filename, L'\\')) != NULL)
+ return(ret + 1);
+ return(filename);
+}
+
+static struct transferiface dctransfer =
+{
+ .detach = (void (*)(struct transfer *, void *))dctransdetach,
+ .gotdata = (void (*)(struct transfer *, void *))dctransgotdata,
+ .endofdata = (void (*)(struct transfer *, void *))dctransendofdata,
+ .wantdata = (void (*)(struct transfer *, void *))dcwantdata
+};
+
+static struct fnet dcnet =
+{
+ .name = L"dc",
+ .connect = hubconnect,
+ .destroy = hubdestroy,
+ .setnick = hubsetnick,
+ .reqconn = hubreqconn,
+ .sendchat = hubsendchat,
+ .search = hubsearch,
+ .filebasename = dcbasename
+};
+
+static void peerread(struct socket *sk, struct dcpeer *peer)
+{
+ char *newbuf, *p;
+ size_t datalen;
+
+ if((newbuf = sockgetinbuf(sk, &datalen)) == NULL)
+ return;
+ sizebuf2(peer->inbuf, peer->inbufdata + datalen, 1);
+ memcpy(peer->inbuf + peer->inbufdata, newbuf, datalen);
+ free(newbuf);
+ p = peer->inbuf + peer->inbufdata;
+ peer->inbufdata += datalen;
+ while((datalen > 0) && (p = memchr(p, '|', datalen)) != NULL)
+ {
+ *(p++) = 0;
+ newqcmd(&peer->queue, peer->inbuf);
+ memmove(peer->inbuf, p, peer->inbufdata -= p - peer->inbuf);
+ datalen = peer->inbufdata;
+ p = peer->inbuf;
+ }
+}
+
+static void peererror(struct socket *sk, int err, struct dcpeer *peer)
+{
+ freedcpeer(peer);
+}
+
+static void peerconnect(struct socket *sk, int err, struct fnetnode *fn)
+{
+ struct dcpeer *peer;
+
+ if(err != 0)
+ {
+ putfnetnode(fn);
+ return;
+ }
+ peer = newdcpeer(sk);
+ peer->fn = fn;
+ peer->accepted = 0;
+ sk->readcb = (void (*)(struct socket *, void *))peerread;
+ sk->errcb = (void (*)(struct socket *, int, void *))peererror;
+ sk->data = peer;
+ socksettos(sk, confgetint("fnet", "fnptos"));
+ peerhandleaction(sk, peer, NULL, NULL);
+ putsock(sk);
+}
+
+static void peeraccept(struct socket *sk, struct socket *newsk, void *data)
+{
+ struct dcpeer *peer;
+
+ peer = newdcpeer(newsk);
+ peer->accepted = 1;
+ newsk->readcb = (void (*)(struct socket *, void *))peerread;
+ newsk->errcb = (void (*)(struct socket *, int, void *))peererror;
+ newsk->data = peer;
+ socksettos(newsk, confgetint("fnet", "fnptos"));
+ peerhandleaction(sk, peer, NULL, NULL);
+}
+
+static void updatehmlist(void)
+{
+ int i, lev, ic, ret;
+ struct sharecache *node;
+ char *buf, *buf2, numbuf[32];
+ size_t bufsize, bufdata;
+ int fd, ibuf;
+
+ bufdata = 0;
+ buf = smalloc(bufsize = 65536);
+ node = shareroot->child;
+ lev = 0;
+ while(1)
+ {
+ ic = 0;
+ if((buf2 = icwcstombs(node->name, DCCHARSET)) != NULL)
+ {
+ for(i = 0; i < lev; i++)
+ addtobuf(buf, 9);
+ bufcat(buf, buf2, strlen(buf2));
+ free(buf2);
+ if(node->f.b.type == FILE_REG)
+ {
+ addtobuf(buf, '|');
+ sprintf(numbuf, "%i", node->size);
+ bufcat(buf, numbuf, strlen(numbuf));
+ }
+ addtobuf(buf, 13);
+ addtobuf(buf, 10);
+ } else {
+ ic = 1;
+ }
+ if((node->child != NULL) && !ic)
+ {
+ lev++;
+ node = node->child;
+ } else if(node->next != NULL) {
+ node = node->next;
+ } else {
+ while(node->next == NULL)
+ {
+ lev--;
+ node = node->parent;
+ if(node == shareroot)
+ break;
+ }
+ if(node == shareroot)
+ break;
+ node = node->next;
+ }
+ }
+ if(hmlistname != NULL)
+ {
+ unlink(hmlistname);
+ free(hmlistname);
+ }
+ hmlistname = sstrdup("/tmp/dc-filelist-hm-XXXXXX");
+ if((fd = mkstemp(hmlistname)) < 0)
+ {
+ flog(LOG_WARNING, "could not create HM file list tempfile: %s", strerror(errno));
+ free(hmlistname);
+ hmlistname = NULL;
+ } else {
+ /*
+ * I do not want to implement a good Huffman encoder, and it's not
+ * like Huffman encoding actually yields any impressive results
+ * for DC file lists anyway, so I'll just output a bogus
+ * tree. Implement a good encoder if you want to.
+ */
+ write(fd, "HE3\r\0", 5);
+ write(fd, &bufdata, 4);
+ ibuf = 256;
+ write(fd, &ibuf, 2);
+ ibuf = 8;
+ for(i = 0; i < 256; i++)
+ {
+ write(fd, &i, 1);
+ write(fd, &ibuf, 1);
+ }
+ for(i = 0; i < 256; i++)
+ write(fd, &i, 1);
+ for(buf2 = buf; bufdata > 0;)
+ {
+ if((ret = write(fd, buf2, bufdata)) < 0)
+ {
+ flog(LOG_WARNING, "could not write file list: %s", strerror(errno));
+ break;
+ }
+ bufdata -= ret;
+ buf2 += ret;
+ }
+ close(fd);
+ }
+ free(buf);
+}
+
+static struct xmlent
+{
+ wchar_t c;
+ wchar_t *ent;
+} entities[] = {
+ {L'&', L"&"},
+ {L'\"', L"""},
+/* {L'\'', L"""}, Emulate DC++ escaping by omitting this. */
+ {L'\0', NULL}
+};
+
+static wchar_t *escapexml(wchar_t *src)
+{
+ static wchar_t *buf = NULL;;
+ int ret, c;
+ wchar_t nbuf[32];
+ size_t bufsize, bufdata;
+ struct xmlent *ent;
+
+ if(buf != NULL)
+ free(buf);
+ buf = NULL;
+ bufsize = bufdata = 0;
+ for(; *src != L'\0'; src++)
+ {
+ c = wctob(*src);
+ if((c > 0) && (c < 32))
+ {
+ bufcat(buf, L"&#", 2);
+ ret = swprintf(nbuf, (sizeof(nbuf) / sizeof(*nbuf)), L"%i", c);
+ bufcat(buf, nbuf, ret);
+ addtobuf(buf, L';');
+ } else {
+ for(ent = entities; ent->ent != NULL; ent++)
+ {
+ if(ent->c == *src)
+ {
+ bufcat(buf, ent->ent, wcslen(ent->ent));
+ break;
+ }
+ }
+ if(ent->ent == NULL)
+ addtobuf(buf, *src);
+ }
+ }
+ addtobuf(buf, L'\0');
+ return(buf);
+}
+
+static void updatexmllist(void)
+{
+ int i, fd, lev;
+ FILE *fs;
+ char cidbuf[14], *namebuf;
+ char *hashbuf;
+ struct sharecache *node;
+
+ if(xmllistname != NULL)
+ {
+ unlink(xmllistname);
+ free(xmllistname);
+ }
+ xmllistname = sstrdup("/tmp/dc-filelist-dcxml-XXXXXX");
+ if((fd = mkstemp(xmllistname)) < 0)
+ {
+ flog(LOG_WARNING, "could not create XML file list tempfile: %s", strerror(errno));
+ free(xmllistname);
+ xmllistname = NULL;
+ return;
+ }
+ if((fs = fdopen(fd, "w")) == NULL)
+ {
+ flog(LOG_WARNING, "could not fdopen XML list fd %i: %s", fd, strerror(errno));
+ unlink(xmllistname);
+ free(xmllistname);
+ xmllistname = NULL;
+ close(fd);
+ return;
+ }
+ fprintf(fs, "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\r\n");
+ for(i = 0; i < sizeof(cidbuf) - 1; i++)
+ cidbuf[i] = (rand() % ('Z' - 'A' + 1)) + 'A';
+ cidbuf[i] = 0;
+ fprintf(fs, "<FileListing Version=\"1\" CID=\"%s\" Base=\"/\" Generator=\"%s\">\r\n", cidbuf, DCIDFULL);
+
+ node = shareroot->child;
+ lev = 0;
+ while(1)
+ {
+ if((namebuf = icswcstombs(escapexml(node->name), "UTF-8", NULL)) != NULL)
+ {
+ for(i = 0; i < lev; i++)
+ fputc('\t', fs);
+ if(node->child != NULL)
+ {
+ fprintf(fs, "<Directory Name=\"%s\">\r\n", namebuf);
+ node = node->child;
+ lev++;
+ continue;
+ } else {
+ fprintf(fs, "<File Name=\"%s\" Size=\"%i\"", namebuf, node->size);
+ if(node->f.b.hastth)
+ {
+ hashbuf = base32encode(node->hashtth, 24);
+ fprintf(fs, " TTH=\"%.39s\"", hashbuf);
+ free(hashbuf);
+ }
+ fprintf(fs, "/>\r\n");
+ }
+ while(node->next == NULL)
+ {
+ node = node->parent;
+ if(node == shareroot)
+ {
+ break;
+ } else {
+ lev--;
+ for(i = 0; i < lev; i++)
+ fputc('\t', fs);
+ fprintf(fs, "</Directory>\r\n");
+ }
+ }
+ if(node == shareroot)
+ break;
+ node = node->next;
+ }
+ }
+
+#ifdef DCPP_MASQUERADE
+ fprintf(fs, "</FileListing>");
+#else
+ fprintf(fs, "</FileListing>\r\n");
+#endif
+ fclose(fs);
+}
+
+static void updatexmlbz2list(void)
+{
+ int err, fd;
+ FILE *in;
+ FILE *real;
+ BZFILE *out;
+ char buf[1024];
+ size_t bufdata;
+
+ if(xmllistname == NULL)
+ return;
+ if((in = fopen(xmllistname, "r")) == NULL)
+ {
+ flog(LOG_WARNING, "could not open XML file list for bzipping: %s", strerror(errno));
+ return;
+ }
+ if(xmlbz2listname != NULL)
+ {
+ unlink(xmlbz2listname);
+ free(xmlbz2listname);
+ }
+ xmlbz2listname = sstrdup("/tmp/dc-filelist-dcxmlbz2-XXXXXX");
+ if((fd = mkstemp(xmlbz2listname)) < 0)
+ {
+ flog(LOG_WARNING, "could not create bzipped XML file list tempfile: %s", strerror(errno));
+ free(xmlbz2listname);
+ xmlbz2listname = NULL;
+ fclose(in);
+ return;
+ }
+ if((real = fdopen(fd, "w")) == NULL)
+ {
+ flog(LOG_WARNING, "could not fdopen bzipped XML list fd %i: %s", fd, strerror(errno));
+ close(fd);
+ unlink(xmlbz2listname);
+ free(xmlbz2listname);
+ xmlbz2listname = NULL;
+ fclose(in);
+ return;
+ }
+ out = BZ2_bzWriteOpen(&err, real, 9, 0, 0);
+ if(err != BZ_OK)
+ {
+ flog(LOG_WARNING, "could not open bzip2 stream from XML list");
+ fclose(real);
+ unlink(xmlbz2listname);
+ free(xmlbz2listname);
+ xmlbz2listname = NULL;
+ fclose(in);
+ return;
+ }
+ while(!feof(in))
+ {
+ bufdata = fread(buf, 1, sizeof(buf), in);
+ BZ2_bzWrite(&err, out, buf, bufdata);
+ }
+ fclose(in);
+ BZ2_bzWriteClose(&err, out, 0, NULL, NULL);
+ fclose(real);
+}
+
+static int shareupdate(unsigned long long uusharesize, void *data)
+{
+ updatehmlist();
+ updatexmllist();
+ updatexmlbz2list();
+ return(0);
+}
+
+static void dispatchcommand(struct qcommand *qcmd, struct command *cmdlist, struct socket *sk, void *data)
+{
+ char *p;
+ struct command *cmd;
+
+ if((p = strchr(qcmd->string, ' ')) != NULL)
+ *(p++) = 0;
+ for(cmd = cmdlist; cmd->handler != NULL; cmd++)
+ {
+ if(!strcmp(cmd->name, qcmd->string))
+ break;
+ }
+ if(cmd->handler != NULL)
+ cmd->handler(sk, data, qcmd->string, p);
+/*
+ else
+ flog(LOG_DEBUG, "Unimplemented DC command: %s \"%s\"", qcmd->string, p?p:"noargs");
+*/
+}
+
+static int run(void)
+{
+ struct fnetnode *fn, *nextfn;
+ struct dchub *hub;
+ struct dcpeer *peer, *nextpeer;
+ struct qcommand *qcmd;
+ int ret;
+
+ ret = 0;
+ for(fn = fnetnodes; fn != NULL; fn = nextfn)
+ {
+ nextfn = fn->next;
+ if(fn->fnet != &dcnet)
+ continue;
+ if(fn->data == NULL)
+ continue;
+ hub = (struct dchub *)fn->data;
+ if((qcmd = ulqcmd(&hub->queue)) != NULL)
+ {
+ if(*qcmd->string == '$')
+ {
+ if((fn->sk != NULL) && (fn->sk->state == SOCK_EST))
+ dispatchcommand(qcmd, hubcmds, fn->sk, fn);
+ } else if(*qcmd->string != 0) {
+ hubrecvchat(fn->sk, fn, NULL, qcmd->string);
+ }
+ freeqcmd(qcmd);
+ ret = 1;
+ break;
+ }
+ }
+ for(peer = peers; peer != NULL; peer = nextpeer)
+ {
+ nextpeer = peer->next;
+ if((qcmd = ulqcmd(&peer->queue)) != NULL)
+ {
+ if(*qcmd->string == '$')
+ dispatchcommand(qcmd, peercmds, peer->sk, peer);
+ freeqcmd(qcmd);
+ ret = 1;
+ break;
+ }
+ }
+ return(ret);
+}
+
+static void preinit(int hup)
+{
+ if(hup)
+ return;
+ regfnet(&dcnet);
+}
+
+static int updateudpport(struct configvar *var, void *uudata)
+{
+ struct sockaddr_in addr;
+ struct socket *newsock;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(var->val.num);
+ if((newsock = netcsdgram((struct sockaddr *)&addr, sizeof(addr))) == NULL)
+ {
+ flog(LOG_WARNING, "could not create new DC UDP socket, reverting to old: %s", strerror(errno));
+ return(0);
+ }
+ newsock->readcb = udpread;
+ if(udpsock != NULL)
+ putsock(udpsock);
+ udpsock = newsock;
+ return(0);
+}
+
+static int updatetcpport(struct configvar *var, void *uudata)
+{
+ struct sockaddr_in addr;
+ struct socket *newsock;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(var->val.num);
+ if((newsock = netcslisten(SOCK_STREAM, (struct sockaddr *)&addr, sizeof(addr), peeraccept, NULL)) == NULL)
+ flog(LOG_INFO, "could not listen to a remote address, going into passive mode");
+ if(tcpsock != NULL)
+ putsock(tcpsock);
+ tcpsock = newsock;
+ return(0);
+}
+
+static int init(int hup)
+{
+ struct sockaddr_in addr;
+
+ if(!hup)
+ {
+ GCBREG(sharechangecb, shareupdate, NULL);
+ if(udpsock != NULL)
+ putsock(udpsock);
+ if(tcpsock != NULL)
+ putsock(tcpsock);
+ addr.sin_family = AF_INET;
+ memset(&addr.sin_addr, 0, sizeof(addr.sin_addr));
+ addr.sin_port = htons(confgetint("dc", "udpport"));
+ if((udpsock = netcsdgram((struct sockaddr *)&addr, sizeof(addr))) == NULL)
+ {
+ flog(LOG_CRIT, "could not create DC UDP socket: %s", strerror(errno));
+ return(1);
+ }
+ udpsock->readcb = udpread;
+ addr.sin_port = htons(confgetint("dc", "tcpport"));
+ if((tcpsock = netcslisten(SOCK_STREAM, (struct sockaddr *)&addr, sizeof(addr), peeraccept, NULL)) == NULL)
+ flog(LOG_INFO, "could not listen to a remote address, going into passive mode");
+ CBREG(confgetvar("dc", "udpport"), conf_update, updateudpport, NULL, NULL);
+ CBREG(confgetvar("dc", "tcpport"), conf_update, updatetcpport, NULL, NULL);
+ CBREG(confgetvar("net", "mode"), conf_update, updatetcpport, NULL, NULL);
+ }
+ return(0);
+}
+
+static void terminate(void)
+{
+ if(hmlistname != NULL)
+ {
+ unlink(hmlistname);
+ free(hmlistname);
+ }
+ if(xmllistname != NULL)
+ {
+ unlink(xmllistname);
+ free(xmllistname);
+ }
+ if(xmlbz2listname != NULL)
+ {
+ unlink(xmlbz2listname);
+ free(xmlbz2listname);
+ }
+}
+
+static struct configvar myvars[] =
+{
+ {CONF_VAR_STRING, "desc", {.str = L""}},
+ {CONF_VAR_STRING, "speedstring", {.str = L"LAN(T1)"}},
+ {CONF_VAR_STRING, "email", {.str = L"spam@spam.org"}},
+ {CONF_VAR_INT, "udpport", {.num = 0}},
+ {CONF_VAR_INT, "tcpport", {.num = 0}},
+ {CONF_VAR_END}
+};
+
+static struct module me =
+{
+ .conf =
+ {
+ .vars = myvars
+ },
+ .preinit = preinit,
+ .init = init,
+ .run = run,
+ .terminate = terminate,
+ .name = "dc"
+};
+
+MODULE(me)
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#include <syslog.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/uio.h>
+#include <string.h>
+#include <malloc.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "utils.h"
+
+int logtostderr, logtosyslog;
+int syslogfac = LOG_DAEMON;
+static int inited = 0;
+
+void flog(int priority, char *format, ...)
+{
+ va_list args;
+ char *b;
+ struct iovec iov[2];
+
+ va_start(args, format);
+ if((b = vsprintf2(format, args)) == NULL)
+ {
+ if(logtostderr)
+ fputs("No memory available for logging\n", stderr);
+ if(logtosyslog)
+ syslog(LOG_CRIT, "No memory available for logging");
+ }
+ va_end(args);
+ if(logtostderr)
+ {
+ iov[0].iov_base = b;
+ iov[0].iov_len = strlen(b);
+ iov[1].iov_base = "\n";
+ iov[1].iov_len = 1;
+ writev(2, iov, 2);
+ }
+ if(logtosyslog)
+ syslog(priority, "%s", b);
+ free(b);
+}
+
+void initlog(void)
+{
+ if(inited)
+ {
+ closelog();
+ openlog("doldacond", LOG_PID, syslogfac);
+ } else {
+ openlog("doldacond", LOG_PID, syslogfac);
+ logtostderr = 1;
+ logtosyslog = 0;
+ inited = 1;
+ }
+}
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#ifndef _LOG_H
+#define _LOG_H
+
+#include <syslog.h>
+
+#define LOGOOM(size) flog(LOG_CRIT, "%s (%s:%i): out of memory (alloc %i)", __FUNCTION__, __FILE__, __LINE__, (size))
+
+extern int logtostderr, logtosyslog;
+extern int syslogfac;
+
+void flog(int priority, char *format, ...)
+#ifdef __GNUC__
+ __attribute__ ((format (printf, 2, 3)))
+#endif
+;
+void initlog(void);
+
+#endif
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <locale.h>
+#include <signal.h>
+#include <getopt.h>
+#include <time.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/wait.h>
+#include <stdarg.h>
+#include <fcntl.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "utils.h"
+#include "log.h"
+#include "conf.h"
+#include "module.h"
+#include "net.h"
+#include "client.h"
+#include "sysevents.h"
+#include "auth.h"
+
+struct module *modchain = NULL;
+static struct timer *timers = NULL;
+static struct child *children = NULL;
+volatile int running;
+volatile int reinit;
+static volatile int childrendone = 0;
+
+struct timer *timercallback(double at, void (*func)(int, void *), void *data)
+{
+ struct timer *new;
+
+ new = smalloc(sizeof(*new));
+ new->at = at;
+ new->func = func;
+ new->data = data;
+ new->next = timers;
+ new->prev = NULL;
+ if(timers != NULL)
+ timers->prev = new;
+ timers = new;
+ return(new);
+}
+
+void canceltimer(struct timer *timer)
+{
+ if(timer->next != NULL)
+ timer->next->prev = timer->prev;
+ if(timer->prev != NULL)
+ timer->prev->next = timer->next;
+ if(timer == timers)
+ timers = timer->next;
+ timer->func(1, timer->data);
+ free(timer);
+}
+
+void childcallback(pid_t pid, void (*func)(pid_t, int, void *), void *data)
+{
+ struct child *new;
+
+ new = smalloc(sizeof(*new));
+ new->pid = pid;
+ new->callback = func;
+ new->data = data;
+ new->finished = 0;
+ new->prev = NULL;
+ new->next = children;
+ if(children != NULL)
+ children->prev = new;
+ children = new;
+}
+
+static void preinit(int hup)
+{
+ struct module *mod;
+
+ for(mod = modchain; mod != NULL; mod = mod->next)
+ {
+ if(mod->preinit)
+ mod->preinit(hup);
+ if(!hup && ((mod->conf.vars != NULL) || (mod->conf.cmds != NULL)))
+ confregmod(&mod->conf);
+ }
+}
+
+static void init(int hup)
+{
+ struct module *mod;
+
+ for(mod = modchain; mod != NULL; mod = mod->next)
+ {
+ if(mod->init && mod->init(hup))
+ {
+ flog(LOG_CRIT, "initialization of \"%s\" failed", mod->name);
+ exit(1);
+ }
+ }
+}
+
+static void terminate(void)
+{
+ struct module *mod;
+
+ for(mod = modchain; mod != NULL; mod = mod->next)
+ {
+ if(mod->terminate)
+ mod->terminate();
+ }
+}
+
+static void handler(int signum)
+{
+ pid_t pid;
+ int status;
+ struct child *child;
+ FILE *dumpfile;
+ extern int numfnetnodes, numtransfers, numdcpeers;
+
+ switch(signum)
+ {
+ case SIGHUP:
+ reinit = 1;
+ break;
+ case SIGINT:
+ case SIGTERM:
+ running = 0;
+ break;
+ case SIGCHLD:
+ while((pid = waitpid(-1, &status, WNOHANG)) > 0)
+ {
+ for(child = children; child != NULL; child = child->next)
+ {
+ if(child->pid == pid)
+ {
+ child->finished = 1;
+ child->status = status;
+ }
+ }
+ childrendone = 1;
+ }
+ break;
+ case SIGUSR1:
+ flog(LOG_NOTICE, "forking and dumping core upon SIGUSR1");
+ if(fork() == 0)
+ abort();
+ break;
+ case SIGUSR2:
+ flog(LOG_NOTICE, "dumping memstats to /tmp/dc-mem upon SIGUSR2");
+ if((dumpfile = fopen("/tmp/dc-mem", "w")) == NULL) {
+ flog(LOG_ERR, "could not dump stats: %s", strerror(errno));
+ break;
+ }
+ fprintf(dumpfile, "%i %i %i\n", numfnetnodes, numtransfers, numdcpeers);
+ fclose(dumpfile);
+ break;
+ }
+}
+
+pid_t forksess(uid_t user, struct authhandle *auth, void (*ccbfunc)(pid_t, int, void *), void *data, ...)
+{
+ int i, o;
+ int cpipe[2];
+ struct
+ {
+ int tfd;
+ int fd;
+ } *files;
+ int maxfd, type, numfiles;
+ int acc;
+ int *ibuf;
+ struct passwd *pwent;
+ pid_t pid;
+ char *buf;
+ va_list args;
+ sigset_t sigset;
+ int ret, status;
+
+ if((pwent = getpwuid(user)) == NULL)
+ {
+ flog(LOG_WARNING, "no passwd entry for uid %i, cannot fork session", user);
+ errno = EACCES;
+ return(-1);
+ }
+ if((geteuid() != 0) && (user != geteuid()))
+ {
+ flog(LOG_WARNING, "cannot fork non-owning session when not running as root (EUID is %i, target UID is %i)", geteuid(), user);
+ errno = EPERM;
+ return(-1);
+ }
+ va_start(args, data);
+ numfiles = 0;
+ files = NULL;
+ maxfd = 0;
+ while((type = va_arg(args, int)) != FD_END)
+ {
+ files = srealloc(files, sizeof(*files) * (numfiles + 1));
+ files[numfiles].fd = va_arg(args, int);
+ if(files[numfiles].fd > maxfd)
+ maxfd = files[numfiles].fd;
+ acc = va_arg(args, int);
+ if(type == FD_PIPE)
+ {
+ if(pipe(cpipe) < 0)
+ {
+ flog(LOG_CRIT, "could not create pipe(!): %s", strerror(errno));
+ for(i = 0; i < numfiles; i++)
+ close(files[i].tfd);
+ return(-1);
+ }
+ ibuf = va_arg(args, int *);
+ if(acc == O_WRONLY)
+ {
+ *ibuf = cpipe[1];
+ files[numfiles].tfd = cpipe[0];
+ } else {
+ *ibuf = cpipe[0];
+ files[numfiles].tfd = cpipe[1];
+ }
+ } else if(type == FD_FILE) {
+ buf = va_arg(args, char *);
+ if((files[numfiles].tfd = open(buf, acc)) < 0)
+ {
+ flog(LOG_CRIT, "could not open file \"%s\": %s", buf, strerror(errno));
+ for(i = 0; i < numfiles; i++)
+ close(files[i].tfd);
+ return(-1);
+ }
+ }
+ if(files[numfiles].tfd > maxfd)
+ maxfd = files[numfiles].tfd;
+ numfiles++;
+ }
+ va_end(args);
+ sigemptyset(&sigset);
+ sigaddset(&sigset, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &sigset, NULL);
+ if((pid = fork()) < 0)
+ {
+ flog(LOG_WARNING, "could not fork(!) in forksess(): %s", strerror(errno));
+ for(i = 0; i < numfiles; i++)
+ close(files[i].tfd);
+ sigprocmask(SIG_UNBLOCK, &sigset, NULL);
+ }
+ if(pid == 0)
+ {
+ sigprocmask(SIG_UNBLOCK, &sigset, NULL);
+ signal(SIGPIPE, SIG_DFL);
+ signal(SIGCHLD, SIG_DFL);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGHUP, SIG_DFL);
+ for(i = 0; i < numfiles; i++)
+ {
+ if(dup2(files[i].tfd, maxfd + i + 1) < 0)
+ exit(127);
+ files[i].tfd = maxfd + i + 1;
+ }
+ for(i = 0; i < numfiles; i++)
+ {
+ if(dup2(files[i].tfd, files[i].fd) < 0)
+ exit(127);
+ }
+ initlog();
+ for(i = 0; i < FD_SETSIZE; i++)
+ {
+ if(i <= maxfd)
+ {
+ for(o = 0; o < numfiles; o++)
+ {
+ if(i == files[o].fd)
+ break;
+ }
+ if(o == numfiles)
+ close(i);
+ } else {
+ close(i);
+ }
+ }
+ setpgrp();
+ signal(SIGHUP, SIG_IGN);
+ errno = 0;
+ if((authopensess(auth)) != AUTH_SUCCESS)
+ {
+ flog(LOG_WARNING, "could not open session for user %s: %s", pwent->pw_name, (errno == 0)?"Unknown error - should be logged above":strerror(errno));
+ exit(127);
+ }
+ if((pid = fork()) < 0)
+ {
+ authclosesess(auth);
+ exit(127);
+ }
+ if(pid == 0)
+ {
+ if(geteuid() == 0)
+ {
+ if(initgroups(pwent->pw_name, pwent->pw_gid))
+ {
+ flog(LOG_WARNING, "could not initgroups: %s", strerror(errno));
+ exit(127);
+ }
+ if(setgid(pwent->pw_gid))
+ {
+ flog(LOG_WARNING, "could not setgid: %s", strerror(errno));
+ exit(127);
+ }
+ if(setuid(pwent->pw_uid))
+ {
+ flog(LOG_WARNING, "could not setuid: %s", strerror(errno));
+ exit(127);
+ }
+ }
+ putenv(sprintf2("HOME=%s", pwent->pw_dir));
+ putenv(sprintf2("SHELL=%s", pwent->pw_shell));
+ putenv(sprintf2("USER=%s", pwent->pw_name));
+ putenv(sprintf2("LOGNAME=%s", pwent->pw_name));
+ putenv(sprintf2("PATH=%s/bin:/usr/local/bin:/bin:/usr/bin", pwent->pw_dir));
+ chdir(pwent->pw_dir);
+ return(0);
+ }
+ for(i = 0; i < numfiles; i++)
+ close(files[i].fd);
+ while(((ret = waitpid(pid, &status, 0)) != pid) && (ret >= 0));
+ authclosesess(auth);
+ if(ret < 0)
+ {
+ flog(LOG_WARNING, "waitpid(%i) said \"%s\"", pid, strerror(errno));
+ exit(127);
+ }
+ if(!WIFEXITED(status))
+ exit(127);
+ exit(WEXITSTATUS(status));
+ }
+ for(i = 0; i < numfiles; i++)
+ close(files[i].tfd);
+ if(files != NULL)
+ free(files);
+ if(ccbfunc != NULL)
+ childcallback(pid, ccbfunc, data);
+ sigprocmask(SIG_UNBLOCK, &sigset, NULL);
+ return(pid);
+}
+
+int main(int argc, char **argv)
+{
+ int c;
+ int nofork;
+ char *configfile;
+ char *pidfile;
+ FILE *pfstream, *confstream;
+ int delay;
+ struct module *mod;
+ struct timer *timer, *ntimer;
+ struct child *child;
+ double now;
+
+ nofork = 0;
+ syslogfac = LOG_DAEMON;
+ configfile = NULL;
+ pidfile = NULL;
+ while((c = getopt(argc, argv, "p:C:f:hn")) != -1)
+ {
+ switch(c)
+ {
+ case 'p':
+ pidfile = optarg;
+ break;
+ case 'C':
+ configfile = optarg;
+ break;
+ case 'f':
+ if(!strcmp(optarg, "auth"))
+ syslogfac = LOG_AUTH;
+ else if(!strcmp(optarg, "authpriv"))
+ syslogfac = LOG_AUTHPRIV;
+ else if(!strcmp(optarg, "cron"))
+ syslogfac = LOG_CRON;
+ else if(!strcmp(optarg, "daemon"))
+ syslogfac = LOG_DAEMON;
+ else if(!strcmp(optarg, "ftp"))
+ syslogfac = LOG_FTP;
+ else if(!strcmp(optarg, "kern"))
+ syslogfac = LOG_KERN;
+ else if(!strcmp(optarg, "lpr"))
+ syslogfac = LOG_LPR;
+ else if(!strcmp(optarg, "mail"))
+ syslogfac = LOG_MAIL;
+ else if(!strcmp(optarg, "news"))
+ syslogfac = LOG_NEWS;
+ else if(!strcmp(optarg, "syslog"))
+ syslogfac = LOG_SYSLOG;
+ else if(!strcmp(optarg, "user"))
+ syslogfac = LOG_USER;
+ else if(!strcmp(optarg, "uucp"))
+ syslogfac = LOG_UUCP;
+ else if(!strncmp(optarg, "local", 5) && (strlen(optarg) == 6))
+ syslogfac = LOG_LOCAL0 + (optarg[5] - '0');
+ else
+ fprintf(stderr, "unknown syslog facility %s, using daemon\n", optarg);
+ break;
+ case 'n':
+ nofork = 1;
+ break;
+ case 'h':
+ case ':':
+ case '?':
+ default:
+ printf("usage: doldacond [-hn] [-C configfile] [-p pidfile] [-f facility]\n");
+ exit(c != 'h');
+ }
+ }
+ setlocale(LC_ALL, "");
+ initlog();
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGHUP, handler);
+ signal(SIGINT, handler);
+ signal(SIGTERM, handler);
+ signal(SIGCHLD, handler);
+ signal(SIGUSR1, handler);
+ signal(SIGUSR2, handler);
+ preinit(0);
+ if(configfile == NULL)
+ {
+ if((configfile = findconfigfile()) == NULL)
+ {
+ flog(LOG_CRIT, "could not find a configuration file");
+ exit(1);
+ }
+ }
+ pfstream = NULL;
+ if(pidfile != NULL)
+ {
+ if((pfstream = fopen(pidfile, "w")) == NULL)
+ {
+ flog(LOG_CRIT, "could not open specified PID file %s: %s", pidfile, strerror(errno));
+ exit(1);
+ }
+ }
+ if((confstream = fopen(configfile, "r")) == NULL)
+ {
+ flog(LOG_CRIT, "could not open configuration file %s: %s", configfile, strerror(errno));
+ exit(1);
+ }
+ readconfig(confstream);
+ fclose(confstream);
+ init(0);
+ if(!nofork)
+ {
+ logtosyslog = 1;
+ daemon(0, 0);
+ flog(LOG_INFO, "daemonized");
+ logtostderr = 0;
+ }
+ if(pfstream != NULL) {
+ fprintf(pfstream, "%i\n", getpid());
+ fclose(pfstream);
+ }
+ running = 1;
+ reinit = 0;
+ while(running)
+ {
+ if(reinit)
+ {
+ if((confstream = fopen(configfile, "r")) == NULL)
+ {
+ flog(LOG_ERR, "could not open configuration file %s: %s (ignoring HUP)", configfile, strerror(errno));
+ } else {
+ preinit(1);
+ readconfig(confstream);
+ fclose(confstream);
+ init(1);
+ }
+ reinit = 0;
+ }
+ delay = 1000; /* -1; */
+ for(mod = modchain; mod != NULL; mod = mod->next)
+ {
+ if(mod->run && mod->run())
+ delay = 0;
+ }
+ if(!running)
+ delay = 0;
+ if(delay != 0)
+ {
+ now = ntime();
+ for(timer = timers; timer != NULL; timer = timer->next)
+ {
+ if((delay == -1) || ((int)((timer->at - now) * 1000.0) < delay))
+ delay = (int)((timer->at - now) * 1000.0);
+ }
+ }
+ if(childrendone)
+ {
+ delay = 0;
+ childrendone = 0;
+ }
+ pollsocks(delay);
+ now = ntime();
+ for(timer = timers; timer != NULL; timer = ntimer)
+ {
+ ntimer = timer->next;
+ if(now < timer->at)
+ continue;
+ if(timer->prev != NULL)
+ timer->prev->next = timer->next;
+ if(timer->next != NULL)
+ timer->next->prev = timer->prev;
+ if(timer == timers)
+ timers = timer->next;
+ timer->func(0, timer->data);
+ free(timer);
+ }
+ do
+ {
+ for(child = children; child != NULL; child = child->next)
+ {
+ if(child->finished)
+ {
+ child->callback(child->pid, child->status, child->data);
+ if(child == children)
+ children = child->next;
+ if(child->prev != NULL)
+ child->prev->next = child->next;
+ if(child->next != NULL)
+ child->next->prev = child->prev;
+ free(child);
+ break;
+ }
+ }
+ } while(child != NULL);
+ }
+ flog(LOG_INFO, "terminating...");
+ terminate();
+ return(0);
+}
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#ifndef _MODULE_H
+#define _MODULE_H
+
+#include <stdio.h>
+
+#include "conf.h"
+
+struct module
+{
+ struct module *next;
+ char *name;
+ /* Called before the configuration file is read. Must either
+ * succeed or call exit. Note that it is called both at startup
+ * and when SIGHUP has been received (then with hup = 1). */
+ void (*preinit)(int hup);
+ /* Called when the configuration file has been read. Return zero
+ * on success and non-zero on failure. Note, as with preinit, that
+ * it can be called both at startup and after SIHUP. */
+ int (*init)(int hup);
+ /* Called every "cycle". Its return value determines whether the
+ * module still has work to do, and thus determines whether the
+ * next pollsocks should block or not. Return non-zero whenever
+ * the module has more work to do. */
+ int (*run)(void);
+ /* Called when the daemon is shutting down. */
+ void (*terminate)(void);
+ struct configmod conf;
+};
+
+#define MODULE(mod) \
+static void __attribute__ ((constructor)) __regmod(void) \
+{ \
+ extern struct module *modchain; \
+ \
+ if(mod.name == NULL) \
+ { \
+ fprintf(stderr, "module at %p has no name", &mod); \
+ exit(1); \
+ } \
+ mod.conf.name = mod.name; \
+ mod.next = modchain; \
+ modchain = &mod; \
+}
+
+void regmod(struct module *);
+
+#endif
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+/* XXX: Implement SOCKS proxyability */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/poll.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <sys/signal.h>
+#include <printf.h>
+#ifdef HAVE_LINUX_SOCKIOS_H
+#include <linux/sockios.h>
+#endif
+#include <errno.h>
+#include <net/if.h>
+
+#include "conf.h"
+#include "net.h"
+#include "module.h"
+#include "log.h"
+#include "utils.h"
+#include "sysevents.h"
+
+static struct configvar myvars[] =
+{
+ /* 0 = Direct mode, 1 = Passive mode, 2 = SOCKS proxy */
+ {CONF_VAR_INT, "mode", {.num = 0}},
+ /* Only for direct mode */
+ {CONF_VAR_IPV4, "visibleipv4", {.ipv4 = {0}}},
+ {CONF_VAR_STRING, "publicif", {.str = L""}},
+ {CONF_VAR_END}
+};
+
+static struct socket *sockets = NULL;
+int numsocks = 0;
+
+/* XXX: Get autoconf for all this... */
+int getpublicaddr(int af, struct sockaddr **addr, socklen_t *lenbuf)
+{
+ struct sockaddr_in *ipv4;
+ struct configvar *var;
+ void *bufend;
+ int sock;
+ struct ifconf conf;
+ struct ifreq *ifr, req;
+ char *pif;
+
+ if(af == AF_INET)
+ {
+ var = confgetvar("net", "visibleipv4");
+ if(var->val.ipv4.s_addr != 0)
+ {
+ ipv4 = smalloc(sizeof(*ipv4));
+ ipv4->sin_family = AF_INET;
+ ipv4->sin_addr.s_addr = var->val.ipv4.s_addr;
+ *addr = (struct sockaddr *)ipv4;
+ *lenbuf = sizeof(*ipv4);
+ return(0);
+ }
+ if((pif = icwcstombs(confgetstr("net", "publicif"), NULL)) == NULL)
+ {
+ flog(LOG_ERR, "could not convert net.publicif into local charset: %s", strerror(errno));
+ return(-1);
+ }
+ if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
+ return(-1);
+ conf.ifc_buf = smalloc(conf.ifc_len = 65536);
+ if(ioctl(sock, SIOCGIFCONF, &conf) < 0)
+ {
+ free(conf.ifc_buf);
+ close(sock);
+ return(-1);
+ }
+ bufend = ((char *)conf.ifc_buf) + conf.ifc_len;
+ ipv4 = NULL;
+ for(ifr = conf.ifc_ifcu.ifcu_req; (void *)ifr < bufend; ifr++)
+ {
+ memset(&req, 0, sizeof(req));
+ memcpy(req.ifr_name, ifr->ifr_name, sizeof(ifr->ifr_name));
+ if(ioctl(sock, SIOCGIFFLAGS, &req) < 0)
+ {
+ free(conf.ifc_buf);
+ close(sock);
+ return(-1);
+ }
+ if(!(req.ifr_flags & IFF_UP))
+ continue;
+ if(ifr->ifr_addr.sa_family == AF_INET)
+ {
+ if(ntohl(((struct sockaddr_in *)&ifr->ifr_addr)->sin_addr.s_addr) == 0x7f000001)
+ continue;
+ if(ipv4 == NULL)
+ {
+ ipv4 = smalloc(sizeof(*ipv4));
+ memcpy(ipv4, &ifr->ifr_addr, sizeof(ifr->ifr_addr));
+ } else {
+ free(ipv4);
+ flog(LOG_WARNING, "could not locate an unambiguous interface for determining your public IP address - set net.publicif");
+ errno = ENFILE; /* XXX: There's no appropriate one for this... */
+ return(-1);
+ }
+ }
+ }
+ close(sock);
+ if(ipv4 != NULL)
+ {
+ *addr = (struct sockaddr *)ipv4;
+ *lenbuf = sizeof(*ipv4);
+ return(0);
+ }
+ errno = ENETDOWN;
+ return(-1);
+ }
+ errno = EPFNOSUPPORT;
+ return(-1);
+}
+
+static struct socket *newsock(int type)
+{
+ struct socket *new;
+
+ new = smalloc(sizeof(*new));
+ new->refcount = 2;
+ new->fd = -1;
+ new->isrealsocket = 1;
+ new->family = -1;
+ new->tos = 0;
+ new->type = type;
+ new->state = -1;
+ new->ignread = 0;
+ new->close = 0;
+ new->remote = NULL;
+ new->remotelen = 0;
+ switch(type)
+ {
+ case SOCK_STREAM:
+ new->outbuf.s.buf = NULL;
+ new->outbuf.s.bufsize = 0;
+ new->outbuf.s.datasize = 0;
+ new->inbuf.s.buf = NULL;
+ new->inbuf.s.bufsize = 0;
+ new->inbuf.s.datasize = 0;
+ break;
+ case SOCK_DGRAM:
+ new->outbuf.d.f = new->outbuf.d.l = NULL;
+ new->inbuf.d.f = new->inbuf.d.l = NULL;
+ break;
+ }
+ new->conncb = NULL;
+ new->errcb = NULL;
+ new->readcb = NULL;
+ new->writecb = NULL;
+ new->acceptcb = NULL;
+ new->next = sockets;
+ new->prev = NULL;
+ if(sockets != NULL)
+ sockets->prev = new;
+ sockets = new;
+ numsocks++;
+ return(new);
+}
+
+static struct socket *mksock(int domain, int type)
+{
+ int fd;
+ struct socket *new;
+
+ if((fd = socket(domain, type, 0)) < 0)
+ {
+ flog(LOG_CRIT, "could not create socket: %s", strerror(errno));
+ return(NULL);
+ }
+ new = newsock(type);
+ new->fd = fd;
+ new->family = domain;
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
+ return(new);
+}
+
+struct socket *wrapsock(int fd)
+{
+ struct socket *new;
+
+ new = newsock(SOCK_STREAM);
+ new->fd = fd;
+ new->state = SOCK_EST;
+ new->isrealsocket = 0;
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
+ return(new);
+}
+
+static void unlinksock(struct socket *sk)
+{
+ if(sk->prev != NULL)
+ sk->prev->next = sk->next;
+ if(sk->next != NULL)
+ sk->next->prev = sk->prev;
+ if(sk == sockets)
+ sockets = sk->next;
+ putsock(sk);
+ numsocks--;
+}
+
+void getsock(struct socket *sk)
+{
+ sk->refcount++;
+}
+
+void putsock(struct socket *sk)
+{
+ struct dgrambuf *buf;
+
+ if(--(sk->refcount) == 0)
+ {
+ switch(sk->type)
+ {
+ case SOCK_STREAM:
+ if(sk->outbuf.s.buf != NULL)
+ free(sk->outbuf.s.buf);
+ if(sk->inbuf.s.buf != NULL)
+ free(sk->inbuf.s.buf);
+ break;
+ case SOCK_DGRAM:
+ while((buf = sk->outbuf.d.f) != NULL)
+ {
+ sk->outbuf.d.f = buf->next;
+ free(buf->data);
+ free(buf);
+ }
+ while((buf = sk->inbuf.d.f) != NULL)
+ {
+ sk->inbuf.d.f = buf->next;
+ free(buf->data);
+ free(buf);
+ }
+ break;
+ }
+ if(sk->fd >= 0)
+ close(sk->fd);
+ if(sk->remote != NULL)
+ free(sk->remote);
+ free(sk);
+ }
+}
+
+void *sockgetinbuf(struct socket *sk, size_t *size)
+{
+ void *buf;
+ struct dgrambuf *dbuf;
+
+ switch(sk->type)
+ {
+ case SOCK_STREAM:
+ if((sk->inbuf.s.buf == NULL) || (sk->inbuf.s.datasize == 0))
+ {
+ *size = 0;
+ return(NULL);
+ }
+ buf = sk->inbuf.s.buf;
+ *size = sk->inbuf.s.datasize;
+ sk->inbuf.s.buf = NULL;
+ sk->inbuf.s.bufsize = sk->inbuf.s.datasize = 0;
+ return(buf);
+ case SOCK_DGRAM:
+ if((dbuf = sk->inbuf.d.f) == NULL)
+ return(NULL);
+ sk->inbuf.d.f = dbuf->next;
+ if(dbuf->next == NULL)
+ sk->inbuf.d.l = NULL;
+ buf = dbuf->data;
+ *size = dbuf->size;
+ free(dbuf->addr);
+ free(dbuf);
+ return(buf);
+ }
+ return(NULL);
+}
+
+static void sockrecv(struct socket *sk)
+{
+ int ret, inq;
+ struct dgrambuf *dbuf;
+
+ switch(sk->type)
+ {
+ case SOCK_STREAM:
+#if defined(HAVE_LINUX_SOCKIOS_H) && defined(SIOCINQ)
+ /* SIOCINQ is Linux-specific AFAIK, but I really have no idea
+ * how to read the inqueue size on other OSs */
+ if(ioctl(sk->fd, SIOCINQ, &inq))
+ {
+ /* I don't really know what could go wrong here, so let's
+ * assume it's transient. */
+ flog(LOG_WARNING, "SIOCINQ return %s on socket %i, falling back to 2048 bytes", strerror(errno), sk->fd);
+ inq = 2048;
+ }
+#else
+ inq = 2048;
+#endif
+ if(inq > 65536)
+ inq = 65536;
+ sizebuf(&sk->inbuf.s.buf, &sk->inbuf.s.bufsize, sk->inbuf.s.datasize + inq, 1, 1);
+ ret = read(sk->fd, sk->inbuf.s.buf + sk->inbuf.s.datasize, inq);
+ if(ret < 0)
+ {
+ if((errno == EINTR) || (errno == EAGAIN))
+ return;
+ if(sk->errcb != NULL)
+ sk->errcb(sk, errno, sk->data);
+ closesock(sk);
+ return;
+ }
+ if(ret == 0)
+ {
+ if(sk->errcb != NULL)
+ sk->errcb(sk, 0, sk->data);
+ closesock(sk);
+ return;
+ }
+ sk->inbuf.s.datasize += ret;
+ if(sk->readcb != NULL)
+ sk->readcb(sk, sk->data);
+ break;
+ case SOCK_DGRAM:
+ if(ioctl(sk->fd, SIOCINQ, &inq))
+ {
+ /* I don't really know what could go wrong here, so let's
+ * assume it's transient. */
+ flog(LOG_WARNING, "SIOCINQ return %s on socket %i", strerror(errno), sk->fd);
+ return;
+ }
+ dbuf = smalloc(sizeof(*dbuf));
+ dbuf->data = smalloc(inq);
+ dbuf->addr = smalloc(dbuf->addrlen = sizeof(struct sockaddr_storage));
+ ret = recvfrom(sk->fd, dbuf->data, inq, 0, dbuf->addr, &dbuf->addrlen);
+ if(ret < 0)
+ {
+ free(dbuf->addr);
+ free(dbuf->data);
+ free(dbuf);
+ if((errno == EINTR) || (errno == EAGAIN))
+ return;
+ if(sk->errcb != NULL)
+ sk->errcb(sk, errno, sk->data);
+ closesock(sk);
+ return;
+ }
+ /* On UDP/IPv[46], ret == 0 doesn't mean EOF (since UDP can't
+ * have EOF), but rather an empty packet. I don't know if any
+ * other potential DGRAM protocols might have an EOF
+ * condition, so let's play safe. */
+ if(ret == 0)
+ {
+ free(dbuf->addr);
+ free(dbuf->data);
+ free(dbuf);
+ if(!((sk->family == AF_INET) || (sk->family == AF_INET6)))
+ {
+ if(sk->errcb != NULL)
+ sk->errcb(sk, 0, sk->data);
+ closesock(sk);
+ }
+ return;
+ }
+ dbuf->addr = srealloc(dbuf->addr, dbuf->addrlen);
+ dbuf->data = srealloc(dbuf->data, dbuf->size = ret);
+ dbuf->next = NULL;
+ if(sk->inbuf.d.l != NULL)
+ sk->inbuf.d.l->next = dbuf;
+ else
+ sk->inbuf.d.f = dbuf;
+ sk->inbuf.d.l = dbuf;
+ if(sk->readcb != NULL)
+ sk->readcb(sk, sk->data);
+ break;
+ }
+}
+
+static void sockflush(struct socket *sk)
+{
+ int ret;
+ struct dgrambuf *dbuf;
+
+ switch(sk->type)
+ {
+ case SOCK_STREAM:
+ if(sk->isrealsocket)
+ ret = send(sk->fd, sk->outbuf.s.buf, sk->outbuf.s.datasize, MSG_DONTWAIT | MSG_NOSIGNAL);
+ else
+ ret = write(sk->fd, sk->outbuf.s.buf, sk->outbuf.s.datasize);
+ if(ret < 0)
+ {
+ /* For now, assume transient error, since
+ * the socket is polled for errors */
+ break;
+ }
+ if(ret > 0)
+ {
+ memmove(sk->outbuf.s.buf, ((char *)sk->outbuf.s.buf) + ret, sk->outbuf.s.datasize -= ret);
+ if(sk->writecb != NULL)
+ sk->writecb(sk, sk->data);
+ }
+ break;
+ case SOCK_DGRAM:
+ dbuf = sk->outbuf.d.f;
+ if((sk->outbuf.d.f = dbuf->next) == NULL)
+ sk->outbuf.d.l = NULL;
+ sendto(sk->fd, dbuf->data, dbuf->size, MSG_DONTWAIT | MSG_NOSIGNAL, dbuf->addr, dbuf->addrlen);
+ free(dbuf->data);
+ free(dbuf->addr);
+ free(dbuf);
+ if(sk->writecb != NULL)
+ sk->writecb(sk, sk->data);
+ break;
+ }
+}
+
+void closesock(struct socket *sk)
+{
+ sk->state = SOCK_STL;
+ close(sk->fd);
+ sk->fd = -1;
+ sk->close = 0;
+}
+
+void sockqueue(struct socket *sk, void *data, size_t size)
+{
+ struct dgrambuf *new;
+
+ if(sk->state == SOCK_STL)
+ return;
+ switch(sk->type)
+ {
+ case SOCK_STREAM:
+ sizebuf(&(sk->outbuf.s.buf), &(sk->outbuf.s.bufsize), sk->outbuf.s.datasize + size, 1, 1);
+ memcpy(sk->outbuf.s.buf + sk->outbuf.s.datasize, data, size);
+ sk->outbuf.s.datasize += size;
+ break;
+ case SOCK_DGRAM:
+ if(sk->remote == NULL)
+ return;
+ new = smalloc(sizeof(*new));
+ new->next = NULL;
+ memcpy(new->data = smalloc(size), data, new->size = size);
+ memcpy(new->addr = smalloc(sk->remotelen), sk->remote, new->addrlen = sk->remotelen);
+ if(sk->outbuf.d.l == NULL)
+ {
+ sk->outbuf.d.l = sk->outbuf.d.f = new;
+ } else {
+ sk->outbuf.d.l->next = new;
+ sk->outbuf.d.l = new;
+ }
+ break;
+ }
+}
+
+size_t sockgetdatalen(struct socket *sk)
+{
+ struct dgrambuf *b;
+ size_t ret;
+
+ switch(sk->type)
+ {
+ case SOCK_STREAM:
+ ret = sk->inbuf.s.datasize;
+ break;
+ case SOCK_DGRAM:
+ ret = 0;
+ for(b = sk->inbuf.d.f; b != NULL; b = b->next)
+ ret += b->size;
+ break;
+ }
+ return(ret);
+}
+
+size_t sockqueuesize(struct socket *sk)
+{
+ struct dgrambuf *b;
+ size_t ret;
+
+ switch(sk->type)
+ {
+ case SOCK_STREAM:
+ ret = sk->outbuf.s.datasize;
+ break;
+ case SOCK_DGRAM:
+ ret = 0;
+ for(b = sk->outbuf.d.f; b != NULL; b = b->next)
+ ret += b->size;
+ break;
+ }
+ return(ret);
+}
+
+struct socket *netcslisten(int type, struct sockaddr *name, socklen_t namelen, void (*func)(struct socket *, struct socket *, void *), void *data)
+{
+ struct socket *sk;
+
+ if(confgetint("net", "mode") == 1)
+ {
+ errno = EOPNOTSUPP;
+ return(NULL);
+ }
+ /* I don't know if this is actually correct (it probably isn't),
+ * but since, at on least Linux systems, PF_* are specifically
+ * #define'd to their AF_* counterparts, it allows for a severely
+ * smoother implementation. If it breaks something on your
+ * platform, please tell me so.
+ */
+ if(confgetint("net", "mode") == 0)
+ {
+ if((sk = mksock(name->sa_family, type)) == NULL)
+ return(NULL);
+ sk->state = SOCK_LST;
+ if(bind(sk->fd, name, namelen) < 0)
+ {
+ putsock(sk);
+ return(NULL);
+ }
+ if(listen(sk->fd, 16) < 0)
+ {
+ putsock(sk);
+ return(NULL);
+ }
+ sk->acceptcb = func;
+ sk->data = data;
+ return(sk);
+ }
+ errno = EOPNOTSUPP;
+ return(NULL);
+}
+
+/*
+ * The difference between netcslisten() and netcslistenlocal() is that
+ * netcslistenlocal() always listens on the local host, instead of
+ * following proxy/passive mode directions. It is suitable for eg. the
+ * UI channel, while the file sharing networks should, naturally, use
+ * netcslisten() instead.
+*/
+
+struct socket *netcslistenlocal(int type, struct sockaddr *name, socklen_t namelen, void (*func)(struct socket *, struct socket *, void *), void *data)
+{
+ struct socket *sk;
+
+ /* I don't know if this is actually correct (it probably isn't),
+ * but since, at on least Linux systems, PF_* are specifically
+ * #define'd to their AF_* counterparts, it allows for a severely
+ * smoother implementation. If it breaks something on your
+ * platform, please tell me so.
+ */
+ if((sk = mksock(name->sa_family, type)) == NULL)
+ return(NULL);
+ sk->state = SOCK_LST;
+ if(bind(sk->fd, name, namelen) < 0)
+ {
+ putsock(sk);
+ return(NULL);
+ }
+ if(listen(sk->fd, 16) < 0)
+ {
+ putsock(sk);
+ return(NULL);
+ }
+ sk->acceptcb = func;
+ sk->data = data;
+ return(sk);
+}
+
+struct socket *netcsdgram(struct sockaddr *name, socklen_t namelen)
+{
+ struct socket *sk;
+ int mode;
+
+ mode = confgetint("net", "mode");
+ if((mode == 0) || (mode == 1))
+ {
+ if((sk = mksock(name->sa_family, SOCK_DGRAM)) == NULL)
+ return(NULL);
+ if(bind(sk->fd, name, namelen) < 0)
+ {
+ putsock(sk);
+ return(NULL);
+ }
+ sk->state = SOCK_EST;
+ return(sk);
+ }
+ errno = EOPNOTSUPP;
+ return(NULL);
+}
+
+struct socket *netdupsock(struct socket *sk)
+{
+ struct socket *newsk;
+
+ newsk = newsock(sk->type);
+ if((newsk->fd = dup(sk->fd)) < 0)
+ {
+ flog(LOG_WARNING, "could not dup() socket: %s", strerror(errno));
+ putsock(newsk);
+ return(NULL);
+ }
+ newsk->state = sk->state;
+ newsk->ignread = sk->ignread;
+ if(sk->remote != NULL)
+ memcpy(newsk->remote = smalloc(sk->remotelen), sk->remote, newsk->remotelen = sk->remotelen);
+ return(newsk);
+}
+
+void netdgramconn(struct socket *sk, struct sockaddr *addr, socklen_t addrlen)
+{
+ if(sk->remote != NULL)
+ free(sk->remote);
+ memcpy(sk->remote = smalloc(addrlen), addr, sk->remotelen = addrlen);
+ sk->ignread = 1;
+}
+
+struct socket *netcsconn(struct sockaddr *addr, socklen_t addrlen, void (*func)(struct socket *, int, void *), void *data)
+{
+ struct socket *sk;
+ int mode;
+
+ mode = confgetint("net", "mode");
+ if((mode == 0) || (mode == 1))
+ {
+ if((sk = mksock(addr->sa_family, SOCK_STREAM)) == NULL)
+ return(NULL);
+ memcpy(sk->remote = smalloc(addrlen), addr, sk->remotelen = addrlen);
+ if(!connect(sk->fd, addr, addrlen))
+ {
+ sk->state = SOCK_EST;
+ func(sk, 0, data);
+ return(sk);
+ }
+ if(errno == EINPROGRESS)
+ {
+ sk->state = SOCK_SYN;
+ sk->conncb = func;
+ sk->data = data;
+ return(sk);
+ }
+ putsock(sk);
+ return(NULL);
+ }
+ errno = EOPNOTSUPP;
+ return(NULL);
+}
+
+int pollsocks(int timeout)
+{
+ int i, num, ret, retlen;
+ int newfd;
+ struct pollfd *pfds;
+ struct socket *sk, *next, *newsk;
+ struct sockaddr_storage ss;
+ socklen_t sslen;
+
+ pfds = smalloc(sizeof(*pfds) * (num = numsocks));
+ for(i = 0, sk = sockets; i < num; sk = sk->next)
+ {
+ if(sk->state == SOCK_STL)
+ {
+ num--;
+ continue;
+ }
+ pfds[i].fd = sk->fd;
+ pfds[i].events = 0;
+ if(!sk->ignread)
+ pfds[i].events |= POLLIN;
+ if((sk->state == SOCK_SYN) || (sockqueuesize(sk) > 0))
+ pfds[i].events |= POLLOUT;
+ pfds[i].revents = 0;
+ i++;
+ }
+ ret = poll(pfds, num, timeout);
+ if(ret < 0)
+ {
+ if(errno != EINTR)
+ {
+ flog(LOG_CRIT, "pollsocks: poll errored out: %s", strerror(errno));
+ /* To avoid CPU hogging in case it's bad, which it
+ * probably is. */
+ sleep(1);
+ }
+ free(pfds);
+ return(1);
+ }
+ for(sk = sockets; sk != NULL; sk = next)
+ {
+ next = sk->next;
+ for(i = 0; i < num; i++)
+ {
+ if(pfds[i].fd == sk->fd)
+ break;
+ }
+ if(i == num)
+ continue;
+ switch(sk->state)
+ {
+ case SOCK_LST:
+ if(pfds[i].revents & POLLIN)
+ {
+ sslen = sizeof(ss);
+ if((newfd = accept(sk->fd, (struct sockaddr *)&ss, &sslen)) < 0)
+ {
+ if(sk->errcb != NULL)
+ sk->errcb(sk, errno, sk->data);
+ }
+ newsk = newsock(sk->type);
+ newsk->fd = newfd;
+ newsk->family = sk->family;
+ newsk->state = SOCK_EST;
+ memcpy(newsk->remote = smalloc(sslen), &ss, sslen);
+ newsk->remotelen = sslen;
+ putsock(newsk);
+ if(sk->acceptcb != NULL)
+ sk->acceptcb(sk, newsk, sk->data);
+ }
+ if(pfds[i].revents & POLLERR)
+ {
+ retlen = sizeof(ret);
+ getsockopt(sk->fd, SOL_SOCKET, SO_ERROR, &ret, &retlen);
+ if(sk->errcb != NULL)
+ sk->errcb(sk, ret, sk->data);
+ continue;
+ }
+ break;
+ case SOCK_SYN:
+ if(pfds[i].revents & POLLERR)
+ {
+ retlen = sizeof(ret);
+ getsockopt(sk->fd, SOL_SOCKET, SO_ERROR, &ret, &retlen);
+ if(sk->conncb != NULL)
+ sk->conncb(sk, ret, sk->data);
+ closesock(sk);
+ continue;
+ }
+ if(pfds[i].revents & (POLLIN | POLLOUT))
+ {
+ sk->state = SOCK_EST;
+ if(sk->conncb != NULL)
+ sk->conncb(sk, 0, sk->data);
+ }
+ break;
+ case SOCK_EST:
+ if(pfds[i].revents & POLLERR)
+ {
+ retlen = sizeof(ret);
+ getsockopt(sk->fd, SOL_SOCKET, SO_ERROR, &ret, &retlen);
+ if(sk->errcb != NULL)
+ sk->errcb(sk, ret, sk->data);
+ closesock(sk);
+ continue;
+ }
+ if(pfds[i].revents & POLLIN)
+ sockrecv(sk);
+ if(pfds[i].revents & POLLOUT)
+ {
+ if(sockqueuesize(sk) > 0)
+ sockflush(sk);
+ }
+ break;
+ }
+ if(pfds[i].revents & POLLNVAL)
+ {
+ flog(LOG_CRIT, "BUG: stale socket struct on fd %i", sk->fd);
+ sk->state = SOCK_STL;
+ unlinksock(sk);
+ continue;
+ }
+ if(pfds[i].revents & POLLHUP)
+ {
+ if(sk->errcb != NULL)
+ sk->errcb(sk, 0, sk->data);
+ closesock(sk);
+ unlinksock(sk);
+ continue;
+ }
+ }
+ free(pfds);
+ for(sk = sockets; sk != NULL; sk = next)
+ {
+ next = sk->next;
+ if(sk->refcount == 1 && (sockqueuesize(sk) == 0))
+ {
+ unlinksock(sk);
+ continue;
+ }
+ if(sk->close && (sockqueuesize(sk) == 0))
+ closesock(sk);
+ if(sk->state == SOCK_STL)
+ {
+ unlinksock(sk);
+ continue;
+ }
+ }
+ return(1);
+}
+
+int socksettos(struct socket *sk, int tos)
+{
+ if(sk->family == AF_INET)
+ {
+ if(setsockopt(sk->fd, SOL_IP, IP_TOS, &tos, sizeof(tos)) < 0)
+ {
+ flog(LOG_WARNING, "could not set sock TOS to %i: %s", tos, strerror(errno));
+ return(-1);
+ }
+ return(0);
+ }
+ /* XXX: How does the IPv6 traffic class work? */
+ flog(LOG_WARNING, "could not set TOS on sock of family %i", sk->family);
+ return(1);
+}
+
+struct resolvedata
+{
+ int fd;
+ void (*callback)(struct sockaddr *addr, int addrlen, void *data);
+ void *data;
+ struct sockaddr_storage addr;
+ int addrlen;
+};
+
+static void resolvecb(pid_t pid, int status, struct resolvedata *data)
+{
+ static char buf[80];
+ int ret;
+ struct sockaddr_in *ipv4;
+
+ if(!status)
+ {
+ if((ret = read(data->fd, buf, sizeof(buf))) != 4)
+ {
+ errno = ENONET;
+ data->callback(NULL, 0, data->data);
+ } else {
+ ipv4 = (struct sockaddr_in *)&data->addr;
+ memcpy(&ipv4->sin_addr, buf, 4);
+ data->callback((struct sockaddr *)ipv4, sizeof(*ipv4), data->data);
+ }
+ } else {
+ errno = ENONET;
+ data->callback(NULL, 0, data->data);
+ }
+ close(data->fd);
+ free(data);
+}
+
+int netresolve(char *addr, void (*callback)(struct sockaddr *addr, int addrlen, void *data), void *data)
+{
+ int i;
+ char *p;
+ int port;
+ int pfd[2];
+ pid_t child;
+ struct resolvedata *rdata;
+ struct sockaddr_in ipv4;
+ struct hostent *he;
+ sigset_t sigset;
+
+ /* IPv4 */
+ port = -1;
+ if((p = strchr(addr, ':')) != NULL)
+ {
+ *p = 0;
+ port = atoi(p + 1);
+ }
+ ipv4.sin_family = AF_INET;
+ ipv4.sin_port = htons(port);
+ if(inet_aton(addr, &ipv4.sin_addr))
+ {
+ callback((struct sockaddr *)&ipv4, sizeof(ipv4), data);
+ } else {
+ sigemptyset(&sigset);
+ sigaddset(&sigset, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &sigset, NULL);
+ if((pipe(pfd) < 0) || ((child = fork()) < 0))
+ {
+ sigprocmask(SIG_UNBLOCK, &sigset, NULL);
+ return(-1);
+ }
+ if(child == 0)
+ {
+ sigprocmask(SIG_UNBLOCK, &sigset, NULL);
+ for(i = 3; i < FD_SETSIZE; i++)
+ {
+ if(i != pfd[1])
+ close(i);
+ }
+ signal(SIGALRM, SIG_DFL);
+ alarm(30);
+ if((he = gethostbyname(addr)) == NULL)
+ exit(1);
+ write(pfd[1], he->h_addr_list[0], 4);
+ exit(0);
+ } else {
+ close(pfd[1]);
+ fcntl(pfd[0], F_SETFL, fcntl(pfd[0], F_GETFL) | O_NONBLOCK);
+ rdata = smalloc(sizeof(*rdata));
+ rdata->fd = pfd[0];
+ rdata->callback = callback;
+ rdata->data = data;
+ memcpy(&rdata->addr, &ipv4, rdata->addrlen = sizeof(ipv4));
+ childcallback(child, (void (*)(pid_t, int, void *))resolvecb, rdata);
+ sigprocmask(SIG_UNBLOCK, &sigset, NULL);
+ return(1);
+ }
+ }
+ return(0);
+}
+
+int sockgetlocalname(struct socket *sk, struct sockaddr **namebuf, socklen_t *lenbuf)
+{
+ socklen_t len;
+ struct sockaddr_storage name;
+
+ *namebuf = NULL;
+ if((sk->state == SOCK_STL) || (sk->fd < 0))
+ return(-1);
+ len = sizeof(name);
+ if(getsockname(sk->fd, (struct sockaddr *)&name, &len) < 0)
+ {
+ flog(LOG_ERR, "BUG: alive socket with dead fd in sockgetlocalname");
+ return(-1);
+ }
+ *namebuf = memcpy(smalloc(len), &name, len);
+ *lenbuf = len;
+ return(0);
+}
+
+int sockgetremotename(struct socket *sk, struct sockaddr **namebuf, socklen_t *lenbuf)
+{
+ socklen_t len;
+ struct sockaddr_storage name;
+ struct sockaddr_in *ipv4;
+ struct sockaddr *pname;
+ socklen_t pnamelen;
+
+ switch(confgetint("net", "mode"))
+ {
+ case 0:
+ *namebuf = NULL;
+ if((sk->state == SOCK_STL) || (sk->fd < 0))
+ return(-1);
+ len = sizeof(name);
+ if(getsockname(sk->fd, (struct sockaddr *)&name, &len) < 0)
+ {
+ flog(LOG_ERR, "BUG: alive socket with dead fd in sockgetremotename");
+ return(-1);
+ }
+ if(name.ss_family == AF_INET)
+ {
+ ipv4 = (struct sockaddr_in *)&name;
+ if(getpublicaddr(AF_INET, &pname, &pnamelen) < 0)
+ {
+ flog(LOG_WARNING, "could not determine public IP address - strange things may happen");
+ return(-1);
+ }
+ ipv4->sin_addr.s_addr = ((struct sockaddr_in *)pname)->sin_addr.s_addr;
+ free(pname);
+ }
+ *namebuf = memcpy(smalloc(len), &name, len);
+ *lenbuf = len;
+ return(0);
+ case 1:
+ errno = EOPNOTSUPP;
+ return(-1);
+ default:
+ flog(LOG_CRIT, "unknown net mode %i active", confgetint("net", "mode"));
+ errno = EOPNOTSUPP;
+ return(-1);
+ }
+}
+
+char *formataddress(struct sockaddr *arg, socklen_t arglen)
+{
+ struct sockaddr_un *UNIX; /* Some wise guy has #defined unix with
+ * lowercase letters to 1, so I do this
+ * instead. */
+ struct sockaddr_in *ipv4;
+#ifdef HAVE_IPV6
+ struct sockaddr_in6 *ipv6;
+#endif
+ static char *ret = NULL;
+ char buf[1024];
+
+ if(ret != NULL)
+ free(ret);
+ ret = NULL;
+ switch(arg->sa_family)
+ {
+ case AF_UNIX:
+ UNIX = (struct sockaddr_un *)arg;
+ ret = sprintf2("%s", UNIX->sun_path);
+ break;
+ case AF_INET:
+ ipv4 = (struct sockaddr_in *)arg;
+ if(inet_ntop(AF_INET, &ipv4->sin_addr, buf, sizeof(buf)) == NULL)
+ return(NULL);
+ ret = sprintf2("%s:%i", buf, (int)ntohs(ipv4->sin_port));
+ break;
+#ifdef HAVE_IPV6
+ case AF_INET6:
+ ipv6 = (struct sockaddr_in6 *)arg;
+ if(inet_ntop(AF_INET6, &ipv6->sin6_addr, buf, sizeof(buf)) == NULL)
+ return(NULL);
+ ret = sprintf2("%s:%i", buf, (int)ntohs(ipv6->sin6_port));
+ break;
+#endif
+ default:
+ errno = EPFNOSUPPORT;
+ break;
+ }
+ return(ret);
+}
+
+#if 0
+
+/*
+ * It was very nice to use this, but it seems
+ * to mess things up, so I guess it has to go... :-(
+ */
+
+static int formataddress(FILE *stream, const struct printf_info *info, const void *const *args)
+{
+ struct sockaddr *arg;
+ socklen_t arglen;
+ struct sockaddr_un *UNIX; /* Some wise guy has #defined unix with
+ * lowercase letters to 1, so I do this
+ * instead. */
+ struct sockaddr_in *ipv4;
+ int ret;
+
+ arg = *(struct sockaddr **)(args[0]);
+ arglen = *(socklen_t *)(args[1]);
+ switch(arg->sa_family)
+ {
+ case AF_UNIX:
+ UNIX = (struct sockaddr_un *)arg;
+ ret = fprintf(stream, "%s", UNIX->sun_path);
+ break;
+ case AF_INET:
+ ipv4 = (struct sockaddr_in *)arg;
+ ret = fprintf(stream, "%s:%i", inet_ntoa(ipv4->sin_addr), (int)ntohs(ipv4->sin_port));
+ break;
+ default:
+ ret = -1;
+ errno = EPFNOSUPPORT;
+ break;
+ }
+ return(ret);
+}
+
+static int formataddress_arginfo(const struct printf_info *info, size_t n, int *argtypes)
+{
+ if(n > 0)
+ argtypes[0] = PA_POINTER;
+ if(n > 1)
+ argtypes[1] = PA_INT; /* Sources tell me that socklen_t _must_
+ * be an int, so I guess this should be
+ * safe. */
+ return(2);
+}
+#endif
+
+static int init(int hup)
+{
+ if(!hup)
+ {
+ /*
+ if(register_printf_function('N', formataddress, formataddress_arginfo))
+ {
+ flog(LOG_CRIT, "could not register printf handler %%N: %s", strerror(errno));
+ return(1);
+ }
+ */
+ }
+ return(0);
+}
+
+static void terminate(void)
+{
+ while(sockets != NULL)
+ unlinksock(sockets);
+}
+
+static struct module me =
+{
+ .name = "net",
+ .conf =
+ {
+ .vars = myvars
+ },
+ .init = init,
+ .terminate = terminate
+};
+
+MODULE(me)
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#ifndef _NET_H
+#define _NET_H
+
+#include <sys/socket.h>
+
+#define SOCK_LST 0 /* Listening */
+#define SOCK_SYN 1 /* Connecting */
+#define SOCK_EST 2 /* Established */
+#define SOCK_STL 3 /* Stale, dead */
+#define SOCK_TOS_MINDELAY 0x10
+#define SOCK_TOS_MAXTP 0x08
+#define SOCK_TOS_MAXREL 0x04
+#define SOCK_TOS_MINCOST 0x02
+
+struct dgrambuf
+{
+ struct dgrambuf *next;
+ struct sockaddr *addr;
+ socklen_t addrlen;
+ void *data;
+ size_t size;
+};
+
+struct socket
+{
+ struct socket *next, *prev;
+ int refcount;
+ int fd;
+ int isrealsocket; /* Bleh... */
+ int family;
+ int tos;
+ int type;
+ int state;
+ int ignread;
+ int events;
+ int close;
+ struct sockaddr *remote;
+ socklen_t remotelen;
+ union
+ {
+ struct
+ {
+ struct dgrambuf *f, *l;
+ } d;
+ struct
+ {
+ void *buf;
+ size_t bufsize;
+ size_t datasize;
+ } s;
+ } outbuf;
+ union
+ {
+ struct
+ {
+ struct dgrambuf *f, *l;
+ } d;
+ struct
+ {
+ void *buf;
+ size_t bufsize;
+ size_t datasize;
+ } s;
+ } inbuf;
+ void (*conncb)(struct socket *sk, int err, void *data);
+ void (*errcb)(struct socket *sk, int err, void *data);
+ void (*readcb)(struct socket *sk, void *data);
+ void (*writecb)(struct socket *sk, void *data);
+ void (*acceptcb)(struct socket *sk, struct socket *newsk, void *data);
+ void *data;
+};
+
+void putsock(struct socket *sk);
+void getsock(struct socket *sk);
+struct socket *netcslisten(int type, struct sockaddr *name, socklen_t namelen, void (*func)(struct socket *, struct socket *, void *), void *data);
+struct socket *netcslistenlocal(int type, struct sockaddr *name, socklen_t namelen, void (*func)(struct socket *, struct socket *, void *), void *data);
+struct socket *netcsconn(struct sockaddr *addr, socklen_t addrlen, void (*func)(struct socket *, int, void *), void *data);
+int pollsocks(int timeout);
+void sockqueue(struct socket *sk, void *data, size_t size);
+size_t sockqueuesize(struct socket *sk);
+int netresolve(char *addr, void (*callback)(struct sockaddr *addr, int addrlen, void *data), void *data);
+struct socket *netcsdgram(struct sockaddr *name, socklen_t namelen);
+struct socket *netdupsock(struct socket *sk);
+void netdgramconn(struct socket *sk, struct sockaddr *addr, socklen_t addrlen);
+int sockgetlocalname(struct socket *sk, struct sockaddr **namebuf, socklen_t *lenbuf);
+int sockgetremotename(struct socket *sk, struct sockaddr **namebuf, socklen_t *lenbuf);
+void closesock(struct socket *sk);
+void *sockgetinbuf(struct socket *sk, size_t *size);
+struct socket *wrapsock(int fd);
+size_t sockgetdatalen(struct socket *sk);
+int getpublicaddr(int af, struct sockaddr **addr, socklen_t *lenbuf);
+int socksettos(struct socket *sk, int tos);
+char *formataddress(struct sockaddr *arg, socklen_t arglen);
+
+#endif
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#include <stdlib.h>
+#include <malloc.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <errno.h>
+#include <regex.h>
+#include <string.h>
+#include <time.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "utils.h"
+#include "log.h"
+#include "sysevents.h"
+#include "filenet.h"
+#include "search.h"
+
+#define TOK_STR 0
+#define TOK_SE 1
+#define TOK_OP 2
+#define TOK_CP 3
+
+struct srchlist
+{
+ struct srchlist *next, *prev;
+ struct search *srch;
+};
+
+struct tok
+{
+ struct tok *next;
+ int type;
+ union
+ {
+ wchar_t *str;
+ struct sexpr *se;
+ } d;
+};
+
+struct reinfo
+{
+ wchar_t *begstr, *endstr, *onestr;
+ struct wcslist *strs;
+};
+
+struct bound
+{
+ int min, max;
+};
+
+static void trycommit(void);
+
+struct search *searches = NULL;
+static struct srchlist *searchqueue = NULL;
+static struct timer *committimer = NULL;
+GCBCHAIN(newsrchcb, struct search *);
+
+wchar_t *regexunquotesimple(wchar_t *re)
+{
+ wchar_t *specials, *buf, *p;
+
+ specials = L"\\^$.*+?[{()|";
+ buf = smalloc((wcslen(re) + 1) * sizeof(wchar_t));
+ p = buf;
+ for(; *re != L'\0'; re++)
+ {
+ if(*re == L'\\')
+ {
+ re++;
+ if(!*re)
+ {
+ *p = L'\0';
+ return(buf);
+ }
+ *(p++) = *re;
+ } else {
+ if(wcschr(specials, *re) != NULL)
+ {
+ free(buf);
+ return(NULL);
+ }
+ *(p++) = *re;
+ }
+ }
+ *p = L'\0';
+ return(buf);
+}
+
+static void freesln(struct wcslist *ln, struct wcslist **list)
+{
+ if(ln->prev != NULL)
+ ln->prev->next = ln->next;
+ if(ln->next != NULL)
+ ln->next->prev = ln->prev;
+ if(ln == *list)
+ *list = ln->next;
+ free(ln->str);
+ free(ln);
+}
+
+void freesl(struct wcslist **list)
+{
+ while(*list != NULL)
+ freesln(*list, list);
+}
+
+static struct wcslist *newsl(struct wcslist **list, wchar_t *str)
+{
+ struct wcslist *ln;
+
+ ln = smalloc(sizeof(*ln));
+ memset(ln, 0, sizeof(*ln));
+ ln->str = swcsdup(str);
+ ln->len = wcslen(str);
+ ln->next = *list;
+ ln->prev = NULL;
+ if(*list != NULL)
+ (*list)->prev = ln;
+ *list = ln;
+ return(ln);
+}
+
+static void slmerge1(struct wcslist **list, wchar_t *str)
+{
+ size_t len;
+ struct wcslist *cur, *next;
+
+ len = wcslen(str);
+ for(cur = *list; cur != NULL; cur = next)
+ {
+ next = cur->next;
+ if(len <= cur->len)
+ {
+ if(((len < cur->len) && wcsexists(cur->str, str)) || !wcscmp(str, cur->str))
+ return;
+ } else if(len > cur->len) {
+ if(wcsexists(str, cur->str))
+ freesln(cur, list);
+ }
+ }
+ newsl(list, str);
+}
+
+void slmergemax(struct wcslist **dest, struct wcslist *src)
+{
+ for(; src != NULL; src = src->next)
+ slmerge1(dest, src->str);
+}
+
+static struct wcslist *makeminlist1(wchar_t *s1, wchar_t *s2)
+{
+ int i;
+ wchar_t *p1, *p2, c;
+ struct wcslist *list;
+
+ list = NULL;
+ for(p1 = s1; *p1 != L'\0'; p1++)
+ {
+ for(p2 = s2; *p2 != L'\0'; p2++)
+ {
+ for(i = 0; (p1[i] != L'\0') && (p2[i] != L'\0') && (towlower(p1[i]) == towlower(p2[i])); i++);
+ if(i > 0)
+ {
+ c = p2[i];
+ p2[i] = L'\0';
+ slmerge1(&list, p2);
+ p2[i] = c;
+ }
+ }
+ }
+ return(list);
+}
+
+struct wcslist *slmergemin(struct wcslist *l1, struct wcslist *l2)
+{
+ struct wcslist *cur1, *cur2, *list, *rlist;
+
+ list = NULL;
+ for(cur1 = l1; cur1 != NULL; cur1 = cur1->next)
+ {
+ for(cur2 = l2; cur2 != NULL; cur2 = cur2->next)
+ {
+ rlist = makeminlist1(cur1->str, cur2->str);
+ slmergemax(&list, rlist);
+ freesl(&rlist);
+ }
+ }
+ return(list);
+}
+
+static struct bound readbound(wchar_t *p, wchar_t **endret)
+{
+ struct bound ret;
+
+ switch(*p)
+ {
+ case L'?':
+ ret.min = 0;
+ ret.max = 1;
+ p++;
+ break;
+ case L'*':
+ ret.min = 0;
+ ret.max = -1;
+ p++;
+ break;
+ case L'+':
+ ret.min = 1;
+ ret.max = -1;
+ p++;
+ break;
+ case L'{':
+ p++;
+ ret.min = wcstol(p, &p, 10);
+ if(*p == L',')
+ {
+ p++;
+ if(*p == L'}')
+ ret.max = -1;
+ else
+ ret.max = wcstol(p, &p, 10);
+ } else {
+ ret.max = ret.min;
+ }
+ if(*p != L'}')
+ {
+ /* Cannot happen in a validated regex... */
+ flog(LOG_WARNING, "BUG? \"Impossible\" case encountered in search.c:readbound() (No `}' after `{'-type bound!");
+ } else {
+ p++;
+ }
+ break;
+ default:
+ ret.min = 1;
+ ret.max = 1;
+ break;
+ }
+ if(endret != NULL)
+ *endret = p;
+ return(ret);
+}
+
+#define fnc(p) do {if((p) != NULL) free(p); (p) = NULL; p ## size = 0; p ## data = 0;} while(0)
+
+static struct reinfo analyzere(wchar_t *re, wchar_t **endret, wchar_t endc)
+{
+ int i, commit, parsealt;
+ struct reinfo ret, sinf;
+ struct bound b;
+ wchar_t *cs, *ns;
+ size_t cssize, csdata, nssize, nsdata, len1, len2, maxlen, minlen;
+ wchar_t beg, c;
+ struct wcslist *list;
+
+ memset(&ret, 0, sizeof(ret));
+ commit = parsealt = 0;
+ beg = 1;
+ cs = ns = NULL;
+ cssize = csdata = nssize = nsdata = 0;
+ while((*re != endc) && !parsealt)
+ {
+ switch(*re)
+ {
+ case L'$':
+ case L'^':
+ re++;
+ commit = 1;
+ break;
+ case L'.':
+ re++;
+ b = readbound(re, &re);
+ if(b.max != 0)
+ commit = 1;
+ break;
+ case L'(':
+ re++;
+ sinf = analyzere(re, &re, L')');
+ re++;
+ b = readbound(re, &re);
+ if(sinf.onestr != NULL)
+ {
+ for(i = 0; i < b.min; i++)
+ {
+ bufcat(cs, sinf.onestr, wcslen(sinf.onestr));
+ bufcat(ns, sinf.onestr, wcslen(sinf.onestr));
+ }
+ if((b.max == -1) || (b.max > b.min))
+ commit = 1;
+ } else {
+ commit = 1;
+ if(b.min > 0)
+ {
+ if(sinf.begstr != NULL)
+ bufcat(cs, sinf.begstr, wcslen(sinf.begstr));
+ if(sinf.endstr != NULL)
+ bufcat(ns, sinf.endstr, wcslen(sinf.endstr));
+ }
+ }
+ if(sinf.begstr != NULL)
+ free(sinf.begstr);
+ if(sinf.endstr != NULL)
+ free(sinf.endstr);
+ if(sinf.onestr != NULL)
+ free(sinf.onestr);
+ if(b.min > 0)
+ slmergemax(&ret.strs, sinf.strs);
+ freesl(&sinf.strs);
+ break;
+ case L'[':
+ c = L'\0';
+ re += 2;
+ /* Must exist in a validated RE... */
+ while(*(re++) != L']');
+ readbound(re, &re);
+ commit = 1;
+ break;
+ case L'|':
+ re++;
+ parsealt = 1;
+ break;
+ case L'\\':
+ re++;
+ /* A validated RE cannot end in a backslash, so fall
+ * through */
+ default:
+ c = *(re++);
+ b = readbound(re, &re);
+ for(i = 0; i < b.min; i++)
+ addtobuf(cs, c);
+ if((b.max == -1) || (b.max > b.min))
+ commit = 1;
+ break;
+ }
+ if(commit)
+ {
+ if(cs != NULL)
+ addtobuf(cs, L'\0');
+ if(beg)
+ {
+ if(cs != NULL)
+ ret.begstr = swcsdup(cs);
+ beg = 0;
+ }
+ if(cs != NULL)
+ slmerge1(&ret.strs, cs);
+ fnc(cs);
+ commit = 0;
+ if(ns != NULL)
+ {
+ cs = swcsdup(ns);
+ cssize = nssize;
+ csdata = nsdata;
+ fnc(ns);
+ }
+ }
+ }
+ if(cs != NULL)
+ {
+ addtobuf(cs, L'\0');
+ if(beg)
+ ret.onestr = swcsdup(cs);
+ else
+ ret.endstr = swcsdup(cs);
+ slmerge1(&ret.strs, cs);
+ fnc(cs);
+ }
+ if(parsealt)
+ {
+ sinf = analyzere(re, &re, endc);
+ list = slmergemin(ret.strs, sinf.strs);
+ freesl(&ret.strs);
+ freesl(&sinf.strs);
+ ret.strs = list;
+ if(sinf.begstr != NULL)
+ {
+ if(ret.begstr != NULL)
+ {
+ for(i = 0; (sinf.begstr[i] != L'\0') && (ret.begstr != L'\0') && (ret.begstr[i] == sinf.begstr[i]); i++);
+ if(i == 0)
+ free(ret.begstr);
+ else
+ ret.begstr[i] = L'\0';
+ }
+ free(sinf.begstr);
+ } else {
+ if(ret.begstr != NULL)
+ {
+ free(ret.begstr);
+ ret.begstr = NULL;
+ }
+ }
+ if(sinf.endstr != NULL)
+ {
+ if(ret.endstr != NULL)
+ {
+ len1 = wcslen(ret.endstr);
+ len2 = wcslen(sinf.endstr);
+ if(len1 < len2)
+ {
+ minlen = len1;
+ maxlen = len2;
+ } else {
+ minlen = len2;
+ maxlen = len1;
+ }
+ for(i = 1; (i <= minlen) && (ret.endstr[len1 - i] == sinf.endstr[len2 - i]); i++);
+ if(i == 1)
+ free(ret.endstr);
+ else if(i <= maxlen)
+ wmemmove(ret.endstr, ret.endstr + (len1 - i) + 1, i);
+ }
+ free(sinf.endstr);
+ } else {
+ if(ret.endstr != NULL)
+ {
+ free(ret.endstr);
+ ret.endstr = NULL;
+ }
+ }
+ if(sinf.onestr != NULL)
+ {
+ if(ret.onestr != NULL)
+ {
+ /* XXX: Comparing beginning and end of the onestrs and
+ * create begstr and endstr if there isn't an exact
+ * match.*/
+ if(wcscmp(ret.onestr, sinf.onestr))
+ {
+ free(ret.onestr);
+ ret.onestr = NULL;
+ }
+ }
+ free(sinf.onestr);
+ } else {
+ if(ret.onestr != NULL)
+ {
+ free(ret.onestr);
+ ret.onestr = NULL;
+ }
+ }
+ }
+ if(endret != NULL)
+ *endret = re;
+ return(ret);
+}
+
+#undef fnc
+
+struct wcslist *regexfindstrings(wchar_t *re)
+{
+ struct reinfo i;
+
+ i = analyzere(re, NULL, L'\0');
+ if(i.begstr != NULL)
+ free(i.begstr);
+ if(i.endstr != NULL)
+ free(i.endstr);
+ if(i.onestr != NULL)
+ free(i.onestr);
+ return(i.strs);
+}
+
+static struct sexpr *newsexpr(void)
+{
+ struct sexpr *sexpr;
+
+ sexpr = smalloc(sizeof(*sexpr));
+ memset(sexpr, 0, sizeof(*sexpr));
+ sexpr->refcount = 1;
+ return(sexpr);
+}
+
+void getsexpr(struct sexpr *sexpr)
+{
+ sexpr->refcount++;
+}
+
+void putsexpr(struct sexpr *sexpr)
+{
+ if(--sexpr->refcount != 0)
+ return;
+ if(sexpr->l != NULL)
+ putsexpr(sexpr->l);
+ if(sexpr->r != NULL)
+ putsexpr(sexpr->r);
+ if((sexpr->op == SOP_NAMERE) || (sexpr->op == SOP_LINKRE))
+ {
+ if(sexpr->d.re.sre != NULL)
+ free(sexpr->d.re.sre);
+ if(sexpr->d.re.inited)
+ regfree(&sexpr->d.re.cre);
+ }
+ if((sexpr->op == SOP_NAMESS) || (sexpr->op == SOP_LINKSS))
+ {
+ if(sexpr->d.s != NULL)
+ free(sexpr->d.s);
+ }
+ free(sexpr);
+}
+
+static struct tok *newtok(void)
+{
+ struct tok *tok;
+
+ tok = smalloc(sizeof(*tok));
+ memset(tok, 0, sizeof(*tok));
+ tok->next = NULL;
+ return(tok);
+}
+
+static void freetok(struct tok *tok)
+{
+ if((tok->type == TOK_STR) && (tok->d.str != NULL))
+ free(tok->d.str);
+ if((tok->type == TOK_SE) && (tok->d.se != NULL))
+ putsexpr(tok->d.se);
+ free(tok);
+}
+
+static void pushtok(struct tok *tok, struct tok **st)
+{
+ tok->next = *st;
+ *st = tok;
+}
+
+static struct tok *poptok(struct tok **st)
+{
+ struct tok *tok;
+
+ tok = *st;
+ *st = (*st)->next;
+ return(tok);
+}
+
+int calccost(struct sexpr *sexpr)
+{
+ sexpr->tcost = sexpr->cost;
+ if(sexpr->l != NULL)
+ sexpr->tcost += calccost(sexpr->l);
+ if(sexpr->r != NULL)
+ sexpr->tcost += calccost(sexpr->r);
+ return(sexpr->tcost);
+}
+
+struct sexpr *parsesexpr(int argc, wchar_t **argv)
+{
+ int i, done, std;
+ struct tok *st, *tok, *tok2;
+ struct sexpr *sexpr;
+ char *buf;
+ wchar_t *wbuf;
+
+ std = 0;
+ st = NULL;
+ for(i = 0; i < argc; i++)
+ {
+ pushtok(tok = newtok(), &st);
+ tok->type = TOK_STR;
+ tok->d.str = swcsdup(argv[i]);
+ std++;
+ do
+ {
+ done = 1;
+ if((st->type == TOK_STR) && !wcscmp(st->d.str, L"("))
+ {
+ freetok(poptok(&st));
+ pushtok(tok = newtok(), &st);
+ tok->type = TOK_OP;
+ done = 0;
+ } else if((st->type == TOK_STR) && !wcscmp(st->d.str, L")")) {
+ freetok(poptok(&st));
+ pushtok(tok = newtok(), &st);
+ tok->type = TOK_CP;
+ done = 0;
+ } else if((st->type == TOK_STR) && (!wcsncmp(st->d.str, L"N~", 2) || !wcsncmp(st->d.str, L"L~", 2))) {
+ tok2 = poptok(&st);
+ pushtok(tok = newtok(), &st);
+ tok->type = TOK_SE;
+ sexpr = newsexpr();
+ if((wbuf = regexunquotesimple(tok2->d.str + 2)) != NULL)
+ {
+ if(tok2->d.str[0] == L'N')
+ sexpr->op = SOP_NAMESS;
+ else
+ sexpr->op = SOP_LINKSS;
+ sexpr->d.s = wbuf;
+ sexpr->cost = 5;
+ } else {
+ if(tok2->d.str[0] == L'N')
+ sexpr->op = SOP_NAMERE;
+ else
+ sexpr->op = SOP_LINKRE;
+ sexpr->cost = 20;
+ if((buf = icwcstombs(tok2->d.str + 2, "UTF-8")) == NULL)
+ {
+ freetok(tok2);
+ putsexpr(sexpr);
+ goto out_err;
+ }
+ if(regcomp(&sexpr->d.re.cre, buf, REG_EXTENDED | REG_ICASE | REG_NOSUB))
+ {
+ freetok(tok2);
+ free(buf);
+ putsexpr(sexpr);
+ goto out_err;
+ }
+ free(buf);
+ sexpr->d.re.inited = 1;
+ sexpr->d.re.sre = swcsdup(tok2->d.str + 2);
+ }
+ getsexpr(tok->d.se = sexpr);
+ freetok(tok2);
+ putsexpr(sexpr);
+ done = 0;
+ } else if((st->type == TOK_STR) && (!wcsncmp(st->d.str, L"S<", 2) || !wcsncmp(st->d.str, L"S=", 2) || !wcsncmp(st->d.str, L"S>", 2))) {
+ tok2 = poptok(&st);
+ pushtok(tok = newtok(), &st);
+ tok->type = TOK_SE;
+ sexpr = newsexpr();
+ if(tok2->d.str[1] == L'<')
+ sexpr->op = SOP_SIZELT;
+ else if(tok2->d.str[1] == L'=')
+ sexpr->op = SOP_SIZEEQ;
+ else
+ sexpr->op = SOP_SIZEGT;
+ sexpr->d.n = wcstol(tok2->d.str + 2, NULL, 0);
+ sexpr->cost = 0;
+ getsexpr(tok->d.se = sexpr);
+ freetok(tok2);
+ putsexpr(sexpr);
+ done = 0;
+ } else if((std >= 3) && (st->type == TOK_CP) && (st->next->type == TOK_SE) && (st->next->next->type == TOK_OP)) {
+ freetok(poptok(&st));
+ tok = poptok(&st);
+ freetok(poptok(&st));
+ pushtok(tok, &st);
+ std -= 2;
+ done = 0;
+ } else if((std >= 2) && (st->type == TOK_SE) && (st->next->type == TOK_STR) && !wcscmp(st->next->d.str, L"!")) {
+ sexpr = newsexpr();
+ sexpr->op = SOP_NOT;
+ sexpr->cost = 0;
+ getsexpr(sexpr->l = st->d.se);
+ freetok(poptok(&st));
+ freetok(poptok(&st));
+ pushtok(tok = newtok(), &st);
+ tok->type = TOK_SE;
+ getsexpr(tok->d.se = sexpr);
+ putsexpr(sexpr);
+ std -= 1;
+ done = 0;
+ } else if((std >= 3) && (st->type == TOK_SE) && (st->next->type == TOK_STR) && (!wcscmp(st->next->d.str, L"&") || !wcscmp(st->next->d.str, L"|")) && (st->next->next->type == TOK_SE)) {
+ sexpr = newsexpr();
+ if(!wcscmp(st->next->d.str, L"&"))
+ sexpr->op = SOP_AND;
+ else
+ sexpr->op = SOP_OR;
+ sexpr->cost = 0;
+ getsexpr(sexpr->l = st->next->next->d.se);
+ getsexpr(sexpr->r = st->d.se);
+ freetok(poptok(&st));
+ freetok(poptok(&st));
+ freetok(poptok(&st));
+ pushtok(tok = newtok(), &st);
+ tok->type = TOK_SE;
+ getsexpr(tok->d.se = sexpr);
+ putsexpr(sexpr);
+ std -= 2;
+ done = 0;
+ }
+ } while(!done);
+ }
+ if((st == NULL) || (st->next != NULL) || (st->type != TOK_SE))
+ goto out_err;
+ getsexpr(sexpr = st->d.se);
+ freetok(st);
+ calccost(sexpr);
+ return(sexpr);
+
+ out_err:
+ while(st != NULL)
+ freetok(poptok(&st));
+ return(NULL);
+}
+
+void optsexpr(struct sexpr *sexpr)
+{
+ struct sexpr *buf;
+
+ if((sexpr->l != NULL) && (sexpr->r != NULL))
+ {
+ if(sexpr->l->tcost > sexpr->r->tcost)
+ {
+ buf = sexpr->r;
+ sexpr->r = sexpr->l;
+ sexpr->l = buf;
+ }
+ }
+ if(sexpr->l != NULL)
+ optsexpr(sexpr->l);
+ if(sexpr->r != NULL)
+ optsexpr(sexpr->r);
+}
+
+struct wcslist *findsexprstrs(struct sexpr *sexpr)
+{
+ struct wcslist *list, *l1, *l2;
+
+ list = NULL;
+ switch(sexpr->op)
+ {
+ case SOP_AND:
+ list = findsexprstrs(sexpr->l);
+ l1 = findsexprstrs(sexpr->r);
+ slmergemax(&list, l1);
+ freesl(&l1);
+ break;
+ case SOP_OR:
+ l1 = findsexprstrs(sexpr->l);
+ l2 = findsexprstrs(sexpr->r);
+ list = slmergemin(l1, l2);
+ freesl(&l1);
+ freesl(&l2);
+ break;
+ case SOP_NOT:
+ break;
+ case SOP_NAMERE:
+ case SOP_LINKRE:
+ list = regexfindstrings(sexpr->d.re.sre);
+ break;
+ case SOP_NAMESS:
+ case SOP_LINKSS:
+ slmerge1(&list, sexpr->d.s);
+ break;
+ default:
+ break;
+ }
+ return(list);
+}
+
+static void unlinksqueue(struct srchlist *ln)
+{
+ if(ln->prev != NULL)
+ ln->prev->next = ln->next;
+ if(ln->next != NULL)
+ ln->next->prev = ln->prev;
+ if(ln == searchqueue)
+ searchqueue = ln->next;
+ free(ln);
+}
+
+static void ctexpire(int cancelled, void *data)
+{
+ committimer = NULL;
+ if(!cancelled)
+ trycommit();
+}
+
+static void estimatequeue(void)
+{
+ struct srchlist *cur;
+ struct srchfnnlist *ln;
+ time_t now, start;
+
+ if(searchqueue == NULL)
+ return;
+ start = now = time(NULL);
+ for(ln = searchqueue->srch->fnl; ln != NULL; ln = ln->next)
+ {
+ if((ln->fn->lastsrch != 0) && (ln->fn->lastsrch + ln->fn->srchwait > start))
+ start = ln->fn->lastsrch + ln->fn->srchwait;
+ }
+ if(start != searchqueue->srch->eta)
+ {
+ searchqueue->srch->eta = start;
+ CBCHAINDOCB(searchqueue->srch, search_eta, searchqueue->srch);
+ }
+ for(cur = searchqueue->next; cur != NULL; cur = cur->next)
+ {
+ now = start;
+ for(ln = cur->srch->fnl; ln != NULL; ln = ln->next)
+ {
+ if(now + ln->fn->srchwait > start)
+ start = now + ln->fn->srchwait;
+ }
+ if(start != cur->srch->eta)
+ {
+ cur->srch->eta = start;
+ CBCHAINDOCB(cur->srch, search_eta, cur->srch);
+ }
+ }
+ if((committimer == NULL) || ((time_t)committimer->at != searchqueue->srch->eta))
+ {
+ if(committimer != NULL)
+ canceltimer(committimer);
+ committimer = timercallback(searchqueue->srch->eta, ctexpire, NULL);
+ }
+}
+
+struct search *findsearch(int id)
+{
+ struct search *srch;
+
+ for(srch = searches; srch != NULL; srch = srch->next)
+ {
+ if(srch->id == id)
+ break;
+ }
+ return(srch);
+}
+
+struct search *newsearch(wchar_t *owner, struct sexpr *sexpr)
+{
+ struct search *srch;
+ static int id = 0;
+
+ srch = smalloc(sizeof(*srch));
+ memset(srch, 0, sizeof(*srch));
+ srch->id = id++;
+ srch->owner = swcsdup(owner);
+ if((srch->sexpr = sexpr) != NULL)
+ getsexpr(srch->sexpr);
+ CBCHAININIT(srch, search_eta);
+ CBCHAININIT(srch, search_commit);
+ CBCHAININIT(srch, search_result);
+ CBCHAININIT(srch, search_destroy);
+ srch->next = searches;
+ srch->prev = NULL;
+ if(searches != NULL)
+ searches->prev = srch;
+ searches = srch;
+ return(srch);
+}
+
+static void srchexpire(int cancelled, struct search *srch)
+{
+ srch->freetimer = NULL;
+ if(!cancelled)
+ freesearch(srch);
+}
+
+static void trycommit(void)
+{
+ struct srchfnnlist *ln;
+ struct search *srch;
+ time_t now;
+
+ if(searchqueue == NULL)
+ return;
+ srch = searchqueue->srch;
+ now = time(NULL);
+ for(ln = srch->fnl; ln != NULL; ln = ln->next)
+ {
+ if(now < ln->fn->lastsrch + ln->fn->srchwait)
+ break;
+ }
+ if(ln != NULL)
+ return;
+ unlinksqueue(searchqueue);
+ srch->state = SRCH_RUN;
+ srch->eta = time(NULL);
+ srch->committime = ntime();
+ srch->freetimer = timercallback(ntime() + 300, (void (*)(int, void *))srchexpire, srch);
+ CBCHAINDOCB(srch, search_commit, srch);
+ for(ln = srch->fnl; ln != NULL; ln = ln->next)
+ fnetsearch(ln->fn, srch, ln);
+ estimatequeue();
+}
+
+void freesearch(struct search *srch)
+{
+ struct srchfnnlist *ln;
+ struct srchlist *sln;
+
+ if(srch->prev != NULL)
+ srch->prev->next = srch->next;
+ if(srch->next != NULL)
+ srch->next->prev = srch->prev;
+ if(srch == searches)
+ searches = srch->next;
+ estimatequeue();
+ if(srch->freetimer != NULL)
+ canceltimer(srch->freetimer);
+ CBCHAINDOCB(srch, search_destroy, srch);
+ CBCHAINFREE(srch, search_eta);
+ CBCHAINFREE(srch, search_commit);
+ CBCHAINFREE(srch, search_result);
+ CBCHAINFREE(srch, search_destroy);
+ while(srch->results != NULL)
+ freesrchres(srch->results);
+ for(sln = searchqueue; sln != NULL; sln = sln->next)
+ {
+ if(sln->srch == srch)
+ {
+ unlinksqueue(sln);
+ break;
+ }
+ }
+ while(srch->fnl != NULL)
+ {
+ ln = srch->fnl;
+ srch->fnl = ln->next;
+ CBCHAINDOCB(ln, searchfnl_destroy, ln);
+ CBCHAINFREE(ln, searchfnl_destroy);
+ putfnetnode(ln->fn);
+ free(ln);
+ }
+ if(srch->sexpr != NULL)
+ putsexpr(srch->sexpr);
+ if(srch->owner != NULL)
+ free(srch->owner);
+ free(srch);
+}
+
+void searchaddfn(struct search *srch, struct fnetnode *fn)
+{
+ struct srchfnnlist *ln;
+
+ for(ln = srch->fnl; ln != NULL; ln = ln->next)
+ {
+ if(ln->fn == fn)
+ return;
+ }
+ ln = smalloc(sizeof(*ln));
+ memset(ln, 0, sizeof(*ln));
+ getfnetnode(ln->fn = fn);
+ CBCHAININIT(ln, searchfnl_destroy);
+ ln->next = srch->fnl;
+ srch->fnl = ln;
+}
+
+static void linksearch(struct search *srch, struct srchlist *prev)
+{
+ struct srchlist *new;
+
+ new = smalloc(sizeof(*new));
+ new->srch = srch;
+ if(prev == NULL)
+ {
+ new->prev = NULL;
+ new->next = searchqueue;
+ if(searchqueue != NULL)
+ searchqueue->prev = new;
+ searchqueue = new;
+ } else {
+ new->prev = prev;
+ if((new->next = prev->next) != NULL)
+ new->next->prev = new;
+ prev->next = new;
+ }
+ GCBCHAINDOCB(newsrchcb, srch);
+ estimatequeue();
+}
+
+/*
+ * queuesearch is also the "scheduler" function - it finds a suitable
+ * place in the queue for the new search. I'll make a weak attempt at
+ * describing the algorithm:
+ * First, we find the first search that doesn't have a lower priority
+ * than this one. If there is no such, we just link this one onto the
+ * end of the queue.
+ * Then, if we have a search of this priority in the queue with the
+ * same owner as the new search, we set lastmine to the search after
+ * that one, otherwise, lastmine is the first search of this
+ * priority. If lastmine is discovered either to not exist (that is,
+ * our last search is at the end of the queue), or to be of lower
+ * priority (higher number), we link it in at the appropriate end.
+ * Then, we find the next search of the same priority and owner as
+ * lastmine, and link this search in before it. That should yield a
+ * 'round-robin-like' scheduling within priority boundaries. I think.
+ */
+void queuesearch(struct search *srch)
+{
+ struct srchlist *cur, *lastmine, *prev;
+ wchar_t *nexto;
+
+ for(prev = NULL, cur = searchqueue; cur != NULL; prev = cur, cur = cur->next)
+ {
+ if(cur->srch->prio >= srch->prio)
+ break;
+ }
+ if(cur == NULL)
+ {
+ linksearch(srch, prev);
+ return;
+ }
+ lastmine = cur;
+ for(; cur != NULL; prev = cur, cur = cur->next)
+ {
+ if(!wcscmp(cur->srch->owner, srch->owner))
+ lastmine = cur->next;
+ if(cur->srch->prio > srch->prio)
+ break;
+ }
+ if((lastmine == NULL) || (lastmine->srch->prio > srch->prio))
+ {
+ linksearch(srch, prev);
+ return;
+ }
+ nexto = lastmine->srch->owner;
+ for(cur = lastmine->next; cur != NULL; prev = cur, cur = cur->next)
+ {
+ if(!wcscmp(cur->srch->owner, nexto))
+ break;
+ if(cur->srch->prio > srch->prio)
+ break;
+ }
+ if(cur == NULL)
+ {
+ linksearch(srch, prev);
+ return;
+ }
+ linksearch(srch, prev);
+}
+
+static int srisvalid(struct srchres *sr, struct sexpr *sexpr)
+{
+ int ret;
+ char *buf;
+ wchar_t *p;
+
+ switch(sexpr->op)
+ {
+ case SOP_FALSE:
+ return(0);
+ case SOP_TRUE:
+ return(1);
+ case SOP_AND:
+ if(!srisvalid(sr, sexpr->l))
+ return(0);
+ return(srisvalid(sr, sexpr->r));
+ case SOP_OR:
+ if(srisvalid(sr, sexpr->l))
+ return(1);
+ return(srisvalid(sr, sexpr->r));
+ case SOP_NOT:
+ return(!srisvalid(sr, sexpr->l));
+ case SOP_NAMERE:
+ if((buf = icwcstombs(sr->filename, "UTF-8")) == NULL)
+ return(0);
+ ret = regexec(&sexpr->d.re.cre, buf, 0, NULL, 0);
+ free(buf);
+ return(!ret);
+ case SOP_LINKRE:
+ p = sr->filename;
+ if(sr->fnet->filebasename != NULL)
+ p = sr->fnet->filebasename(p);
+ if((buf = icwcstombs(p, "UTF-8")) == NULL)
+ return(0);
+ ret = regexec(&sexpr->d.re.cre, buf, 0, NULL, 0);
+ free(buf);
+ return(!ret);
+ case SOP_NAMESS:
+ return(wcsexists(sr->filename, sexpr->d.s));
+ case SOP_LINKSS:
+ p = sr->filename;
+ if(sr->fnet->filebasename != NULL)
+ p = sr->fnet->filebasename(p);
+ return(wcsexists(p, sexpr->d.s));
+ case SOP_SIZELT:
+ return(sr->size < sexpr->d.n);
+ case SOP_SIZEEQ:
+ return(sr->size == sexpr->d.n);
+ case SOP_SIZEGT:
+ return(sr->size > sexpr->d.n);
+ }
+ return(0);
+}
+
+struct srchres *newsrchres(struct fnet *fnet, wchar_t *filename, wchar_t *peerid)
+{
+ struct srchres *sr;
+
+ sr = smalloc(sizeof(*sr));
+ memset(sr, 0, sizeof(*sr));
+ sr->size = -1;
+ sr->slots = -1;
+ sr->fnet = fnet;
+ sr->filename = swcsdup(filename);
+ sr->peerid = swcsdup(peerid);
+ return(sr);
+}
+
+void freesrchres(struct srchres *sr)
+{
+ if(sr->next != NULL)
+ sr->next->prev = sr->prev;
+ if(sr->prev != NULL)
+ sr->prev->next = sr->next;
+ if(sr->srch != NULL)
+ {
+ if(sr->srch->results == sr)
+ sr->srch->results = sr->next;
+ sr->srch->numres--;
+ }
+ if(sr->filename != NULL)
+ free(sr->filename);
+ if(sr->peerid != NULL)
+ free(sr->peerid);
+ if(sr->peernick != NULL)
+ free(sr->peernick);
+ if(sr->fn != NULL)
+ putfnetnode(sr->fn);
+ free(sr);
+}
+
+struct srchres *dupsrchres(struct srchres *sr)
+{
+ struct srchres *new;
+
+ new = smalloc(sizeof(*new));
+ memset(new, 0, sizeof(*new));
+ new->size = sr->size;
+ new->slots = sr->slots;
+ new->fnet = sr->fnet;
+ if(sr->peerid != NULL)
+ new->peerid = swcsdup(sr->peerid);
+ if(sr->peernick != NULL)
+ new->peernick = swcsdup(sr->peernick);
+ if(sr->filename != NULL)
+ new->filename = swcsdup(sr->filename);
+ if(sr->fn != NULL)
+ getfnetnode(new->fn = sr->fn);
+ return(new);
+}
+
+static void linksr(struct search *srch, struct srchres *sr)
+{
+ sr->prev = NULL;
+ sr->next = srch->results;
+ if(srch->results != NULL)
+ srch->results->prev = sr;
+ srch->results = sr;
+ sr->srch = srch;
+ sr->time = ntime() - srch->committime;
+ srch->numres++;
+}
+
+void submitsrchres(struct srchres *sr)
+{
+ struct search *srch;
+ struct srchfnnlist *ln;
+ struct srchres *dsr;
+
+ for(srch = searches; srch != NULL; srch = srch->next)
+ {
+ if(srch->state == SRCH_RUN)
+ {
+ if(!srisvalid(sr, srch->sexpr))
+ continue;
+ for(ln = srch->fnl; ln != NULL; ln = ln->next)
+ {
+ if(((sr->fn != NULL) && (ln->fn == sr->fn)) || ((sr->fn == NULL) && (sr->fnet == ln->fn->fnet)))
+ break;
+ }
+ if(ln != NULL)
+ {
+ dsr = dupsrchres(sr);
+ linksr(srch, dsr);
+ CBCHAINDOCB(srch, search_result, srch, dsr);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#ifndef _SEARCH_H
+#define _SEARCH_H
+
+#include "filenet.h"
+#include "sysevents.h"
+#include <regex.h>
+#include <wchar.h>
+
+#define SOP_FALSE 0
+#define SOP_TRUE 1
+#define SOP_AND 2
+#define SOP_OR 3
+#define SOP_NOT 4
+#define SOP_NAMERE 5
+#define SOP_NAMESS 6
+#define SOP_LINKRE 7
+#define SOP_LINKSS 8
+#define SOP_SIZEGT 9
+#define SOP_SIZELT 10
+#define SOP_SIZEEQ 11
+
+#define SRCH_WAIT 0
+#define SRCH_RUN 1
+
+struct wcslist
+{
+ struct wcslist *next, *prev;
+ wchar_t *str;
+ size_t len;
+};
+
+struct sexpr
+{
+ int refcount;
+ int op;
+ struct sexpr *l, *r;
+ int cost, tcost;
+ union
+ {
+ struct
+ {
+ wchar_t *sre;
+ regex_t cre;
+ int inited;
+ } re;
+ wchar_t *s;
+ int n;
+ } d;
+};
+
+struct srchfnnlist
+{
+ struct srchfnnlist *next;
+ struct fnetnode *fn;
+ void *fnetdata;
+ CBCHAIN(searchfnl_destroy, struct srchfnnlist *ln);
+};
+
+struct search
+{
+ struct search *next, *prev;
+ int id;
+ int state;
+ wchar_t *owner;
+ int prio;
+ time_t eta;
+ double committime;
+ struct sexpr *sexpr;
+ struct srchfnnlist *fnl;
+ struct srchres *results;
+ int numres;
+ struct timer *freetimer;
+ CBCHAIN(search_eta, struct search *srch);
+ CBCHAIN(search_commit, struct search *srch);
+ CBCHAIN(search_result, struct search *srch, struct srchres *sr);
+ CBCHAIN(search_destroy, struct search *srch);
+};
+
+struct srchres
+{
+ struct srchres *next, *prev;
+ struct search *srch;
+ wchar_t *filename;
+ struct fnet *fnet;
+ wchar_t *peerid, *peernick;
+ size_t size;
+ int slots;
+ struct fnetnode *fn;
+ double time;
+};
+
+wchar_t *regexunquotesimple(wchar_t *re);
+struct sexpr *parsesexpr(int argc, wchar_t **argv);
+void optsexpr(struct sexpr *sexpr);
+void getsexpr(struct sexpr *sexpr);
+void putsexpr(struct sexpr *sexpr);
+struct search *newsearch(wchar_t *owner, struct sexpr *sexpr);
+void searchaddfn(struct search *srch, struct fnetnode *fn);
+void queuesearch(struct search *srch);
+void freesearch(struct search *srch);
+struct wcslist *regexfindstrings(wchar_t *re);
+void freesl(struct wcslist **list);
+void slmergemax(struct wcslist **dest, struct wcslist *src);
+struct wcslist *slmergemin(struct wcslist *l1, struct wcslist *l2);
+struct wcslist *findsexprstrs(struct sexpr *sexpr);
+struct srchres *newsrchres(struct fnet *fnet, wchar_t *filename, wchar_t *peerid);
+void freesrchres(struct srchres *sr);
+void submitsrchres(struct srchres *sr);
+struct search *findsearch(int id);
+
+extern struct search *searches;
+EGCBCHAIN(newsrchcb, struct search *);
+
+#endif
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#ifndef _SYSEVENTS_H
+#define _SYSEVENTS_H
+
+#include <sys/types.h>
+#include "auth.h"
+
+#define FD_END -1
+#define FD_PIPE 0
+#define FD_FILE 1
+
+struct timer
+{
+ struct timer *next, *prev;
+ double at;
+ void (*func)(int cancelled, void *data);
+ void *data;
+};
+
+struct child
+{
+ struct child *next, *prev;
+ pid_t pid;
+ void (*callback)(pid_t pid, int status, void *data);
+ void *data;
+ int status;
+ volatile int finished;
+};
+
+void childcallback(pid_t pid, void (*func)(pid_t, int, void *), void *data);
+struct timer *timercallback(double at, void (*func)(int, void *), void *data);
+void canceltimer(struct timer *timer);
+pid_t forksess(uid_t user, struct authhandle *auth, void (*ccbfunc)(pid_t, int, void *), void *data, ...);
+
+#endif
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "tiger.h"
+#include "utils.h"
+
+/*
+ * This won't be a fast implementation of Tiger. For now, I just want
+ * to be done with it and have it somewhat portable.
+ */
+
+static unsigned long long table[];
+
+void inittiger(struct tigerhash *th)
+{
+ th->a = 0x0123456789abcdefULL;
+ th->b = 0xfedcba9876543210ULL;
+ th->c = 0xf096a5b4c3b2e187ULL;
+ th->offset = 0;
+ th->len = 0;
+}
+
+static void round(unsigned long long *a, unsigned long long *b, unsigned long long *c, unsigned long long x, int mul)
+{
+ *c ^= x;
+ *a -= table[((*c >> 0) & 0xff) + 0x0000] ^ table[((*c >> 16) & 0xff) + 0x0100] ^ table[((*c >> 32) & 0xff) + 0x0200] ^ table[((*c >> 48) & 0xff) + 0x0300];
+ *b += table[((*c >> 8) & 0xff) + 0x0300] ^ table[((*c >> 24) & 0xff) + 0x0200] ^ table[((*c >> 40) & 0xff) + 0x0100] ^ table[((*c >> 56) & 0xff) + 0x0000];
+ *b *= mul;
+}
+
+static void pass(unsigned long long *a, unsigned long long *b, unsigned long long *c, unsigned long long *x, int mul)
+{
+ round(a, b, c, x[0], mul);
+ round(b, c, a, x[1], mul);
+ round(c, a, b, x[2], mul);
+ round(a, b, c, x[3], mul);
+ round(b, c, a, x[4], mul);
+ round(c, a, b, x[5], mul);
+ round(a, b, c, x[6], mul);
+ round(b, c, a, x[7], mul);
+}
+
+static void key_schedule(unsigned long long *x)
+{
+ x[0] -= x[7] ^ 0xa5a5a5a5a5a5a5a5ULL;
+ x[1] ^= x[0];
+ x[2] += x[1];
+ x[3] -= x[2] ^ ((~x[1]) << 19);
+ x[4] ^= x[3];
+ x[5] += x[4];
+ x[6] -= x[5] ^ ((~x[4]) >> 23);
+ x[7] ^= x[6];
+ x[0] += x[7];
+ x[1] -= x[0] ^ ((~x[7]) << 19);
+ x[2] ^= x[1];
+ x[3] += x[2];
+ x[4] -= x[3] ^ ((~x[2]) >> 23);
+ x[5] ^= x[4];
+ x[6] += x[5];
+ x[7] -= x[6] ^ 0x0123456789abcdefULL;
+}
+
+static void doblock(struct tigerhash *th)
+{
+ int i, o;
+ unsigned long long x[8], aa, bb, cc;
+
+ for(i = 0; i < 8; i++) {
+ x[i] = 0;
+ for(o = 0; o < 8; o++) {
+ x[i] <<= 8;
+ x[i] |= th->block[(i * 8) + 7 - o];
+ }
+ }
+ aa = th->a;
+ bb = th->b;
+ cc = th->c;
+ pass(&th->a, &th->b, &th->c, x, 5);
+ key_schedule(x);
+ pass(&th->c, &th->a, &th->b, x, 7);
+ key_schedule(x);
+ pass(&th->b, &th->c, &th->a, x, 9);
+ th->a ^= aa;
+ th->b -= bb;
+ th->c += cc;
+ th->offset = 0;
+}
+
+void dotiger(struct tigerhash *th, char *buf, size_t buflen)
+{
+ int taken;
+
+ th->len += buflen;
+ while(buflen > 0) {
+ taken = buflen;
+ if(taken > 64 - th->offset)
+ taken = 64 - th->offset;
+ memcpy(th->block + th->offset, buf, taken);
+ th->offset += taken;
+ buflen -= taken;
+ buf += taken;
+ if(th->offset == 64)
+ doblock(th);
+ }
+}
+
+void synctiger(struct tigerhash *th)
+{
+ int i;
+ unsigned long long buf;
+
+ th->block[th->offset++] = 1;
+ while(th->offset & 7)
+ th->block[th->offset++] = 0;
+ if(th->offset > 56)
+ doblock(th);
+ if(th->offset < 56)
+ memset(th->block + th->offset, 0, 56 - th->offset);
+ buf = th->len << 3;
+ for(i = 0; i < 8; i++) {
+ th->block[56 + i] = buf & 0xff;
+ buf >>= 8;
+ }
+ doblock(th);
+}
+
+void restiger(struct tigerhash *th, char *rbuf)
+{
+ int i;
+ unsigned long long buf;
+
+ buf = th->a;
+ for(i = 0; i < 8; i++) {
+ rbuf[i] = buf & 0xff;
+ buf >>= 8;
+ }
+ buf = th->b;
+ for(; i < 16; i++) {
+ rbuf[i] = buf & 0xff;
+ buf >>= 8;
+ }
+ buf = th->c;
+ for(; i < 24; i++) {
+ rbuf[i] = buf & 0xff;
+ buf >>= 8;
+ }
+}
+
+void inittigertree(struct tigertreehash *tth)
+{
+ tth->blocks = 0;
+ tth->offset = 0;
+ tth->depth = 0;
+}
+
+static void combine(struct tigertreehash *tth)
+{
+ struct tigerhash th;
+
+ inittiger(&th);
+ dotiger(&th, "\001", 1);
+ tth->depth--;
+ dotiger(&th, tth->stack[tth->depth - 1], 24);
+ dotiger(&th, tth->stack[tth->depth], 24);
+ synctiger(&th);
+ restiger(&th, tth->stack[tth->depth - 1]);
+}
+
+static void dotreeblock(struct tigertreehash *tth)
+{
+ struct tigerhash th;
+ int nb;
+
+ inittiger(&th);
+ dotiger(&th, "\0", 1);
+ dotiger(&th, tth->block, tth->offset);
+ synctiger(&th);
+ restiger(&th, tth->stack[tth->depth++]);
+ tth->offset = 0;
+ for(nb = ++tth->blocks; !(nb & 1); nb >>= 1)
+ combine(tth);
+}
+
+void dotigertree(struct tigertreehash *tth, char *buf, size_t buflen)
+{
+ int taken;
+
+ while(buflen > 0) {
+ taken = buflen;
+ if(taken > 1024 - tth->offset)
+ taken = 1024 - tth->offset;
+ memcpy(tth->block + tth->offset, buf, taken);
+ tth->offset += taken;
+ buflen -= taken;
+ buf += taken;
+ if(tth->offset == 1024)
+ dotreeblock(tth);
+ }
+}
+
+void synctigertree(struct tigertreehash *tth)
+{
+ if((tth->offset > 0) || (tth->blocks == 0))
+ dotreeblock(tth);
+ while(tth->depth > 1)
+ combine(tth);
+}
+
+void restigertree(struct tigertreehash *tth, char *rbuf)
+{
+ memcpy(rbuf, tth->stack[0], 24);
+}
+
+static unsigned long long table[1024] = {
+ 0x02aab17cf7e90c5eULL, 0xac424b03e243a8ecULL,
+ 0x72cd5be30dd5fcd3ULL, 0x6d019b93f6f97f3aULL,
+ 0xcd9978ffd21f9193ULL, 0x7573a1c9708029e2ULL,
+ 0xb164326b922a83c3ULL, 0x46883eee04915870ULL,
+ 0xeaace3057103ece6ULL, 0xc54169b808a3535cULL,
+ 0x4ce754918ddec47cULL, 0x0aa2f4dfdc0df40cULL,
+ 0x10b76f18a74dbefaULL, 0xc6ccb6235ad1ab6aULL,
+ 0x13726121572fe2ffULL, 0x1a488c6f199d921eULL,
+ 0x4bc9f9f4da0007caULL, 0x26f5e6f6e85241c7ULL,
+ 0x859079dbea5947b6ULL, 0x4f1885c5c99e8c92ULL,
+ 0xd78e761ea96f864bULL, 0x8e36428c52b5c17dULL,
+ 0x69cf6827373063c1ULL, 0xb607c93d9bb4c56eULL,
+ 0x7d820e760e76b5eaULL, 0x645c9cc6f07fdc42ULL,
+ 0xbf38a078243342e0ULL, 0x5f6b343c9d2e7d04ULL,
+ 0xf2c28aeb600b0ec6ULL, 0x6c0ed85f7254bcacULL,
+ 0x71592281a4db4fe5ULL, 0x1967fa69ce0fed9fULL,
+ 0xfd5293f8b96545dbULL, 0xc879e9d7f2a7600bULL,
+ 0x860248920193194eULL, 0xa4f9533b2d9cc0b3ULL,
+ 0x9053836c15957613ULL, 0xdb6dcf8afc357bf1ULL,
+ 0x18beea7a7a370f57ULL, 0x037117ca50b99066ULL,
+ 0x6ab30a9774424a35ULL, 0xf4e92f02e325249bULL,
+ 0x7739db07061ccae1ULL, 0xd8f3b49ceca42a05ULL,
+ 0xbd56be3f51382f73ULL, 0x45faed5843b0bb28ULL,
+ 0x1c813d5c11bf1f83ULL, 0x8af0e4b6d75fa169ULL,
+ 0x33ee18a487ad9999ULL, 0x3c26e8eab1c94410ULL,
+ 0xb510102bc0a822f9ULL, 0x141eef310ce6123bULL,
+ 0xfc65b90059ddb154ULL, 0xe0158640c5e0e607ULL,
+ 0x884e079826c3a3cfULL, 0x930d0d9523c535fdULL,
+ 0x35638d754e9a2b00ULL, 0x4085fccf40469dd5ULL,
+ 0xc4b17ad28be23a4cULL, 0xcab2f0fc6a3e6a2eULL,
+ 0x2860971a6b943fcdULL, 0x3dde6ee212e30446ULL,
+ 0x6222f32ae01765aeULL, 0x5d550bb5478308feULL,
+ 0xa9efa98da0eda22aULL, 0xc351a71686c40da7ULL,
+ 0x1105586d9c867c84ULL, 0xdcffee85fda22853ULL,
+ 0xccfbd0262c5eef76ULL, 0xbaf294cb8990d201ULL,
+ 0xe69464f52afad975ULL, 0x94b013afdf133e14ULL,
+ 0x06a7d1a32823c958ULL, 0x6f95fe5130f61119ULL,
+ 0xd92ab34e462c06c0ULL, 0xed7bde33887c71d2ULL,
+ 0x79746d6e6518393eULL, 0x5ba419385d713329ULL,
+ 0x7c1ba6b948a97564ULL, 0x31987c197bfdac67ULL,
+ 0xde6c23c44b053d02ULL, 0x581c49fed002d64dULL,
+ 0xdd474d6338261571ULL, 0xaa4546c3e473d062ULL,
+ 0x928fce349455f860ULL, 0x48161bbacaab94d9ULL,
+ 0x63912430770e6f68ULL, 0x6ec8a5e602c6641cULL,
+ 0x87282515337ddd2bULL, 0x2cda6b42034b701bULL,
+ 0xb03d37c181cb096dULL, 0xe108438266c71c6fULL,
+ 0x2b3180c7eb51b255ULL, 0xdf92b82f96c08bbcULL,
+ 0x5c68c8c0a632f3baULL, 0x5504cc861c3d0556ULL,
+ 0xabbfa4e55fb26b8fULL, 0x41848b0ab3baceb4ULL,
+ 0xb334a273aa445d32ULL, 0xbca696f0a85ad881ULL,
+ 0x24f6ec65b528d56cULL, 0x0ce1512e90f4524aULL,
+ 0x4e9dd79d5506d35aULL, 0x258905fac6ce9779ULL,
+ 0x2019295b3e109b33ULL, 0xf8a9478b73a054ccULL,
+ 0x2924f2f934417eb0ULL, 0x3993357d536d1bc4ULL,
+ 0x38a81ac21db6ff8bULL, 0x47c4fbf17d6016bfULL,
+ 0x1e0faadd7667e3f5ULL, 0x7abcff62938beb96ULL,
+ 0xa78dad948fc179c9ULL, 0x8f1f98b72911e50dULL,
+ 0x61e48eae27121a91ULL, 0x4d62f7ad31859808ULL,
+ 0xeceba345ef5ceaebULL, 0xf5ceb25ebc9684ceULL,
+ 0xf633e20cb7f76221ULL, 0xa32cdf06ab8293e4ULL,
+ 0x985a202ca5ee2ca4ULL, 0xcf0b8447cc8a8fb1ULL,
+ 0x9f765244979859a3ULL, 0xa8d516b1a1240017ULL,
+ 0x0bd7ba3ebb5dc726ULL, 0xe54bca55b86adb39ULL,
+ 0x1d7a3afd6c478063ULL, 0x519ec608e7669eddULL,
+ 0x0e5715a2d149aa23ULL, 0x177d4571848ff194ULL,
+ 0xeeb55f3241014c22ULL, 0x0f5e5ca13a6e2ec2ULL,
+ 0x8029927b75f5c361ULL, 0xad139fabc3d6e436ULL,
+ 0x0d5df1a94ccf402fULL, 0x3e8bd948bea5dfc8ULL,
+ 0xa5a0d357bd3ff77eULL, 0xa2d12e251f74f645ULL,
+ 0x66fd9e525e81a082ULL, 0x2e0c90ce7f687a49ULL,
+ 0xc2e8bcbeba973bc5ULL, 0x000001bce509745fULL,
+ 0x423777bbe6dab3d6ULL, 0xd1661c7eaef06eb5ULL,
+ 0xa1781f354daacfd8ULL, 0x2d11284a2b16affcULL,
+ 0xf1fc4f67fa891d1fULL, 0x73ecc25dcb920adaULL,
+ 0xae610c22c2a12651ULL, 0x96e0a810d356b78aULL,
+ 0x5a9a381f2fe7870fULL, 0xd5ad62ede94e5530ULL,
+ 0xd225e5e8368d1427ULL, 0x65977b70c7af4631ULL,
+ 0x99f889b2de39d74fULL, 0x233f30bf54e1d143ULL,
+ 0x9a9675d3d9a63c97ULL, 0x5470554ff334f9a8ULL,
+ 0x166acb744a4f5688ULL, 0x70c74caab2e4aeadULL,
+ 0xf0d091646f294d12ULL, 0x57b82a89684031d1ULL,
+ 0xefd95a5a61be0b6bULL, 0x2fbd12e969f2f29aULL,
+ 0x9bd37013feff9fe8ULL, 0x3f9b0404d6085a06ULL,
+ 0x4940c1f3166cfe15ULL, 0x09542c4dcdf3defbULL,
+ 0xb4c5218385cd5ce3ULL, 0xc935b7dc4462a641ULL,
+ 0x3417f8a68ed3b63fULL, 0xb80959295b215b40ULL,
+ 0xf99cdaef3b8c8572ULL, 0x018c0614f8fcb95dULL,
+ 0x1b14accd1a3acdf3ULL, 0x84d471f200bb732dULL,
+ 0xc1a3110e95e8da16ULL, 0x430a7220bf1a82b8ULL,
+ 0xb77e090d39df210eULL, 0x5ef4bd9f3cd05e9dULL,
+ 0x9d4ff6da7e57a444ULL, 0xda1d60e183d4a5f8ULL,
+ 0xb287c38417998e47ULL, 0xfe3edc121bb31886ULL,
+ 0xc7fe3ccc980ccbefULL, 0xe46fb590189bfd03ULL,
+ 0x3732fd469a4c57dcULL, 0x7ef700a07cf1ad65ULL,
+ 0x59c64468a31d8859ULL, 0x762fb0b4d45b61f6ULL,
+ 0x155baed099047718ULL, 0x68755e4c3d50baa6ULL,
+ 0xe9214e7f22d8b4dfULL, 0x2addbf532eac95f4ULL,
+ 0x32ae3909b4bd0109ULL, 0x834df537b08e3450ULL,
+ 0xfa209da84220728dULL, 0x9e691d9b9efe23f7ULL,
+ 0x0446d288c4ae8d7fULL, 0x7b4cc524e169785bULL,
+ 0x21d87f0135ca1385ULL, 0xcebb400f137b8aa5ULL,
+ 0x272e2b66580796beULL, 0x3612264125c2b0deULL,
+ 0x057702bdad1efbb2ULL, 0xd4babb8eacf84be9ULL,
+ 0x91583139641bc67bULL, 0x8bdc2de08036e024ULL,
+ 0x603c8156f49f68edULL, 0xf7d236f7dbef5111ULL,
+ 0x9727c4598ad21e80ULL, 0xa08a0896670a5fd7ULL,
+ 0xcb4a8f4309eba9cbULL, 0x81af564b0f7036a1ULL,
+ 0xc0b99aa778199abdULL, 0x959f1ec83fc8e952ULL,
+ 0x8c505077794a81b9ULL, 0x3acaaf8f056338f0ULL,
+ 0x07b43f50627a6778ULL, 0x4a44ab49f5eccc77ULL,
+ 0x3bc3d6e4b679ee98ULL, 0x9cc0d4d1cf14108cULL,
+ 0x4406c00b206bc8a0ULL, 0x82a18854c8d72d89ULL,
+ 0x67e366b35c3c432cULL, 0xb923dd61102b37f2ULL,
+ 0x56ab2779d884271dULL, 0xbe83e1b0ff1525afULL,
+ 0xfb7c65d4217e49a9ULL, 0x6bdbe0e76d48e7d4ULL,
+ 0x08df828745d9179eULL, 0x22ea6a9add53bd34ULL,
+ 0xe36e141c5622200aULL, 0x7f805d1b8cb750eeULL,
+ 0xafe5c7a59f58e837ULL, 0xe27f996a4fb1c23cULL,
+ 0xd3867dfb0775f0d0ULL, 0xd0e673de6e88891aULL,
+ 0x123aeb9eafb86c25ULL, 0x30f1d5d5c145b895ULL,
+ 0xbb434a2dee7269e7ULL, 0x78cb67ecf931fa38ULL,
+ 0xf33b0372323bbf9cULL, 0x52d66336fb279c74ULL,
+ 0x505f33ac0afb4eaaULL, 0xe8a5cd99a2cce187ULL,
+ 0x534974801e2d30bbULL, 0x8d2d5711d5876d90ULL,
+ 0x1f1a412891bc038eULL, 0xd6e2e71d82e56648ULL,
+ 0x74036c3a497732b7ULL, 0x89b67ed96361f5abULL,
+ 0xffed95d8f1ea02a2ULL, 0xe72b3bd61464d43dULL,
+ 0xa6300f170bdc4820ULL, 0xebc18760ed78a77aULL,
+ 0xe6a6be5a05a12138ULL, 0xb5a122a5b4f87c98ULL,
+ 0x563c6089140b6990ULL, 0x4c46cb2e391f5dd5ULL,
+ 0xd932addbc9b79434ULL, 0x08ea70e42015aff5ULL,
+ 0xd765a6673e478cf1ULL, 0xc4fb757eab278d99ULL,
+ 0xdf11c6862d6e0692ULL, 0xddeb84f10d7f3b16ULL,
+ 0x6f2ef604a665ea04ULL, 0x4a8e0f0ff0e0dfb3ULL,
+ 0xa5edeef83dbcba51ULL, 0xfc4f0a2a0ea4371eULL,
+ 0xe83e1da85cb38429ULL, 0xdc8ff882ba1b1ce2ULL,
+ 0xcd45505e8353e80dULL, 0x18d19a00d4db0717ULL,
+ 0x34a0cfeda5f38101ULL, 0x0be77e518887caf2ULL,
+ 0x1e341438b3c45136ULL, 0xe05797f49089ccf9ULL,
+ 0xffd23f9df2591d14ULL, 0x543dda228595c5cdULL,
+ 0x661f81fd99052a33ULL, 0x8736e641db0f7b76ULL,
+ 0x15227725418e5307ULL, 0xe25f7f46162eb2faULL,
+ 0x48a8b2126c13d9feULL, 0xafdc541792e76eeaULL,
+ 0x03d912bfc6d1898fULL, 0x31b1aafa1b83f51bULL,
+ 0xf1ac2796e42ab7d9ULL, 0x40a3a7d7fcd2ebacULL,
+ 0x1056136d0afbbcc5ULL, 0x7889e1dd9a6d0c85ULL,
+ 0xd33525782a7974aaULL, 0xa7e25d09078ac09bULL,
+ 0xbd4138b3eac6edd0ULL, 0x920abfbe71eb9e70ULL,
+ 0xa2a5d0f54fc2625cULL, 0xc054e36b0b1290a3ULL,
+ 0xf6dd59ff62fe932bULL, 0x3537354511a8ac7dULL,
+ 0xca845e9172fadcd4ULL, 0x84f82b60329d20dcULL,
+ 0x79c62ce1cd672f18ULL, 0x8b09a2add124642cULL,
+ 0xd0c1e96a19d9e726ULL, 0x5a786a9b4ba9500cULL,
+ 0x0e020336634c43f3ULL, 0xc17b474aeb66d822ULL,
+ 0x6a731ae3ec9baac2ULL, 0x8226667ae0840258ULL,
+ 0x67d4567691caeca5ULL, 0x1d94155c4875adb5ULL,
+ 0x6d00fd985b813fdfULL, 0x51286efcb774cd06ULL,
+ 0x5e8834471fa744afULL, 0xf72ca0aee761ae2eULL,
+ 0xbe40e4cdaee8e09aULL, 0xe9970bbb5118f665ULL,
+ 0x726e4beb33df1964ULL, 0x703b000729199762ULL,
+ 0x4631d816f5ef30a7ULL, 0xb880b5b51504a6beULL,
+ 0x641793c37ed84b6cULL, 0x7b21ed77f6e97d96ULL,
+ 0x776306312ef96b73ULL, 0xae528948e86ff3f4ULL,
+ 0x53dbd7f286a3f8f8ULL, 0x16cadce74cfc1063ULL,
+ 0x005c19bdfa52c6ddULL, 0x68868f5d64d46ad3ULL,
+ 0x3a9d512ccf1e186aULL, 0x367e62c2385660aeULL,
+ 0xe359e7ea77dcb1d7ULL, 0x526c0773749abe6eULL,
+ 0x735ae5f9d09f734bULL, 0x493fc7cc8a558ba8ULL,
+ 0xb0b9c1533041ab45ULL, 0x321958ba470a59bdULL,
+ 0x852db00b5f46c393ULL, 0x91209b2bd336b0e5ULL,
+ 0x6e604f7d659ef19fULL, 0xb99a8ae2782ccb24ULL,
+ 0xccf52ab6c814c4c7ULL, 0x4727d9afbe11727bULL,
+ 0x7e950d0c0121b34dULL, 0x756f435670ad471fULL,
+ 0xf5add442615a6849ULL, 0x4e87e09980b9957aULL,
+ 0x2acfa1df50aee355ULL, 0xd898263afd2fd556ULL,
+ 0xc8f4924dd80c8fd6ULL, 0xcf99ca3d754a173aULL,
+ 0xfe477bacaf91bf3cULL, 0xed5371f6d690c12dULL,
+ 0x831a5c285e687094ULL, 0xc5d3c90a3708a0a4ULL,
+ 0x0f7f903717d06580ULL, 0x19f9bb13b8fdf27fULL,
+ 0xb1bd6f1b4d502843ULL, 0x1c761ba38fff4012ULL,
+ 0x0d1530c4e2e21f3bULL, 0x8943ce69a7372c8aULL,
+ 0xe5184e11feb5ce66ULL, 0x618bdb80bd736621ULL,
+ 0x7d29bad68b574d0bULL, 0x81bb613e25e6fe5bULL,
+ 0x071c9c10bc07913fULL, 0xc7beeb7909ac2d97ULL,
+ 0xc3e58d353bc5d757ULL, 0xeb017892f38f61e8ULL,
+ 0xd4effb9c9b1cc21aULL, 0x99727d26f494f7abULL,
+ 0xa3e063a2956b3e03ULL, 0x9d4a8b9a4aa09c30ULL,
+ 0x3f6ab7d500090fb4ULL, 0x9cc0f2a057268ac0ULL,
+ 0x3dee9d2dedbf42d1ULL, 0x330f49c87960a972ULL,
+ 0xc6b2720287421b41ULL, 0x0ac59ec07c00369cULL,
+ 0xef4eac49cb353425ULL, 0xf450244eef0129d8ULL,
+ 0x8acc46e5caf4deb6ULL, 0x2ffeab63989263f7ULL,
+ 0x8f7cb9fe5d7a4578ULL, 0x5bd8f7644e634635ULL,
+ 0x427a7315bf2dc900ULL, 0x17d0c4aa2125261cULL,
+ 0x3992486c93518e50ULL, 0xb4cbfee0a2d7d4c3ULL,
+ 0x7c75d6202c5ddd8dULL, 0xdbc295d8e35b6c61ULL,
+ 0x60b369d302032b19ULL, 0xce42685fdce44132ULL,
+ 0x06f3ddb9ddf65610ULL, 0x8ea4d21db5e148f0ULL,
+ 0x20b0fce62fcd496fULL, 0x2c1b912358b0ee31ULL,
+ 0xb28317b818f5a308ULL, 0xa89c1e189ca6d2cfULL,
+ 0x0c6b18576aaadbc8ULL, 0xb65deaa91299fae3ULL,
+ 0xfb2b794b7f1027e7ULL, 0x04e4317f443b5bebULL,
+ 0x4b852d325939d0a6ULL, 0xd5ae6beefb207ffcULL,
+ 0x309682b281c7d374ULL, 0xbae309a194c3b475ULL,
+ 0x8cc3f97b13b49f05ULL, 0x98a9422ff8293967ULL,
+ 0x244b16b01076ff7cULL, 0xf8bf571c663d67eeULL,
+ 0x1f0d6758eee30da1ULL, 0xc9b611d97adeb9b7ULL,
+ 0xb7afd5887b6c57a2ULL, 0x6290ae846b984fe1ULL,
+ 0x94df4cdeacc1a5fdULL, 0x058a5bd1c5483affULL,
+ 0x63166cc142ba3c37ULL, 0x8db8526eb2f76f40ULL,
+ 0xe10880036f0d6d4eULL, 0x9e0523c9971d311dULL,
+ 0x45ec2824cc7cd691ULL, 0x575b8359e62382c9ULL,
+ 0xfa9e400dc4889995ULL, 0xd1823ecb45721568ULL,
+ 0xdafd983b8206082fULL, 0xaa7d29082386a8cbULL,
+ 0x269fcd4403b87588ULL, 0x1b91f5f728bdd1e0ULL,
+ 0xe4669f39040201f6ULL, 0x7a1d7c218cf04adeULL,
+ 0x65623c29d79ce5ceULL, 0x2368449096c00bb1ULL,
+ 0xab9bf1879da503baULL, 0xbc23ecb1a458058eULL,
+ 0x9a58df01bb401eccULL, 0xa070e868a85f143dULL,
+ 0x4ff188307df2239eULL, 0x14d565b41a641183ULL,
+ 0xee13337452701602ULL, 0x950e3dcf3f285e09ULL,
+ 0x59930254b9c80953ULL, 0x3bf299408930da6dULL,
+ 0xa955943f53691387ULL, 0xa15edecaa9cb8784ULL,
+ 0x29142127352be9a0ULL, 0x76f0371fff4e7afbULL,
+ 0x0239f450274f2228ULL, 0xbb073af01d5e868bULL,
+ 0xbfc80571c10e96c1ULL, 0xd267088568222e23ULL,
+ 0x9671a3d48e80b5b0ULL, 0x55b5d38ae193bb81ULL,
+ 0x693ae2d0a18b04b8ULL, 0x5c48b4ecadd5335fULL,
+ 0xfd743b194916a1caULL, 0x2577018134be98c4ULL,
+ 0xe77987e83c54a4adULL, 0x28e11014da33e1b9ULL,
+ 0x270cc59e226aa213ULL, 0x71495f756d1a5f60ULL,
+ 0x9be853fb60afef77ULL, 0xadc786a7f7443dbfULL,
+ 0x0904456173b29a82ULL, 0x58bc7a66c232bd5eULL,
+ 0xf306558c673ac8b2ULL, 0x41f639c6b6c9772aULL,
+ 0x216defe99fda35daULL, 0x11640cc71c7be615ULL,
+ 0x93c43694565c5527ULL, 0xea038e6246777839ULL,
+ 0xf9abf3ce5a3e2469ULL, 0x741e768d0fd312d2ULL,
+ 0x0144b883ced652c6ULL, 0xc20b5a5ba33f8552ULL,
+ 0x1ae69633c3435a9dULL, 0x97a28ca4088cfdecULL,
+ 0x8824a43c1e96f420ULL, 0x37612fa66eeea746ULL,
+ 0x6b4cb165f9cf0e5aULL, 0x43aa1c06a0abfb4aULL,
+ 0x7f4dc26ff162796bULL, 0x6cbacc8e54ed9b0fULL,
+ 0xa6b7ffefd2bb253eULL, 0x2e25bc95b0a29d4fULL,
+ 0x86d6a58bdef1388cULL, 0xded74ac576b6f054ULL,
+ 0x8030bdbc2b45805dULL, 0x3c81af70e94d9289ULL,
+ 0x3eff6dda9e3100dbULL, 0xb38dc39fdfcc8847ULL,
+ 0x123885528d17b87eULL, 0xf2da0ed240b1b642ULL,
+ 0x44cefadcd54bf9a9ULL, 0x1312200e433c7ee6ULL,
+ 0x9ffcc84f3a78c748ULL, 0xf0cd1f72248576bbULL,
+ 0xec6974053638cfe4ULL, 0x2ba7b67c0cec4e4cULL,
+ 0xac2f4df3e5ce32edULL, 0xcb33d14326ea4c11ULL,
+ 0xa4e9044cc77e58bcULL, 0x5f513293d934fcefULL,
+ 0x5dc9645506e55444ULL, 0x50de418f317de40aULL,
+ 0x388cb31a69dde259ULL, 0x2db4a83455820a86ULL,
+ 0x9010a91e84711ae9ULL, 0x4df7f0b7b1498371ULL,
+ 0xd62a2eabc0977179ULL, 0x22fac097aa8d5c0eULL,
+ 0xf49fcc2ff1daf39bULL, 0x487fd5c66ff29281ULL,
+ 0xe8a30667fcdca83fULL, 0x2c9b4be3d2fcce63ULL,
+ 0xda3ff74b93fbbbc2ULL, 0x2fa165d2fe70ba66ULL,
+ 0xa103e279970e93d4ULL, 0xbecdec77b0e45e71ULL,
+ 0xcfb41e723985e497ULL, 0xb70aaa025ef75017ULL,
+ 0xd42309f03840b8e0ULL, 0x8efc1ad035898579ULL,
+ 0x96c6920be2b2abc5ULL, 0x66af4163375a9172ULL,
+ 0x2174abdcca7127fbULL, 0xb33ccea64a72ff41ULL,
+ 0xf04a4933083066a5ULL, 0x8d970acdd7289af5ULL,
+ 0x8f96e8e031c8c25eULL, 0xf3fec02276875d47ULL,
+ 0xec7bf310056190ddULL, 0xf5adb0aebb0f1491ULL,
+ 0x9b50f8850fd58892ULL, 0x4975488358b74de8ULL,
+ 0xa3354ff691531c61ULL, 0x0702bbe481d2c6eeULL,
+ 0x89fb24057deded98ULL, 0xac3075138596e902ULL,
+ 0x1d2d3580172772edULL, 0xeb738fc28e6bc30dULL,
+ 0x5854ef8f63044326ULL, 0x9e5c52325add3bbeULL,
+ 0x90aa53cf325c4623ULL, 0xc1d24d51349dd067ULL,
+ 0x2051cfeea69ea624ULL, 0x13220f0a862e7e4fULL,
+ 0xce39399404e04864ULL, 0xd9c42ca47086fcb7ULL,
+ 0x685ad2238a03e7ccULL, 0x066484b2ab2ff1dbULL,
+ 0xfe9d5d70efbf79ecULL, 0x5b13b9dd9c481854ULL,
+ 0x15f0d475ed1509adULL, 0x0bebcd060ec79851ULL,
+ 0xd58c6791183ab7f8ULL, 0xd1187c5052f3eee4ULL,
+ 0xc95d1192e54e82ffULL, 0x86eea14cb9ac6ca2ULL,
+ 0x3485beb153677d5dULL, 0xdd191d781f8c492aULL,
+ 0xf60866baa784ebf9ULL, 0x518f643ba2d08c74ULL,
+ 0x8852e956e1087c22ULL, 0xa768cb8dc410ae8dULL,
+ 0x38047726bfec8e1aULL, 0xa67738b4cd3b45aaULL,
+ 0xad16691cec0dde19ULL, 0xc6d4319380462e07ULL,
+ 0xc5a5876d0ba61938ULL, 0x16b9fa1fa58fd840ULL,
+ 0x188ab1173ca74f18ULL, 0xabda2f98c99c021fULL,
+ 0x3e0580ab134ae816ULL, 0x5f3b05b773645abbULL,
+ 0x2501a2be5575f2f6ULL, 0x1b2f74004e7e8ba9ULL,
+ 0x1cd7580371e8d953ULL, 0x7f6ed89562764e30ULL,
+ 0xb15926ff596f003dULL, 0x9f65293da8c5d6b9ULL,
+ 0x6ecef04dd690f84cULL, 0x4782275fff33af88ULL,
+ 0xe41433083f820801ULL, 0xfd0dfe409a1af9b5ULL,
+ 0x4325a3342cdb396bULL, 0x8ae77e62b301b252ULL,
+ 0xc36f9e9f6655615aULL, 0x85455a2d92d32c09ULL,
+ 0xf2c7dea949477485ULL, 0x63cfb4c133a39ebaULL,
+ 0x83b040cc6ebc5462ULL, 0x3b9454c8fdb326b0ULL,
+ 0x56f56a9e87ffd78cULL, 0x2dc2940d99f42bc6ULL,
+ 0x98f7df096b096e2dULL, 0x19a6e01e3ad852bfULL,
+ 0x42a99ccbdbd4b40bULL, 0xa59998af45e9c559ULL,
+ 0x366295e807d93186ULL, 0x6b48181bfaa1f773ULL,
+ 0x1fec57e2157a0a1dULL, 0x4667446af6201ad5ULL,
+ 0xe615ebcacfb0f075ULL, 0xb8f31f4f68290778ULL,
+ 0x22713ed6ce22d11eULL, 0x3057c1a72ec3c93bULL,
+ 0xcb46acc37c3f1f2fULL, 0xdbb893fd02aaf50eULL,
+ 0x331fd92e600b9fcfULL, 0xa498f96148ea3ad6ULL,
+ 0xa8d8426e8b6a83eaULL, 0xa089b274b7735cdcULL,
+ 0x87f6b3731e524a11ULL, 0x118808e5cbc96749ULL,
+ 0x9906e4c7b19bd394ULL, 0xafed7f7e9b24a20cULL,
+ 0x6509eadeeb3644a7ULL, 0x6c1ef1d3e8ef0edeULL,
+ 0xb9c97d43e9798fb4ULL, 0xa2f2d784740c28a3ULL,
+ 0x7b8496476197566fULL, 0x7a5be3e6b65f069dULL,
+ 0xf96330ed78be6f10ULL, 0xeee60de77a076a15ULL,
+ 0x2b4bee4aa08b9bd0ULL, 0x6a56a63ec7b8894eULL,
+ 0x02121359ba34fef4ULL, 0x4cbf99f8283703fcULL,
+ 0x398071350caf30c8ULL, 0xd0a77a89f017687aULL,
+ 0xf1c1a9eb9e423569ULL, 0x8c7976282dee8199ULL,
+ 0x5d1737a5dd1f7abdULL, 0x4f53433c09a9fa80ULL,
+ 0xfa8b0c53df7ca1d9ULL, 0x3fd9dcbc886ccb77ULL,
+ 0xc040917ca91b4720ULL, 0x7dd00142f9d1dcdfULL,
+ 0x8476fc1d4f387b58ULL, 0x23f8e7c5f3316503ULL,
+ 0x032a2244e7e37339ULL, 0x5c87a5d750f5a74bULL,
+ 0x082b4cc43698992eULL, 0xdf917becb858f63cULL,
+ 0x3270b8fc5bf86ddaULL, 0x10ae72bb29b5dd76ULL,
+ 0x576ac94e7700362bULL, 0x1ad112dac61efb8fULL,
+ 0x691bc30ec5faa427ULL, 0xff246311cc327143ULL,
+ 0x3142368e30e53206ULL, 0x71380e31e02ca396ULL,
+ 0x958d5c960aad76f1ULL, 0xf8d6f430c16da536ULL,
+ 0xc8ffd13f1be7e1d2ULL, 0x7578ae66004ddbe1ULL,
+ 0x05833f01067be646ULL, 0xbb34b5ad3bfe586dULL,
+ 0x095f34c9a12b97f0ULL, 0x247ab64525d60ca8ULL,
+ 0xdcdbc6f3017477d1ULL, 0x4a2e14d4decad24dULL,
+ 0xbdb5e6d9be0a1eebULL, 0x2a7e70f7794301abULL,
+ 0xdef42d8a270540fdULL, 0x01078ec0a34c22c1ULL,
+ 0xe5de511af4c16387ULL, 0x7ebb3a52bd9a330aULL,
+ 0x77697857aa7d6435ULL, 0x004e831603ae4c32ULL,
+ 0xe7a21020ad78e312ULL, 0x9d41a70c6ab420f2ULL,
+ 0x28e06c18ea1141e6ULL, 0xd2b28cbd984f6b28ULL,
+ 0x26b75f6c446e9d83ULL, 0xba47568c4d418d7fULL,
+ 0xd80badbfe6183d8eULL, 0x0e206d7f5f166044ULL,
+ 0xe258a43911cbca3eULL, 0x723a1746b21dc0bcULL,
+ 0xc7caa854f5d7cdd3ULL, 0x7cac32883d261d9cULL,
+ 0x7690c26423ba942cULL, 0x17e55524478042b8ULL,
+ 0xe0be477656a2389fULL, 0x4d289b5e67ab2da0ULL,
+ 0x44862b9c8fbbfd31ULL, 0xb47cc8049d141365ULL,
+ 0x822c1b362b91c793ULL, 0x4eb14655fb13dfd8ULL,
+ 0x1ecbba0714e2a97bULL, 0x6143459d5cde5f14ULL,
+ 0x53a8fbf1d5f0ac89ULL, 0x97ea04d81c5e5b00ULL,
+ 0x622181a8d4fdb3f3ULL, 0xe9bcd341572a1208ULL,
+ 0x1411258643cce58aULL, 0x9144c5fea4c6e0a4ULL,
+ 0x0d33d06565cf620fULL, 0x54a48d489f219ca1ULL,
+ 0xc43e5eac6d63c821ULL, 0xa9728b3a72770dafULL,
+ 0xd7934e7b20df87efULL, 0xe35503b61a3e86e5ULL,
+ 0xcae321fbc819d504ULL, 0x129a50b3ac60bfa6ULL,
+ 0xcd5e68ea7e9fb6c3ULL, 0xb01c90199483b1c7ULL,
+ 0x3de93cd5c295376cULL, 0xaed52edf2ab9ad13ULL,
+ 0x2e60f512c0a07884ULL, 0xbc3d86a3e36210c9ULL,
+ 0x35269d9b163951ceULL, 0x0c7d6e2ad0cdb5faULL,
+ 0x59e86297d87f5733ULL, 0x298ef221898db0e7ULL,
+ 0x55000029d1a5aa7eULL, 0x8bc08ae1b5061b45ULL,
+ 0xc2c31c2b6c92703aULL, 0x94cc596baf25ef42ULL,
+ 0x0a1d73db22540456ULL, 0x04b6a0f9d9c4179aULL,
+ 0xeffdafa2ae3d3c60ULL, 0xf7c8075bb49496c4ULL,
+ 0x9cc5c7141d1cd4e3ULL, 0x78bd1638218e5534ULL,
+ 0xb2f11568f850246aULL, 0xedfabcfa9502bc29ULL,
+ 0x796ce5f2da23051bULL, 0xaae128b0dc93537cULL,
+ 0x3a493da0ee4b29aeULL, 0xb5df6b2c416895d7ULL,
+ 0xfcabbd25122d7f37ULL, 0x70810b58105dc4b1ULL,
+ 0xe10fdd37f7882a90ULL, 0x524dcab5518a3f5cULL,
+ 0x3c9e85878451255bULL, 0x4029828119bd34e2ULL,
+ 0x74a05b6f5d3ceccbULL, 0xb610021542e13ecaULL,
+ 0x0ff979d12f59e2acULL, 0x6037da27e4f9cc50ULL,
+ 0x5e92975a0df1847dULL, 0xd66de190d3e623feULL,
+ 0x5032d6b87b568048ULL, 0x9a36b7ce8235216eULL,
+ 0x80272a7a24f64b4aULL, 0x93efed8b8c6916f7ULL,
+ 0x37ddbff44cce1555ULL, 0x4b95db5d4b99bd25ULL,
+ 0x92d3fda169812fc0ULL, 0xfb1a4a9a90660bb6ULL,
+ 0x730c196946a4b9b2ULL, 0x81e289aa7f49da68ULL,
+ 0x64669a0f83b1a05fULL, 0x27b3ff7d9644f48bULL,
+ 0xcc6b615c8db675b3ULL, 0x674f20b9bcebbe95ULL,
+ 0x6f31238275655982ULL, 0x5ae488713e45cf05ULL,
+ 0xbf619f9954c21157ULL, 0xeabac46040a8eae9ULL,
+ 0x454c6fe9f2c0c1cdULL, 0x419cf6496412691cULL,
+ 0xd3dc3bef265b0f70ULL, 0x6d0e60f5c3578a9eULL,
+ 0x5b0e608526323c55ULL, 0x1a46c1a9fa1b59f5ULL,
+ 0xa9e245a17c4c8ffaULL, 0x65ca5159db2955d7ULL,
+ 0x05db0a76ce35afc2ULL, 0x81eac77ea9113d45ULL,
+ 0x528ef88ab6ac0a0dULL, 0xa09ea253597be3ffULL,
+ 0x430ddfb3ac48cd56ULL, 0xc4b3a67af45ce46fULL,
+ 0x4ececfd8fbe2d05eULL, 0x3ef56f10b39935f0ULL,
+ 0x0b22d6829cd619c6ULL, 0x17fd460a74df2069ULL,
+ 0x6cf8cc8e8510ed40ULL, 0xd6c824bf3a6ecaa7ULL,
+ 0x61243d581a817049ULL, 0x048bacb6bbc163a2ULL,
+ 0xd9a38ac27d44cc32ULL, 0x7fddff5baaf410abULL,
+ 0xad6d495aa804824bULL, 0xe1a6a74f2d8c9f94ULL,
+ 0xd4f7851235dee8e3ULL, 0xfd4b7f886540d893ULL,
+ 0x247c20042aa4bfdaULL, 0x096ea1c517d1327cULL,
+ 0xd56966b4361a6685ULL, 0x277da5c31221057dULL,
+ 0x94d59893a43acff7ULL, 0x64f0c51ccdc02281ULL,
+ 0x3d33bcc4ff6189dbULL, 0xe005cb184ce66af1ULL,
+ 0xff5ccd1d1db99beaULL, 0xb0b854a7fe42980fULL,
+ 0x7bd46a6a718d4b9fULL, 0xd10fa8cc22a5fd8cULL,
+ 0xd31484952be4bd31ULL, 0xc7fa975fcb243847ULL,
+ 0x4886ed1e5846c407ULL, 0x28cddb791eb70b04ULL,
+ 0xc2b00be2f573417fULL, 0x5c9590452180f877ULL,
+ 0x7a6bddfff370eb00ULL, 0xce509e38d6d9d6a4ULL,
+ 0xebeb0f00647fa702ULL, 0x1dcc06cf76606f06ULL,
+ 0xe4d9f28ba286ff0aULL, 0xd85a305dc918c262ULL,
+ 0x475b1d8732225f54ULL, 0x2d4fb51668ccb5feULL,
+ 0xa679b9d9d72bba20ULL, 0x53841c0d912d43a5ULL,
+ 0x3b7eaa48bf12a4e8ULL, 0x781e0e47f22f1ddfULL,
+ 0xeff20ce60ab50973ULL, 0x20d261d19dffb742ULL,
+ 0x16a12b03062a2e39ULL, 0x1960eb2239650495ULL,
+ 0x251c16fed50eb8b8ULL, 0x9ac0c330f826016eULL,
+ 0xed152665953e7671ULL, 0x02d63194a6369570ULL,
+ 0x5074f08394b1c987ULL, 0x70ba598c90b25ce1ULL,
+ 0x794a15810b9742f6ULL, 0x0d5925e9fcaf8c6cULL,
+ 0x3067716cd868744eULL, 0x910ab077e8d7731bULL,
+ 0x6a61bbdb5ac42f61ULL, 0x93513efbf0851567ULL,
+ 0xf494724b9e83e9d5ULL, 0xe887e1985c09648dULL,
+ 0x34b1d3c675370cfdULL, 0xdc35e433bc0d255dULL,
+ 0xd0aab84234131be0ULL, 0x08042a50b48b7eafULL,
+ 0x9997c4ee44a3ab35ULL, 0x829a7b49201799d0ULL,
+ 0x263b8307b7c54441ULL, 0x752f95f4fd6a6ca6ULL,
+ 0x927217402c08c6e5ULL, 0x2a8ab754a795d9eeULL,
+ 0xa442f7552f72943dULL, 0x2c31334e19781208ULL,
+ 0x4fa98d7ceaee6291ULL, 0x55c3862f665db309ULL,
+ 0xbd0610175d53b1f3ULL, 0x46fe6cb840413f27ULL,
+ 0x3fe03792df0cfa59ULL, 0xcfe700372eb85e8fULL,
+ 0xa7be29e7adbce118ULL, 0xe544ee5cde8431ddULL,
+ 0x8a781b1b41f1873eULL, 0xa5c94c78a0d2f0e7ULL,
+ 0x39412e2877b60728ULL, 0xa1265ef3afc9a62cULL,
+ 0xbcc2770c6a2506c5ULL, 0x3ab66dd5dce1ce12ULL,
+ 0xe65499d04a675b37ULL, 0x7d8f523481bfd216ULL,
+ 0x0f6f64fcec15f389ULL, 0x74efbe618b5b13c8ULL,
+ 0xacdc82b714273e1dULL, 0xdd40bfe003199d17ULL,
+ 0x37e99257e7e061f8ULL, 0xfa52626904775aaaULL,
+ 0x8bbbf63a463d56f9ULL, 0xf0013f1543a26e64ULL,
+ 0xa8307e9f879ec898ULL, 0xcc4c27a4150177ccULL,
+ 0x1b432f2cca1d3348ULL, 0xde1d1f8f9f6fa013ULL,
+ 0x606602a047a7ddd6ULL, 0xd237ab64cc1cb2c7ULL,
+ 0x9b938e7225fcd1d3ULL, 0xec4e03708e0ff476ULL,
+ 0xfeb2fbda3d03c12dULL, 0xae0bced2ee43889aULL,
+ 0x22cb8923ebfb4f43ULL, 0x69360d013cf7396dULL,
+ 0x855e3602d2d4e022ULL, 0x073805bad01f784cULL,
+ 0x33e17a133852f546ULL, 0xdf4874058ac7b638ULL,
+ 0xba92b29c678aa14aULL, 0x0ce89fc76cfaadcdULL,
+ 0x5f9d4e0908339e34ULL, 0xf1afe9291f5923b9ULL,
+ 0x6e3480f60f4a265fULL, 0xeebf3a2ab29b841cULL,
+ 0xe21938a88f91b4adULL, 0x57dfeff845c6d3c3ULL,
+ 0x2f006b0bf62caaf2ULL, 0x62f479ef6f75ee78ULL,
+ 0x11a55ad41c8916a9ULL, 0xf229d29084fed453ULL,
+ 0x42f1c27b16b000e6ULL, 0x2b1f76749823c074ULL,
+ 0x4b76eca3c2745360ULL, 0x8c98f463b91691bdULL,
+ 0x14bcc93cf1ade66aULL, 0x8885213e6d458397ULL,
+ 0x8e177df0274d4711ULL, 0xb49b73b5503f2951ULL,
+ 0x10168168c3f96b6bULL, 0x0e3d963b63cab0aeULL,
+ 0x8dfc4b5655a1db14ULL, 0xf789f1356e14de5cULL,
+ 0x683e68af4e51dac1ULL, 0xc9a84f9d8d4b0fd9ULL,
+ 0x3691e03f52a0f9d1ULL, 0x5ed86e46e1878e80ULL,
+ 0x3c711a0e99d07150ULL, 0x5a0865b20c4e9310ULL,
+ 0x56fbfc1fe4f0682eULL, 0xea8d5de3105edf9bULL,
+ 0x71abfdb12379187aULL, 0x2eb99de1bee77b9cULL,
+ 0x21ecc0ea33cf4523ULL, 0x59a4d7521805c7a1ULL,
+ 0x3896f5eb56ae7c72ULL, 0xaa638f3db18f75dcULL,
+ 0x9f39358dabe9808eULL, 0xb7defa91c00b72acULL,
+ 0x6b5541fd62492d92ULL, 0x6dc6dee8f92e4d5bULL,
+ 0x353f57abc4beea7eULL, 0x735769d6da5690ceULL,
+ 0x0a234aa642391484ULL, 0xf6f9508028f80d9dULL,
+ 0xb8e319a27ab3f215ULL, 0x31ad9c1151341a4dULL,
+ 0x773c22a57bef5805ULL, 0x45c7561a07968633ULL,
+ 0xf913da9e249dbe36ULL, 0xda652d9b78a64c68ULL,
+ 0x4c27a97f3bc334efULL, 0x76621220e66b17f4ULL,
+ 0x967743899acd7d0bULL, 0xf3ee5bcae0ed6782ULL,
+ 0x409f753600c879fcULL, 0x06d09a39b5926db6ULL,
+ 0x6f83aeb0317ac588ULL, 0x01e6ca4a86381f21ULL,
+ 0x66ff3462d19f3025ULL, 0x72207c24ddfd3bfbULL,
+ 0x4af6b6d3e2ece2ebULL, 0x9c994dbec7ea08deULL,
+ 0x49ace597b09a8bc4ULL, 0xb38c4766cf0797baULL,
+ 0x131b9373c57c2a75ULL, 0xb1822cce61931e58ULL,
+ 0x9d7555b909ba1c0cULL, 0x127fafdd937d11d2ULL,
+ 0x29da3badc66d92e4ULL, 0xa2c1d57154c2ecbcULL,
+ 0x58c5134d82f6fe24ULL, 0x1c3ae3515b62274fULL,
+ 0xe907c82e01cb8126ULL, 0xf8ed091913e37fcbULL,
+ 0x3249d8f9c80046c9ULL, 0x80cf9bede388fb63ULL,
+ 0x1881539a116cf19eULL, 0x5103f3f76bd52457ULL,
+ 0x15b7e6f5ae47f7a8ULL, 0xdbd7c6ded47e9ccfULL,
+ 0x44e55c410228bb1aULL, 0xb647d4255edb4e99ULL,
+ 0x5d11882bb8aafc30ULL, 0xf5098bbb29d3212aULL,
+ 0x8fb5ea14e90296b3ULL, 0x677b942157dd025aULL,
+ 0xfb58e7c0a390acb5ULL, 0x89d3674c83bd4a01ULL,
+ 0x9e2da4df4bf3b93bULL, 0xfcc41e328cab4829ULL,
+ 0x03f38c96ba582c52ULL, 0xcad1bdbd7fd85db2ULL,
+ 0xbbb442c16082ae83ULL, 0xb95fe86ba5da9ab0ULL,
+ 0xb22e04673771a93fULL, 0x845358c9493152d8ULL,
+ 0xbe2a488697b4541eULL, 0x95a2dc2dd38e6966ULL,
+ 0xc02c11ac923c852bULL, 0x2388b1990df2a87bULL,
+ 0x7c8008fa1b4f37beULL, 0x1f70d0c84d54e503ULL,
+ 0x5490adec7ece57d4ULL, 0x002b3c27d9063a3aULL,
+ 0x7eaea3848030a2bfULL, 0xc602326ded2003c0ULL,
+ 0x83a7287d69a94086ULL, 0xc57a5fcb30f57a8aULL,
+ 0xb56844e479ebe779ULL, 0xa373b40f05dcbce9ULL,
+ 0xd71a786e88570ee2ULL, 0x879cbacdbde8f6a0ULL,
+ 0x976ad1bcc164a32fULL, 0xab21e25e9666d78bULL,
+ 0x901063aae5e5c33cULL, 0x9818b34448698d90ULL,
+ 0xe36487ae3e1e8abbULL, 0xafbdf931893bdcb4ULL,
+ 0x6345a0dc5fbbd519ULL, 0x8628fe269b9465caULL,
+ 0x1e5d01603f9c51ecULL, 0x4de44006a15049b7ULL,
+ 0xbf6c70e5f776cbb1ULL, 0x411218f2ef552bedULL,
+ 0xcb0c0708705a36a3ULL, 0xe74d14754f986044ULL,
+ 0xcd56d9430ea8280eULL, 0xc12591d7535f5065ULL,
+ 0xc83223f1720aef96ULL, 0xc3a0396f7363a51fULL
+};
--- /dev/null
+#ifndef _TIGER_H
+#define _TIGER_H
+
+struct tigerhash {
+ unsigned long long a, b, c;
+ unsigned char block[64];
+ int offset;
+ size_t len;
+};
+
+struct tigertreehash {
+ int blocks;
+ char block[1024];
+ int offset;
+ char stack[64][24];
+ int depth;
+};
+
+void inittiger(struct tigerhash *th);
+void dotiger(struct tigerhash *th, char *buf, size_t buflen);
+void synctiger(struct tigerhash *th);
+void restiger(struct tigerhash *th, char *rbuf);
+void inittigertree(struct tigertreehash *tth);
+void dotigertree(struct tigertreehash *tth, char *buf, size_t buflen);
+void synctigertree(struct tigertreehash *tth);
+void restigertree(struct tigertreehash *tth, char *rbuf);
+
+#endif
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <errno.h>
+#include <sys/wait.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "log.h"
+#include "utils.h"
+#include "sysevents.h"
+#include "auth.h"
+#include "transfer.h"
+#include "module.h"
+
+static void killfilter(struct transfer *transfer);
+
+struct transfer *transfers = NULL;
+int numtransfers = 0;
+GCBCHAIN(newtransfercb, struct transfer *);
+
+void freetransfer(struct transfer *transfer)
+{
+ struct transarg *ta;
+
+ if(transfer == transfers)
+ transfers = transfer->next;
+ if(transfer->next != NULL)
+ transfer->next->prev = transfer->prev;
+ if(transfer->prev != NULL)
+ transfer->prev->next = transfer->next;
+ CBCHAINDOCB(transfer, trans_destroy, transfer);
+ CBCHAINFREE(transfer, trans_ac);
+ CBCHAINFREE(transfer, trans_act);
+ CBCHAINFREE(transfer, trans_p);
+ CBCHAINFREE(transfer, trans_destroy);
+ CBCHAINFREE(transfer, trans_filterout);
+ while((ta = transfer->args) != NULL)
+ {
+ transfer->args = ta->next;
+ free(ta->rec);
+ free(ta->val);
+ free(ta);
+ }
+ if(transfer->filter != -1)
+ killfilter(transfer);
+ if(transfer->etimer != NULL)
+ canceltimer(transfer->etimer);
+ if(transfer->auth != NULL)
+ authputhandle(transfer->auth);
+ if(transfer->peerid != NULL)
+ free(transfer->peerid);
+ if(transfer->peernick != NULL)
+ free(transfer->peernick);
+ if(transfer->path != NULL)
+ free(transfer->path);
+ if(transfer->actdesc != NULL)
+ free(transfer->actdesc);
+ if(transfer->filterbuf != NULL)
+ free(transfer->filterbuf);
+ if(transfer->localend != NULL)
+ {
+ transfer->localend->readcb = NULL;
+ transfer->localend->writecb = NULL;
+ transfer->localend->errcb = NULL;
+ putsock(transfer->localend);
+ }
+ if(transfer->filterout != NULL)
+ {
+ transfer->filterout->readcb = NULL;
+ transfer->filterout->writecb = NULL;
+ transfer->filterout->errcb = NULL;
+ putsock(transfer->filterout);
+ }
+ if(transfer->fn != NULL)
+ putfnetnode(transfer->fn);
+ free(transfer);
+ numtransfers--;
+}
+
+struct transfer *newtransfer(void)
+{
+ struct transfer *new;
+ static int curid = 0;
+
+ new = smalloc(sizeof(*new));
+ memset(new, 0, sizeof(*new));
+ new->id = curid++;
+ new->size = -1;
+ new->endpos = -1;
+ new->filter = -1;
+ CBCHAININIT(new, trans_ac);
+ CBCHAININIT(new, trans_act);
+ CBCHAININIT(new, trans_p);
+ CBCHAININIT(new, trans_destroy);
+ CBCHAININIT(new, trans_filterout);
+ new->next = NULL;
+ new->prev = NULL;
+ time(&new->activity);
+ numtransfers++;
+ return(new);
+}
+
+void transferaddarg(struct transfer *transfer, wchar_t *rec, wchar_t *val)
+{
+ struct transarg *ta;
+
+ ta = smalloc(sizeof(*ta));
+ ta->rec = swcsdup(rec);
+ ta->val = swcsdup(val);
+ ta->next = transfer->args;
+ transfer->args = ta;
+}
+
+void transferattach(struct transfer *transfer, struct transferiface *iface, void *data)
+{
+ if(transfer->iface != NULL)
+ transferdetach(transfer);
+ transfer->iface = iface;
+ transfer->ifacedata = data;
+}
+
+void transferdetach(struct transfer *transfer)
+{
+ if(transfer->iface != NULL)
+ {
+ transfer->iface->detach(transfer, transfer->ifacedata);
+ transfer->iface = NULL;
+ transfer->ifacedata = NULL;
+ }
+}
+
+struct transfer *newupload(struct fnetnode *fn, struct fnet *fnet, wchar_t *nickid, struct transferiface *iface, void *data)
+{
+ struct transfer *transfer;
+
+ transfer = newtransfer();
+ if(fnet != NULL)
+ transfer->fnet = fnet;
+ else
+ transfer->fnet = fn->fnet;
+ transfer->peerid = swcsdup(nickid);
+ transfer->state = TRNS_HS;
+ transfer->dir = TRNSD_UP;
+ if(fn != NULL)
+ getfnetnode(transfer->fn = fn);
+ transferattach(transfer, iface, data);
+ linktransfer(transfer);
+ bumptransfer(transfer);
+ return(transfer);
+}
+
+void linktransfer(struct transfer *transfer)
+{
+ transfer->next = transfers;
+ transfer->prev = NULL;
+ if(transfers != NULL)
+ transfers->prev = transfer;
+ transfers = transfer;
+ GCBCHAINDOCB(newtransfercb, transfer);
+}
+
+void resettransfer(struct transfer *transfer)
+{
+ if(transfer->dir == TRNSD_DOWN)
+ {
+ if(transfer->iface != NULL)
+ transferdetach(transfer);
+ killfilter(transfer);
+ transfersetstate(transfer, TRNS_WAITING);
+ transfersetactivity(transfer, L"reset");
+ return;
+ }
+}
+
+struct transfer *findtransfer(int id)
+{
+ struct transfer *transfer;
+
+ for(transfer = transfers; transfer != NULL; transfer = transfer->next)
+ {
+ if(transfer->id == id)
+ break;
+ }
+ return(transfer);
+}
+
+static void transexpire(int cancelled, struct transfer *transfer)
+{
+ transfer->etimer = NULL;
+ if(!cancelled)
+ bumptransfer(transfer);
+ else
+ transfer->timeout = 0;
+}
+
+static void transferread(struct socket *sk, struct transfer *transfer)
+{
+ if(sockgetdatalen(sk) >= 65536)
+ sk->ignread = 1;
+ if((transfer->iface != NULL) && (transfer->iface->gotdata != NULL))
+ transfer->iface->gotdata(transfer, transfer->ifacedata);
+}
+
+static void transferwrite(struct socket *sk, struct transfer *transfer)
+{
+ if((transfer->iface != NULL) && (transfer->iface->wantdata != NULL))
+ transfer->iface->wantdata(transfer, transfer->ifacedata);
+}
+
+static void transfererr(struct socket *sk, int errno, struct transfer *transfer)
+{
+ if((transfer->iface != NULL) && (transfer->iface->endofdata != NULL))
+ transfer->iface->endofdata(transfer, transfer->ifacedata);
+}
+
+void transferputdata(struct transfer *transfer, void *buf, size_t size)
+{
+ time(&transfer->activity);
+ sockqueue(transfer->localend, buf, size);
+ transfer->curpos += size;
+ CBCHAINDOCB(transfer, trans_p, transfer);
+}
+
+void transferendofdata(struct transfer *transfer)
+{
+ if(transfer->curpos >= transfer->size)
+ {
+ transfersetstate(transfer, TRNS_DONE);
+ transfer->localend->readcb = NULL;
+ transfer->localend->writecb = NULL;
+ transfer->localend->errcb = NULL;
+ putsock(transfer->localend);
+ transfer->localend = NULL;
+ } else {
+ resettransfer(transfer);
+ }
+}
+
+size_t transferdatasize(struct transfer *transfer)
+{
+ return(sockqueuesize(transfer->localend));
+}
+
+void *transfergetdata(struct transfer *transfer, size_t *size)
+{
+ void *buf;
+
+ if(transfer->localend == NULL)
+ return(NULL);
+ transfer->localend->ignread = 0;
+ time(&transfer->activity);
+ if((buf = sockgetinbuf(transfer->localend, size)) == NULL)
+ return(NULL);
+ if((transfer->endpos >= 0) && (transfer->curpos + *size >= transfer->endpos))
+ {
+ *size = transfer->endpos - transfer->curpos;
+ buf = srealloc(buf, *size);
+ }
+ transfer->curpos += *size;
+ CBCHAINDOCB(transfer, trans_p, transfer);
+ return(buf);
+}
+
+void transferprepul(struct transfer *transfer, size_t size, size_t start, size_t end, struct socket *lesk)
+{
+ transfersetsize(transfer, size);
+ transfer->curpos = start;
+ transfer->endpos = end;
+ lesk->ignread = 1;
+ transfersetlocalend(transfer, lesk);
+}
+
+void transferstartul(struct transfer *transfer, struct socket *sk)
+{
+ transfersetstate(transfer, TRNS_MAIN);
+ socksettos(sk, confgetint("transfer", "ultos"));
+ if(transfer->localend != NULL)
+ transfer->localend->ignread = 0;
+}
+
+void transfersetlocalend(struct transfer *transfer, struct socket *sk)
+{
+ if(transfer->localend != NULL)
+ putsock(transfer->localend);
+ getsock(transfer->localend = sk);
+ sk->data = transfer;
+ sk->readcb = (void (*)(struct socket *, void *))transferread;
+ sk->writecb = (void (*)(struct socket *, void *))transferwrite;
+ sk->errcb = (void (*)(struct socket *, int, void *))transfererr;
+}
+
+void bumptransfer(struct transfer *transfer)
+{
+ struct fnetnode *fn;
+ struct fnetpeer *peer;
+ time_t now;
+
+ if((now = time(NULL)) < transfer->timeout)
+ {
+ if(transfer->etimer == NULL)
+ transfer->etimer = timercallback(transfer->timeout, (void (*)(int, void *))transexpire, transfer);
+ return;
+ }
+ if(transfer->etimer != NULL)
+ canceltimer(transfer->etimer);
+ switch(transfer->state)
+ {
+ case TRNS_WAITING:
+ if(transfer->fn != NULL)
+ {
+ fn = transfer->fn;
+ if(fn->state != FNN_EST)
+ {
+ transfer->close = 1;
+ return;
+ }
+ peer = fnetfindpeer(fn, transfer->peerid);
+ } else {
+ peer = NULL;
+ for(fn = fnetnodes; fn != NULL; fn = fn->next)
+ {
+ if((fn->state == FNN_EST) && (fn->fnet == transfer->fnet) && ((peer = fnetfindpeer(fn, transfer->peerid)) != NULL))
+ break;
+ }
+ }
+ transfer->etimer = timercallback(transfer->timeout = (time(NULL) + 30), (void (*)(int, void *))transexpire, transfer);
+ if(now - transfer->lastreq > 30)
+ {
+ if(peer != NULL)
+ {
+ fn->fnet->reqconn(peer);
+ time(&transfer->lastreq);
+ }
+ }
+ break;
+ case TRNS_HS:
+ if(transfer->dir == TRNSD_UP)
+ {
+ if(now - transfer->activity < 60)
+ transfer->etimer = timercallback(transfer->timeout = (time(NULL) + 60), (void (*)(int, void *))transexpire, transfer);
+ else
+ transfer->close = 1;
+ } else if(transfer->dir == TRNSD_DOWN) {
+ if(now - transfer->activity < 60)
+ transfer->etimer = timercallback(transfer->timeout = (time(NULL) + 60), (void (*)(int, void *))transexpire, transfer);
+ else
+ resettransfer(transfer);
+ }
+ break;
+ case TRNS_MAIN:
+ if(transfer->dir == TRNSD_UP)
+ {
+ if(now - transfer->activity < 300)
+ transfer->etimer = timercallback(transfer->timeout = (time(NULL) + 300), (void (*)(int, void *))transexpire, transfer);
+ else
+ transfer->close = 1;
+ }
+ break;
+ }
+}
+
+void transfersetactivity(struct transfer *transfer, wchar_t *desc)
+{
+ time(&transfer->activity);
+ if(desc != NULL)
+ {
+ if(transfer->actdesc != NULL)
+ free(transfer->actdesc);
+ transfer->actdesc = swcsdup(desc);
+ }
+ bumptransfer(transfer);
+ CBCHAINDOCB(transfer, trans_act, transfer);
+}
+
+void transfersetstate(struct transfer *transfer, int newstate)
+{
+ transfer->state = newstate;
+ if(transfer->etimer != NULL)
+ canceltimer(transfer->etimer);
+ transfersetactivity(transfer, NULL);
+ CBCHAINDOCB(transfer, trans_ac, transfer, L"state");
+}
+
+void transfersetnick(struct transfer *transfer, wchar_t *newnick)
+{
+ if(transfer->peernick != NULL)
+ free(transfer->peernick);
+ transfer->peernick = swcsdup(newnick);
+ CBCHAINDOCB(transfer, trans_ac, transfer, L"nick");
+}
+
+void transfersetsize(struct transfer *transfer, int newsize)
+{
+ transfer->size = newsize;
+ CBCHAINDOCB(transfer, trans_ac, transfer, L"size");
+}
+
+void transferseterror(struct transfer *transfer, int error)
+{
+ transfer->error = error;
+ CBCHAINDOCB(transfer, trans_ac, transfer, L"error");
+}
+
+void transfersetpath(struct transfer *transfer, wchar_t *path)
+{
+ if(transfer->path != NULL)
+ free(transfer->path);
+ transfer->path = swcsdup(path);
+ CBCHAINDOCB(transfer, trans_ac, transfer, L"path");
+}
+
+int slotsleft(void)
+{
+ struct transfer *transfer;
+ int slots;
+
+ slots = confgetint("transfer", "slots");
+ for(transfer = transfers; (transfer != NULL) && (slots > 0); transfer = transfer->next)
+ {
+ if((transfer->dir == TRNSD_UP) && (transfer->state == TRNS_MAIN) && !transfer->flags.b.minislot)
+ slots--;
+ }
+ return(slots);
+}
+
+static void killfilter(struct transfer *transfer)
+{
+ if(transfer->filter != -1)
+ {
+ kill(-transfer->filter, SIGHUP);
+ transfer->filter = -1;
+ }
+ if(transfer->localend)
+ {
+ transfer->localend->readcb = NULL;
+ transfer->localend->writecb = NULL;
+ transfer->localend->errcb = NULL;
+ putsock(transfer->localend);
+ transfer->localend = NULL;
+ }
+ if(transfer->filterout)
+ {
+ transfer->filterout->readcb = NULL;
+ putsock(transfer->filterout);
+ transfer->filterout = NULL;
+ }
+ if(transfer->filterbuf)
+ {
+ free(transfer->filterbuf);
+ transfer->filterbuf = NULL;
+ }
+ transfer->filterbufsize = transfer->filterbufdata = 0;
+}
+
+static char *findfilter(struct passwd *pwd)
+{
+ char *path, *filtername;
+
+ if((path = sprintf2("%s/.dcdl-filter", pwd->pw_dir)) != NULL)
+ {
+ if(!access(path, X_OK))
+ return(path);
+ free(path);
+ }
+ if((filtername = icwcstombs(confgetstr("transfer", "filter"), NULL)) == NULL)
+ {
+ flog(LOG_WARNING, "could not convert filter name into local charset: %s", strerror(errno));
+ } else {
+ if(strchr(filtername, '/') == NULL)
+ {
+ if((path = sprintf2("/etc/%s", filtername)) != NULL)
+ {
+ if(!access(path, X_OK))
+ {
+ free(filtername);
+ return(path);
+ }
+ free(path);
+ }
+ if((path = sprintf2("/usr/etc/%s", filtername)) != NULL)
+ {
+ if(!access(path, X_OK))
+ {
+ free(filtername);
+ return(path);
+ }
+ free(path);
+ }
+ if((path = sprintf2("/usr/local/etc/%s", filtername)) != NULL)
+ {
+ if(!access(path, X_OK))
+ {
+ free(filtername);
+ return(path);
+ }
+ free(path);
+ }
+ } else {
+ if(!access(filtername, X_OK))
+ return(filtername);
+ }
+ free(filtername);
+ }
+ return(NULL);
+}
+
+static void filterread(struct socket *sk, struct transfer *transfer)
+{
+ char *buf, *p, *p2;
+ size_t bufsize;
+ wchar_t *cmd, *arg;
+
+ if((buf = sockgetinbuf(sk, &bufsize)) == NULL)
+ return;
+ bufcat(transfer->filterbuf, buf, bufsize);
+ free(buf);
+ if((p = memchr(transfer->filterbuf, '\n', transfer->filterbufdata)) != NULL)
+ {
+ *(p++) = 0;
+ if((p2 = strchr(transfer->filterbuf, ' ')) != NULL)
+ *(p2++) = 0;
+ if((cmd = icmbstowcs(transfer->filterbuf, NULL)) != NULL)
+ {
+ arg = NULL;
+ if(p2 != NULL)
+ {
+ if((arg = icmbstowcs(p2, NULL)) == NULL)
+ flog(LOG_WARNING, "filter sent a string which could not be converted into the local charset: %s: %s", transfer->filterbuf, strerror(errno));
+ }
+ CBCHAINDOCB(transfer, trans_filterout, transfer, cmd, arg);
+ if(arg != NULL)
+ free(arg);
+ free(cmd);
+ } else {
+ flog(LOG_WARNING, "filter sent a string which could not be converted into the local charset: %s: %s", transfer->filterbuf, strerror(errno));
+ }
+ memmove(transfer->filterbuf, p, transfer->filterbufdata -= (p - transfer->filterbuf));
+ }
+}
+
+static void filterexit(pid_t pid, int status, void *data)
+{
+ struct transfer *transfer;
+
+ for(transfer = transfers; transfer != NULL; transfer = transfer->next)
+ {
+ if(transfer->filter == pid)
+ {
+ transfer->filter = -1;
+ killfilter(transfer);
+ if(WEXITSTATUS(status))
+ {
+ resettransfer(transfer);
+ } else {
+ freetransfer(transfer);
+ }
+ break;
+ }
+ }
+}
+
+int forkfilter(struct transfer *transfer)
+{
+ char *filtername, *filename, *peerid, *buf;
+ wchar_t *wfilename;
+ struct passwd *pwent;
+ pid_t pid;
+ int inpipe, outpipe;
+ char **argv;
+ size_t argvsize, argvdata;
+ struct socket *insock, *outsock;
+ struct transarg *ta;
+ char *rec, *val;
+
+ wfilename = transfer->path;
+ if(transfer->fnet->filebasename != NULL)
+ wfilename = transfer->fnet->filebasename(wfilename);
+ if(transfer->auth == NULL)
+ {
+ flog(LOG_WARNING, "tried to fork filter for transfer with NULL authhandle (tranfer %i)", transfer->id);
+ errno = EACCES;
+ return(-1);
+ }
+ if((pwent = getpwuid(transfer->owner)) == NULL)
+ {
+ flog(LOG_WARNING, "no passwd entry for uid %i (found in transfer %i)", transfer->owner, transfer->id);
+ errno = EACCES;
+ return(-1);
+ }
+ if((filtername = findfilter(pwent)) == NULL)
+ {
+ flog(LOG_WARNING, "could not find filter for user %s", pwent->pw_name);
+ errno = ENOENT;
+ return(-1);
+ }
+ if((filename = icwcstombs(wfilename, NULL)) == NULL)
+ {
+ if((buf = icwcstombs(wfilename, "UTF-8")) == NULL)
+ {
+ flog(LOG_WARNING, "could convert transfer filename to neither local charset nor UTF-8: %s", strerror(errno));
+ return(-1);
+ }
+ filename = sprintf2("utf8-%s", buf);
+ free(buf);
+ }
+ if((peerid = icwcstombs(transfer->peerid, NULL)) == NULL)
+ {
+ if((buf = icwcstombs(transfer->peerid, "UTF-8")) == NULL)
+ {
+ flog(LOG_WARNING, "could convert transfer peerid to neither local charset nor UTF-8: %s", strerror(errno));
+ free(filename);
+ return(-1);
+ }
+ peerid = sprintf2("utf8-%s", buf);
+ free(buf);
+ }
+ if((pid = forksess(transfer->owner, transfer->auth, filterexit, NULL, FD_PIPE, 0, O_WRONLY, &inpipe, FD_PIPE, 1, O_RDONLY, &outpipe, FD_FILE, 2, O_RDWR, "/dev/null", FD_END)) < 0)
+ {
+ flog(LOG_WARNING, "could not fork session for filter for transfer %i: %s", transfer->id, strerror(errno));
+ return(-1);
+ }
+ if(pid == 0)
+ {
+ argv = NULL;
+ argvsize = argvdata = 0;
+ buf = sprintf2("%i", transfer->size);
+ addtobuf(argv, filtername);
+ addtobuf(argv, filename);
+ addtobuf(argv, buf);
+ addtobuf(argv, peerid);
+ for(ta = transfer->args; ta != NULL; ta = ta->next)
+ {
+ if((rec = icwcstombs(ta->rec, NULL)) == NULL)
+ continue;
+ if((val = icwcstombs(ta->val, NULL)) == NULL)
+ continue;
+ addtobuf(argv, rec);
+ addtobuf(argv, val);
+ }
+ addtobuf(argv, NULL);
+ execv(filtername, argv);
+ flog(LOG_WARNING, "could not exec filter %s: %s", filtername, strerror(errno));
+ exit(127);
+ }
+ insock = wrapsock(inpipe);
+ outsock = wrapsock(outpipe);
+ /* Really, really strange thing here - sometimes the kernel would
+ * return POLLIN on insock, even though it's a write-side
+ * pipe. The corresponding read on the pipe naturally returns
+ * EBADF, causing doldacond to think there's something wrong with
+ * the fd, and thus it closes it. Until I can find out whyever the
+ * kernel gives a POLLIN on the fd (if I can at all...), I'll just
+ * set ignread on insock for now. */
+ insock->ignread = 1;
+ transfer->filter = pid;
+ transfersetlocalend(transfer, insock);
+ getsock(transfer->filterout = outsock);
+ outsock->data = transfer;
+ outsock->readcb = (void (*)(struct socket *, void *))filterread;
+ putsock(insock);
+ putsock(outsock);
+ free(filtername);
+ free(filename);
+ free(peerid);
+ return(0);
+}
+
+static int run(void)
+{
+ struct transfer *transfer, *next;
+
+ for(transfer = transfers; transfer != NULL; transfer = transfer->next)
+ {
+ if((transfer->endpos >= 0) && (transfer->state == TRNS_MAIN) && (transfer->localend != NULL) && (transfer->localend->state == SOCK_EST) && (transfer->curpos >= transfer->endpos))
+ {
+ if((transfer->iface != NULL) && (transfer->iface->endofdata != NULL))
+ transfer->iface->endofdata(transfer, transfer->ifacedata);
+ closesock(transfer->localend);
+ }
+ }
+ for(transfer = transfers; transfer != NULL; transfer = next)
+ {
+ next = transfer->next;
+ if(transfer->close)
+ {
+ transferdetach(transfer);
+ freetransfer(transfer);
+ continue;
+ }
+ }
+ return(0);
+}
+
+static struct configvar myvars[] =
+{
+ {CONF_VAR_INT, "slots", {.num = 3}},
+ {CONF_VAR_INT, "ultos", {.num = SOCK_TOS_MAXTP}},
+ {CONF_VAR_INT, "dltos", {.num = SOCK_TOS_MAXTP}},
+ {CONF_VAR_STRING, "filter", {.str = L"dc-filter"}},
+ {CONF_VAR_END}
+};
+
+static struct module me =
+{
+ .conf =
+ {
+ .vars = myvars
+ },
+ .name = "transfer",
+ .run = run
+};
+
+MODULE(me);
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#ifndef _TRANSFER_H
+#define _TRANSFER_H
+
+#include <wchar.h>
+#include <sys/types.h>
+
+#include "net.h"
+#include "filenet.h"
+#include "utils.h"
+#include "auth.h"
+
+#define TRNS_WAITING 0
+#define TRNS_HS 1
+#define TRNS_MAIN 2
+#define TRNS_DONE 3
+
+#define TRNSD_UNKNOWN 0
+#define TRNSD_UP 1
+#define TRNSD_DOWN 2
+
+#define TRNSE_NOERROR 0
+#define TRNSE_NOTFOUND 1
+#define TRNSE_NOSLOTS 2
+
+struct transfer;
+
+struct transferiface
+{
+ void (*detach)(struct transfer *transfer, void *data);
+ void (*gotdata)(struct transfer *transfer, void *data);
+ void (*endofdata)(struct transfer *transfer, void *data);
+ void (*wantdata)(struct transfer *transfer, void *data);
+};
+
+struct transarg
+{
+ struct transarg *next;
+ wchar_t *rec;
+ wchar_t *val;
+};
+
+struct transfer
+{
+ struct transfer *next, *prev;
+ int id, close;
+ union
+ {
+ int w;
+ struct
+ {
+ int byop:1;
+ int sgranted:1;
+ int minislot:1;
+ } b;
+ } flags;
+ struct timer *etimer;
+ time_t timeout, activity, lastreq;
+ wchar_t *actdesc;
+ struct fnet *fnet;
+ struct transferiface *iface;
+ wchar_t *peerid, *peernick;
+ wchar_t *path;
+ uid_t owner;
+ int state, dir, error;
+ size_t size, curpos, endpos;
+ struct fnetnode *fn;
+ void *ifacedata;
+ struct socket *localend;
+ struct transarg *args;
+ pid_t filter;
+ struct authhandle *auth;
+ struct socket *filterout;
+ char *filterbuf;
+ size_t filterbufsize, filterbufdata;
+ CBCHAIN(trans_ac, struct transfer *transfer, wchar_t *attrib);
+ CBCHAIN(trans_p, struct transfer *transfer);
+ CBCHAIN(trans_act, struct transfer *transfer);
+ CBCHAIN(trans_destroy, struct transfer *transfer);
+ CBCHAIN(trans_filterout, struct transfer *transfer, wchar_t *cmd, wchar_t *arg);
+};
+
+void freetransfer(struct transfer *transfer);
+struct transfer *newtransfer(void);
+void linktransfer(struct transfer *transfer);
+int slotsleft(void);
+void bumptransfer(struct transfer *transfer);
+struct transfer *findtransfer(int id);
+struct transfer *newupload(struct fnetnode *fn, struct fnet *fnet, wchar_t *nickid, struct transferiface *iface, void *data);
+void transfersetnick(struct transfer *transfer, wchar_t *newnick);
+void transfersetpath(struct transfer *transfer, wchar_t *newpath);
+void transfersetstate(struct transfer *transfer, int newstate);
+void transfersetsize(struct transfer *transfer, int newsize);
+void transferseterror(struct transfer *transfer, int error);
+void transfersetactivity(struct transfer *transfer, wchar_t *desc);
+void transferattach(struct transfer *transfer, struct transferiface *iface, void *data);
+void transferdetach(struct transfer *transfer);
+void resettransfer(struct transfer *transfer);
+void transfersetlocalend(struct transfer *transfer, struct socket *sk);
+void *transfergetdata(struct transfer *transfer, size_t *size);
+void transferaddarg(struct transfer *transfer, wchar_t *rec, wchar_t *val);
+int forkfilter(struct transfer *transfer);
+void transferputdata(struct transfer *transfer, void *buf, size_t size);
+size_t transferdatasize(struct transfer *transfer);
+void transferendofdata(struct transfer *transfer);
+void transferprepul(struct transfer *transfer, size_t size, size_t start, size_t end, struct socket *lesk);
+void transferstartul(struct transfer *transfer, struct socket *sk);
+
+extern struct transfer *transfers;
+EGCBCHAIN(newtransfercb, struct transfer *);
+
+#endif
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#define _GNU_SOURCE
+#include <unistd.h>
+#include <stdlib.h>
+#include <malloc.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <errno.h>
+#include <string.h>
+#include <stdarg.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <iconv.h>
+#include <pwd.h>
+#include <time.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "conf.h"
+#include "auth.h"
+#include "utils.h"
+#include "net.h"
+#include "module.h"
+#include "sysevents.h"
+#include "filenet.h"
+#include "transfer.h"
+#include "search.h"
+#include "client.h"
+
+#define PERM_DISALLOW 1
+#define PERM_ADMIN 2
+#define PERM_FNETCTL 4
+#define PERM_TRANS 8
+#define PERM_TRANSCU 16
+#define PERM_CHAT 32
+#define PERM_SRCH 64
+
+#define NOTIF_END 0
+#define NOTIF_INT 1
+#define NOTIF_STR 2
+#define NOTIF_FLOAT 3
+#define NOTIF_ID 4
+#define NOTIF_PEND 0
+#define NOTIF_WAIT 1
+
+struct uidata;
+
+struct command
+{
+ wchar_t *name;
+ void (*handler)(struct socket *sk, struct uidata *data, int argc, wchar_t **argv);
+};
+
+struct qcommand
+{
+ struct qcommand *next;
+ struct command *cmd;
+ int argc;
+ wchar_t **argv;
+};
+
+struct uiuser
+{
+ struct uiuser *next, *prev;
+ int used;
+ wchar_t *name;
+ unsigned long perms;
+ int delete;
+};
+
+struct notif
+{
+ struct notif *next, *prev;
+ struct uidata *ui;
+ int state;
+ int code;
+ double rlimit;
+ size_t argc;
+ struct timer *exptimer;
+ struct notifarg
+ {
+ int dt;
+ union
+ {
+ int n;
+ wchar_t *s;
+ double d;
+ } d;
+ } *argv;
+};
+
+struct uidata
+{
+ struct uidata *next, *prev;
+ struct socket *sk;
+ struct qcommand *queue, *queuelast;
+ struct authhandle *auth;
+ int close;
+ union
+ {
+ struct
+ {
+ int fnact:1;
+ int fnchat:1;
+ int tract:1;
+ int trprog:1;
+ int srch:1;
+ } b;
+ int w;
+ } notify;
+ wchar_t *username;
+ struct uiuser *userinfo;
+ uid_t uid;
+ struct notif *fnotif, *lnotif;
+ char *fcmdbuf;
+ size_t fcmdbufdata, fcmdbufsize;
+ pid_t fcmdpid;
+ struct socket *fcmdsk;
+ /* Read buffer */
+ char *inbuf;
+ size_t inbufsize, indata;
+ /* Wordset storage */
+ wchar_t **argv;
+ int argc, args;
+ /* WCS conversation stuff */
+ wchar_t *cb; /* Conversation buffer */
+ size_t cbsize, cbdata;
+ iconv_t ichandle;
+ /* Parser data */
+ int ps; /* Parser state */
+ wchar_t *pp; /* Current parse pointer */
+ wchar_t *cw; /* Current word (building storage) */
+ size_t cwsize, cwdata;
+};
+
+static int srcheta(struct search *srch, void *uudata);
+static int srchcommit(struct search *srch, void *uudata);
+static int srchres(struct search *srch, struct srchres *sr, void *uudata);
+
+struct uiuser *users = NULL;
+struct uidata *actives = NULL;
+struct socket *uisocket = NULL;
+
+static wchar_t *quoteword(wchar_t *word)
+{
+ wchar_t *wp, *buf, *bp;
+ int dq, numbs, numc;
+
+ dq = 0;
+ numbs = 0;
+ numc = 0;
+ if(*word == L'\0')
+ {
+ dq = 1;
+ } else {
+ for(wp = word; *wp != L'\0'; wp++)
+ {
+ if(!dq && iswspace(*wp))
+ dq = 1;
+ if((*wp == L'\\') || (*wp == L'\"'))
+ numbs++;
+ numc++;
+ }
+ }
+ if(!dq && !numbs)
+ return(NULL);
+ bp = buf = smalloc(sizeof(wchar_t) * (numc + numbs + (dq?2:0) + 1));
+ if(dq)
+ *(bp++) = L'\"';
+ for(wp = word; *wp != L'\0'; wp++)
+ {
+ if((*wp == L'\\') || (*wp == L'\"'))
+ *(bp++) = L'\\';
+ *(bp++) = *wp;
+ }
+ if(dq)
+ *(bp++) = L'\"';
+ *(bp++) = L'\0';
+ return(buf);
+}
+
+static void sq(struct socket *sk, int cont, ...)
+{
+ int num, freepart;
+ va_list al;
+ char *final;
+ wchar_t *buf;
+ wchar_t *part, *tpart;
+ size_t bufsize, bufdata;
+
+ buf = NULL;
+ bufsize = bufdata = 0;
+ num = 0;
+ va_start(al, cont);
+ while((part = va_arg(al, wchar_t *)) != NULL)
+ {
+ if(*part == L'%')
+ {
+ /*
+ * This kludge demands that all arguments that you call it
+ * with are the size of an int. That happens to be the
+ * case for most datatypes on most platforms and
+ * compilers, but I don't know exactly which ones, and
+ * also a long long is a notable candidate of an arg that
+ * is not the size of an int on 32-bit archs. If it breaks
+ * some existing call on your architecture, please tell
+ * me.
+ */
+ part = vswprintf2(tpart = (part + 1), al);
+ for(; *tpart != L'\0'; tpart++)
+ {
+ if(*tpart == L'%')
+ {
+ if(tpart[1] == L'%')
+ tpart++;
+ else
+ va_arg(al, int);
+ }
+ }
+ freepart = 1;
+ } else {
+ freepart = 0;
+ }
+ if((tpart = quoteword(part)) != NULL)
+ {
+ if(freepart)
+ free(part);
+ part = tpart;
+ freepart = 1;
+ }
+ if((num > 1) || ((num == 1) && !(cont & 1)))
+ addtobuf(buf, L' ');
+ bufcat(buf, part, wcslen(part));
+ if((num == 0) && (cont & 1))
+ addtobuf(buf, L'-');
+ num++;
+ if(freepart)
+ free(part);
+ }
+ if(cont & 2)
+ bufcat(buf, L" \0", 2);
+ else
+ bufcat(buf, L"\r\n\0", 3);
+ if((final = icwcstombs(buf, "utf-8")) == NULL)
+ {
+ flog(LOG_CRIT, "could not convert \"%ls\" into utf-8: %s", buf, strerror(errno));
+ free(buf);
+ return;
+ }
+ va_end(al);
+ free(buf);
+ sockqueue(sk, final, strlen(final));
+ free(final);
+}
+
+struct uiuser *finduser(wchar_t *name)
+{
+ struct uiuser *user;
+
+ for(user = users; user != NULL; user = user->next)
+ {
+ if(!wcscmp(user->name, name))
+ break;
+ }
+ return(user);
+}
+
+static void logout(struct uidata *data)
+{
+ data->userinfo = NULL;
+ if(data->username != NULL)
+ free(data->username);
+ data->username = NULL;
+ if(data->auth != NULL)
+ authputhandle(data->auth);
+ data->auth = NULL;
+}
+
+static int haspriv(struct uidata *data, int perm)
+{
+ if(data->userinfo == NULL)
+ return(0);
+ if(data->userinfo->perms & perm)
+ return(1);
+ else
+ return(0);
+}
+
+/* Useful macros for the command functions: */
+#define haveargs(n) do { if(argc < n) { sq(sk, 0, L"501", L"Wrong number of arguments", NULL); return; } } while(0)
+#define havepriv(p) do { if((data->userinfo == NULL) || ((data->userinfo->perms & (p)) != (p))) { sq(sk, 0, L"502", L"%Unauthorized request - %x needed", (p), NULL); return; } } while(0)
+
+static void cmd_connect(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ int valid;
+
+ if(confgetint("ui", "onlylocal"))
+ {
+ switch(sk->remote->sa_family)
+ {
+ case AF_INET:
+ valid = ((struct sockaddr_in *)sk->remote)->sin_addr.s_addr == INADDR_LOOPBACK;
+ break;
+ case AF_INET6:
+ valid = !memcmp(&((struct sockaddr_in6 *)sk->remote)->sin6_addr, &in6addr_loopback, sizeof(in6addr_loopback));
+ break;
+ default:
+ valid = 0;
+ break;
+ }
+ if(!valid)
+ {
+ sq(sk, 0, L"502", L"Only localhost connections allowed to this host", NULL);
+ sk->close = 1;
+ data->close = 1;
+ return;
+ }
+ }
+ sq(sk, 0, L"200", L"%Dolda Connect daemon v%s", VERSION, NULL);
+}
+
+static void cmd_notfound(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ if((argv != NULL) && (argv[0] != NULL))
+ sq(sk, 0, L"500", L"%Command not found: %ls", argv[0], NULL);
+ else
+ sq(sk, 0, L"500", L"No command", NULL);
+}
+
+static void cmd_shutdown(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ extern volatile int running;
+
+ havepriv(PERM_ADMIN);
+ flog(LOG_NOTICE, "UI shutdown request from %ls, shutting down", data->username);
+ running = 0;
+ sq(sk, 0, L"200", L"Daemon shutting down", NULL);
+}
+
+static void cmd_quit(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ sq(sk, 0, L"200", L"Closing connection", NULL);
+ data->close = 1;
+}
+
+static void cmd_lsauth(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ struct authmech *mech, *prev;
+
+ prev = NULL;
+ for(mech = mechs; mech != NULL; mech = mech->next)
+ {
+ if(mech->enabled)
+ {
+ if(prev != NULL)
+ sq(sk, 1, L"200", prev->name, NULL);
+ prev = mech;
+ }
+ }
+ if(prev == NULL)
+ sq(sk, 0, L"201", L"No authentication methods supported", NULL);
+ else
+ sq(sk, 0, L"200", prev->name, NULL);
+}
+
+static void cmd_login(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ char *buf;
+ int code;
+ struct passwd *pwd;
+
+ haveargs(3);
+ if(data->username != NULL)
+ {
+ if(data->userinfo != NULL)
+ sq(sk, 0, L"503", L"Already logged in", NULL);
+ else
+ sq(sk, 0, L"503", L"Already logging in", NULL);
+ return;
+ }
+ if((buf = icwcstombs(argv[2], NULL)) == NULL)
+ {
+ sq(sk, 0, L"504", L"Could not convert username to locale charset", NULL);
+ return;
+ }
+ data->username = swcsdup(argv[2]);
+ if((pwd = getpwnam(buf)) == NULL)
+ data->uid = -1;
+ else
+ data->uid = pwd->pw_uid;
+ if((data->auth = initauth(argv[1], buf)) == NULL)
+ {
+ if(errno == ENOENT)
+ sq(sk, 0, L"508", L"No such authentication mechanism", NULL);
+ else
+ sq(sk, 0, L"505", L"Could not initialize authentication system", L"%%s", strerror(errno), NULL);
+ free(buf);
+ logout(data);
+ return;
+ }
+ free(buf);
+ switch(authenticate(data->auth, NULL))
+ {
+ case AUTH_SUCCESS:
+ data->userinfo = finduser(data->username);
+ if(data->userinfo == NULL)
+ data->userinfo = finduser(L"default");
+ if(data->uid == -1)
+ {
+ sq(sk, 0, L"506", L"Authentication error", NULL);
+ flog(LOG_INFO, "user %ls authenticated successfully, but no account existed", data->username);
+ logout(data);
+ } else if((data->userinfo == NULL) || (data->userinfo->perms & PERM_DISALLOW)) {
+ sq(sk, 0, L"506", L"Authentication error", NULL);
+ flog(LOG_INFO, "user %ls authenticated successfully, but was not authorized", data->username);
+ logout(data);
+ } else {
+ sq(sk, 0, L"200", L"Welcome", NULL);
+ flog(LOG_INFO, "%ls (UID %i) logged in", data->username, data->uid);
+ }
+ break;
+ case AUTH_DENIED:
+ sq(sk, 0, L"506", L"Authentication error", L"%%ls", (data->auth->text == NULL)?L"":(data->auth->text), NULL);
+ logout(data);
+ break;
+ case AUTH_PASS:
+ switch(data->auth->prompt)
+ {
+ case AUTH_PR_AUTO:
+ code = 300;
+ break;
+ case AUTH_PR_NOECHO:
+ code = 301;
+ break;
+ case AUTH_PR_ECHO:
+ code = 302;
+ break;
+ case AUTH_PR_INFO:
+ code = 303;
+ break;
+ case AUTH_PR_ERROR:
+ code = 304;
+ break;
+ }
+ sq(sk, 0, L"%%i", code, data->auth->text, NULL);
+ break;
+ case AUTH_ERR:
+ sq(sk, 0, L"505", L"System error", L"%%s", strerror(errno), NULL);
+ logout(data);
+ break;
+ default:
+ flog(LOG_WARNING, "BUG? Non-caught return from authenticate in cmd_login");
+ sq(sk, 0, L"505", L"System error", L"%%s", strerror(errno), NULL);
+ logout(data);
+ break;
+ }
+}
+
+static void cmd_pass(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ char *buf;
+ int code;
+
+ haveargs(2);
+ if((buf = icwcstombs(argv[1], NULL)) == NULL)
+ {
+ sq(sk, 0, L"504", L"Could not convert data to locale charset", NULL);
+ return;
+ }
+ if((data->auth == NULL) || (data->userinfo != NULL))
+ {
+ sq(sk, 0, L"507", L"Data not expected", NULL);
+ return;
+ }
+ switch(authenticate(data->auth, buf))
+ {
+ case AUTH_SUCCESS:
+ data->userinfo = finduser(data->username);
+ if(data->userinfo == NULL)
+ data->userinfo = finduser(L"default");
+ if(data->uid == -1)
+ {
+ sq(sk, 0, L"506", L"Authentication error", NULL);
+ flog(LOG_INFO, "user %ls authenticated successfully, but no account existed", data->username);
+ logout(data);
+ } else if((data->userinfo == NULL) || (data->userinfo->perms & PERM_DISALLOW)) {
+ sq(sk, 0, L"506", L"Authentication error", NULL);
+ flog(LOG_INFO, "user %ls authenticated successfully, but was not authorized", data->username);
+ logout(data);
+ } else {
+ sq(sk, 0, L"200", L"Welcome", NULL);
+ flog(LOG_INFO, "%ls (UID %i) logged in", data->username, data->uid);
+ }
+ break;
+ case AUTH_DENIED:
+ sq(sk, 0, L"506", L"Authentication error", L"%%ls", (data->auth->text == NULL)?L"":(data->auth->text), NULL);
+ logout(data);
+ break;
+ case AUTH_PASS:
+ switch(data->auth->prompt)
+ {
+ case AUTH_PR_AUTO:
+ code = 300;
+ break;
+ case AUTH_PR_NOECHO:
+ code = 301;
+ break;
+ case AUTH_PR_ECHO:
+ code = 302;
+ break;
+ case AUTH_PR_INFO:
+ code = 303;
+ break;
+ case AUTH_PR_ERROR:
+ code = 304;
+ break;
+ }
+ sq(sk, 0, L"%%i", code, data->auth->text, NULL);
+ break;
+ case AUTH_ERR:
+ sq(sk, 0, L"505", L"System error", L"%%s", strerror(errno), NULL);
+ logout(data);
+ break;
+ default:
+ flog(LOG_WARNING, "BUG? Non-caught return from authenticate in cmd_pass");
+ sq(sk, 0, L"505", L"System error", L"%%s", strerror(errno), NULL);
+ logout(data);
+ break;
+ }
+ free(buf);
+}
+
+static void cmd_fnetconnect(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ int i;
+ char *buf;
+ int err;
+ struct fnetnode *fn;
+
+ haveargs(3);
+ havepriv(PERM_FNETCTL);
+ if((buf = icwcstombs(argv[2], NULL)) == NULL)
+ {
+ sq(sk, 0, L"504", L"Could not convert data to locale charset", NULL);
+ return;
+ }
+ fn = fnetinitconnect(argv[1], buf);
+ err = errno;
+ free(buf);
+ if(fn == NULL)
+ {
+ if(errno == EPROTONOSUPPORT)
+ sq(sk, 0, L"511", L"No such network name", NULL);
+ else
+ sq(sk, 0, L"509", L"Could not parse the address", L"%%s", strerror(err), NULL);
+ return;
+ }
+ for(i = 3; i < argc - 1; i += 2)
+ {
+ if(!wcscmp(argv[i], L"nick"))
+ fnetsetnick(fn, argv[i + 1]);
+ }
+ linkfnetnode(fn);
+ fnetsetname(fn, argv[2]);
+ putfnetnode(fn);
+ sq(sk, 0, L"200", L"Connection under way", NULL);
+}
+
+static void cmd_lsnodes(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ struct fnetnode *fn;
+
+ if(fnetnodes == NULL)
+ {
+ sq(sk, 0, L"201", L"No connected nodes", NULL);
+ return;
+ }
+ for(fn = fnetnodes; fn != NULL; fn = fn->next)
+ {
+ sq(sk, (fn->next != NULL)?1:0, L"200", L"%%i", fn->id, fn->fnet->name, (fn->name == NULL)?L"":fn->name, L"%%i", fn->numpeers, L"%%i", fn->state, NULL);
+ }
+}
+
+static void cmd_disconnect(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ struct fnetnode *fn;
+ int i;
+
+ haveargs(2);
+ havepriv(PERM_FNETCTL);
+ /* Note - Programmatical user interfaces must only give one
+ * argument per command, the multiple argument form is only for
+ * convenience when manually controlling the daemon via
+ * eg. telnet. The reason is that the return codes aren't clear
+ * enough for the multiple argument form. */
+ for(i = 1; i < argc; i++)
+ {
+ if((fn = findfnetnode(wcstol(argv[i], NULL, 0))) == NULL)
+ {
+ sq(sk, 0, L"510", L"No such node", NULL);
+ return;
+ }
+ killfnetnode(fn);
+ unlinkfnetnode(fn);
+ }
+ sq(sk, 0, L"200", L"Node flagged for disconnection", NULL);
+}
+
+static void cmd_lspa(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ struct fnetnode *fn;
+ struct fnetpeerdatum *datum;
+
+ haveargs(2);
+ if((fn = findfnetnode(wcstol(argv[1], NULL, 0))) == NULL)
+ {
+ sq(sk, 0, L"510", L"No such node", NULL);
+ return;
+ }
+ if(fn->peerdata == NULL)
+ {
+ sq(sk, 0, L"201", L"No data available", NULL);
+ } else {
+ for(datum = fn->peerdata; datum != NULL; datum = datum->next)
+ sq(sk, (datum->next != NULL)?1:0, L"200", datum->id, L"%%i", datum->datatype, NULL);
+ }
+}
+
+static void cmd_lspeers(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ int i;
+ struct fnetnode *fn;
+ struct fnetpeer *peer;
+ wchar_t buf[40];
+
+ haveargs(2);
+ if((fn = findfnetnode(wcstol(argv[1], NULL, 0))) == NULL)
+ {
+ sq(sk, 0, L"510", L"No such node", NULL);
+ return;
+ }
+ if(fn->peers == NULL)
+ {
+ sq(sk, 0, L"201", L"No peers avaiable", NULL);
+ } else {
+ for(peer = fn->peers; peer != NULL; peer = peer->next)
+ {
+ sq(sk, 2 | ((peer->next != NULL)?1:0), L"200", peer->id, peer->nick, NULL);
+ for(i = 0; i < peer->dinum; i++)
+ {
+ if(peer->peerdi[i].datum->datatype == FNPD_INT)
+ sq(sk, 2, peer->peerdi[i].datum->id, L"%%i", peer->peerdi[i].data.num, NULL);
+ /* Note: A long long is not the size of an int, so
+ * sq() can't handle the conversion itself. */
+ if(peer->peerdi[i].datum->datatype == FNPD_LL)
+ {
+ swprintf(buf, 40, L"%lli", peer->peerdi[i].data.lnum);
+ sq(sk, 2, peer->peerdi[i].datum->id, buf, NULL);
+ }
+ if((peer->peerdi[i].datum->datatype == FNPD_STR) && (peer->peerdi[i].data.str != NULL))
+ sq(sk, 2, peer->peerdi[i].datum->id, peer->peerdi[i].data.str, NULL);
+ }
+ sq(sk, 0, NULL);
+ }
+ }
+}
+
+static void cmd_download(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ int i;
+ struct fnet *net;
+ struct fnetnode *fn;
+ struct transfer *transfer;
+ struct fnetpeer *peer;
+
+ haveargs(4);
+ if((argc > 5) && ((argc % 2) == 0))
+ {
+ sq(sk, 0, L"501", L"Must have an even number of arguments", NULL);
+ return;
+ }
+ havepriv(PERM_TRANS);
+ if((*(argv[1]) >= L'0') && (*(argv[1]) <= L'9'))
+ {
+ if((fn = findfnetnode(wcstol(argv[1], NULL, 0))) == NULL)
+ {
+ sq(sk, 0, L"510", L"No such node", NULL);
+ return;
+ }
+ net = fn->fnet;
+ } else {
+ fn = NULL;
+ if((net = findfnet(argv[1])) == NULL)
+ {
+ sq(sk, 0, L"511", L"No such network name", NULL);
+ return;
+ }
+ }
+ transfer = newtransfer();
+ authgethandle(transfer->auth = data->auth);
+ transfer->fnet = net;
+ transfer->peerid = swcsdup(argv[2]);
+ transfer->path = swcsdup(argv[3]);
+ transfer->dir = TRNSD_DOWN;
+ transfer->owner = data->uid;
+ if(fn != NULL)
+ {
+ transfer->fn = fn;
+ getfnetnode(fn);
+ linktransfer(transfer);
+ if(((peer = fnetfindpeer(fn, transfer->peerid)) != NULL) && (peer->nick != NULL))
+ transfersetnick(transfer, peer->nick);
+ } else {
+ linktransfer(transfer);
+ }
+ if(argc > 4)
+ transfersetsize(transfer, wcstol(argv[4], NULL, 0));
+ if(argc > 5)
+ {
+ for(i = 5; i < argc; i += 2)
+ transferaddarg(transfer, argv[i], argv[i + 1]);
+ }
+ sq(sk, 0, L"200", L"%%i", transfer->id, L"Download queued", NULL);
+ transfersetactivity(transfer, L"create");
+}
+
+static void cmd_lstrans(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ struct transfer *transfer, *pt;
+
+ havepriv(PERM_TRANS);
+ pt = NULL;
+ for(transfer = transfers; transfer != NULL; transfer = transfer->next)
+ {
+ if((transfer->dir != TRNSD_DOWN) || (transfer->owner == data->uid))
+ {
+ if(pt != NULL)
+ sq(sk, 1, L"200", L"%%i", pt->id, L"%%i", pt->dir,
+ L"%%i", pt->state, pt->peerid,
+ (pt->peernick == NULL)?L"":(pt->peernick),
+ (pt->path == NULL)?L"":(pt->path),
+ L"%%i", pt->size, L"%%i", pt->curpos,
+ NULL);
+ pt = transfer;
+ }
+ }
+ if(pt == NULL)
+ sq(sk, 0, L"201", L"No transfers", NULL);
+ else
+ sq(sk, 0, L"200", L"%%i", pt->id, L"%%i", pt->dir,
+ L"%%i", pt->state, pt->peerid,
+ (pt->peernick == NULL)?L"":(pt->peernick),
+ (pt->path == NULL)?L"":(pt->path),
+ L"%%i", pt->size, L"%%i", pt->curpos,
+ NULL);
+}
+
+static void cmd_cancel(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ struct transfer *transfer;
+
+ haveargs(2);
+ havepriv(PERM_TRANS);
+ if((transfer = findtransfer(wcstol(argv[1], NULL, 0))) == NULL)
+ {
+ sq(sk, 0, L"512", L"No such transfer", NULL);
+ return;
+ }
+ if((transfer->dir == TRNSD_UP) && !(data->userinfo->perms & PERM_TRANSCU))
+ {
+ sq(sk, 0, L"502", L"You are not allowed to cancel uploads", NULL);
+ return;
+ }
+ if((transfer->dir == TRNSD_DOWN) && (transfer->owner != data->uid))
+ {
+ sq(sk, 0, L"502", L"You do not own that transfer", NULL);
+ return;
+ }
+ transfer->close = 1;
+ sq(sk, 0, L"200", L"Transfer cancelled", NULL);
+}
+
+static void cmd_notify(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ int i, val;
+
+ if((argc % 2) != 1)
+ {
+ sq(sk, 0, L"501", L"Must have an even number of arguments", NULL);
+ return;
+ }
+ for(i = 1; i < argc; i += 2)
+ {
+ if(!wcscasecmp(argv[i + 1], L"on"))
+ val = 1;
+ else
+ val = 0;
+ if(!wcscasecmp(argv[i], L"all"))
+ {
+ if(val)
+ data->notify.w = ~0;
+ else
+ data->notify.w = 0;
+ } else if(!wcscasecmp(argv[i], L"fn:chat")) {
+ data->notify.b.fnchat = val;
+ } else if(!wcscasecmp(argv[i], L"fn:act")) {
+ data->notify.b.fnact = val;
+ } else if(!wcscasecmp(argv[i], L"trans:act")) {
+ data->notify.b.tract = val;
+ } else if(!wcscasecmp(argv[i], L"trans:prog")) {
+ data->notify.b.trprog = val;
+ } else if(!wcscasecmp(argv[i], L"srch:act")) {
+ data->notify.b.srch = val;
+ }
+ }
+ sq(sk, 0, L"200", L"Notification alteration succeeded", NULL);
+}
+
+static void cmd_sendchat(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ struct fnetnode *fn;
+ int public;
+
+ haveargs(5);
+ havepriv(PERM_CHAT);
+ if((fn = findfnetnode(wcstol(argv[1], NULL, 0))) == NULL)
+ {
+ sq(sk, 0, L"510", L"No such node", NULL);
+ return;
+ }
+ public = wcstol(argv[2], NULL, 0);
+ if((public != 0) && (public != 1))
+ {
+ sq(sk, 0, L"509", L"Second argument must be 0 or 1", NULL);
+ return;
+ }
+ if(fn->state != FNN_EST)
+ {
+ sq(sk, 0, L"513", L"Hub is in state FNN_EST", NULL);
+ return;
+ }
+ if(fnetsendchat(fn, public, argv[3], argv[4]))
+ {
+ if(errno == ENOTSUP)
+ sq(sk, 0, L"513", L"This network does not support chatting", NULL);
+ else if(errno == EPERM)
+ sq(sk, 0, L"502", L"This node does not allow you to chat", NULL);
+ else if(errno == EILSEQ)
+ sq(sk, 0, L"504", L"This network could not support all the characters in that message", NULL);
+ else
+ sq(sk, 0, L"505", L"Could not chat", NULL);
+ return;
+ }
+ sq(sk, 0, L"200", L"Chat string sent", NULL);
+}
+
+static void cmd_search(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ struct search *srch;
+ struct fnetnode *fn;
+ struct sexpr *sexpr;
+ int i;
+
+ haveargs(3);
+ havepriv(PERM_SRCH);
+ srch = newsearch(data->username, NULL);
+ for(i = 1; i < argc; i++)
+ {
+ if(!wcscmp(argv[i], L"all"))
+ {
+ for(fn = fnetnodes; fn != NULL; fn = fn->next)
+ {
+ if(fn->state == FNN_EST)
+ searchaddfn(srch, fn);
+ }
+ i++;
+ break;
+ } else if(!wcscmp(argv[i], L"prio")) {
+ if(++i == argc)
+ {
+ sq(sk, 0, L"501", L"No argument to prio", NULL);
+ freesearch(srch);
+ return;
+ }
+ srch->prio = wcstol(argv[i], NULL, 0);
+ } else if(iswdigit(*argv[i])) {
+ if((fn = findfnetnode(wcstol(argv[i], NULL, 0))) == NULL)
+ {
+ sq(sk, 0, L"510", L"No such node", NULL);
+ freesearch(srch);
+ return;
+ }
+ searchaddfn(srch, fn);
+ } else {
+ break;
+ }
+ }
+ if(srch->fnl == NULL)
+ {
+ sq(sk, 0, L"501", L"No fnetnodes to search found on line", NULL);
+ freesearch(srch);
+ return;
+ }
+ if(i == argc)
+ {
+ sq(sk, 0, L"501", L"No search expression found on line", NULL);
+ freesearch(srch);
+ return;
+ }
+ if((sexpr = parsesexpr(argc - i, argv + i)) == NULL)
+ {
+ sq(sk, 0, L"509", L"Could not parse search expression", NULL);
+ freesearch(srch);
+ return;
+ }
+ optsexpr(sexpr);
+ getsexpr(srch->sexpr = sexpr);
+ queuesearch(srch);
+ CBREG(srch, search_eta, srcheta, NULL, NULL);
+ CBREG(srch, search_commit, srchcommit, NULL, NULL);
+ CBREG(srch, search_result, srchres, NULL, NULL);
+ sq(sk, 0, L"200", L"%%i", srch->id, L"%%i", srch->eta - time(NULL), NULL);
+ putsexpr(sexpr);
+}
+
+static void cmd_lssrch(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ struct search *srch, *pt;
+ time_t now;
+
+ havepriv(PERM_SRCH);
+ pt = NULL;
+ now = time(NULL);
+ for(srch = searches; srch != NULL; srch = srch->next)
+ {
+ if(!wcscmp(srch->owner, data->username))
+ {
+ if(pt != NULL)
+ sq(sk, 1, L"200", L"%%i", pt->id, L"%%i", pt->state, L"%%i", pt->eta - now, L"%%i", pt->numres, NULL);
+ pt = srch;
+ }
+ }
+ if(pt == NULL)
+ sq(sk, 0, L"201", L"No searches", NULL);
+ else
+ sq(sk, 0, L"200", L"%%i", pt->id, L"%%i", pt->state, L"%%i", pt->eta - now, L"%%i", pt->numres, NULL);
+}
+
+static void cmd_lssr(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ struct search *srch;
+ struct srchres *sr;
+ wchar_t buf[64];
+
+ haveargs(2);
+ havepriv(PERM_SRCH);
+ if((srch = findsearch(wcstol(argv[1], NULL, 0))) == NULL)
+ {
+ sq(sk, 0, L"514", L"No such search", NULL);
+ return;
+ }
+ if(srch->results == NULL)
+ {
+ sq(sk, 0, L"201", L"No results", NULL);
+ } else {
+ for(sr = srch->results; sr != NULL; sr = sr->next)
+ {
+ swprintf(buf, 64, L"%f", sr->time);
+ sq(sk, (sr->next != NULL)?1:0, L"200", L"%%ls", sr->filename, sr->fnet->name, L"%%ls", sr->peerid, L"%%i", sr->size, L"%%i", sr->slots, L"%%i", (sr->fn == NULL)?-1:(sr->fn->id), buf, NULL);
+ }
+ }
+}
+
+static void cmd_cansrch(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ struct search *srch;
+ int i;
+
+ haveargs(2);
+ havepriv(PERM_SRCH);
+ /* Note - Programmatical user interfaces must only give one
+ * argument per command, the multiple argument form is only for
+ * convenience when manually controlling the daemon via
+ * eg. telnet. The reason is that the return codes aren't clear
+ * enough for the multiple argument form. */
+ for(i = 1; i < argc; i++)
+ {
+ if((srch = findsearch(wcstol(argv[i], NULL, 0))) == NULL)
+ {
+ sq(sk, 0, L"514", L"No such search", NULL);
+ return;
+ }
+ freesearch(srch);
+ }
+ sq(sk, 0, L"200", L"Search cancelled", NULL);
+}
+
+static void fcmdread(struct socket *sk, struct uidata *data)
+{
+ char *buf;
+ size_t bufsize;
+
+ if((buf = sockgetinbuf(sk, &bufsize)) == NULL)
+ return;
+ bufcat(data->fcmdbuf, buf, bufsize);
+ free(buf);
+}
+
+static void fcmderr(struct socket *sk, int err, struct uidata *data)
+{
+ wchar_t *wbuf, *p, *p2;
+
+ if(err)
+ {
+ flog(LOG_WARNING, "error occurred on filtercmd pipe socket: %s", strerror(err));
+ kill(-data->fcmdpid, SIGHUP);
+ putsock(data->fcmdsk);
+ data->fcmdsk = NULL;
+ if(data->fcmdbuf != NULL)
+ {
+ free(data->fcmdbuf);
+ data->fcmdbuf = NULL;
+ }
+ data->fcmdbufsize = data->fcmdbufdata = 0;
+ sq(data->sk, 0, L"505", L"An error occurred on the pipe to the filtercmd", L"%%s", strerror(err), NULL);
+ return;
+ }
+ putsock(data->fcmdsk);
+ data->fcmdsk = NULL;
+ data->fcmdpid = 0;
+ if(data->fcmdbuf == NULL)
+ {
+ wbuf = swcsdup(L"");
+ } else {
+ addtobuf(data->fcmdbuf, 0);
+ wbuf = icmbstowcs(data->fcmdbuf, NULL);
+ free(data->fcmdbuf);
+ }
+ data->fcmdbuf = NULL;
+ data->fcmdbufsize = data->fcmdbufdata = 0;
+ if(wbuf == NULL)
+ {
+ sq(data->sk, 0, L"504", L"Filtercmd sent data which could not be converted from the local charset", NULL);
+ return;
+ }
+ p = wbuf;
+ for(p2 = wcschr(p, L'\n'); p2 != NULL; p2 = wcschr(p, L'\n'))
+ {
+ *(p2++) = L'\0';
+ sq(data->sk, (*p2 == L'\0')?0:1, L"200", L"%%ls", p, NULL);
+ p = p2;
+ }
+ if(*p == L'\0')
+ {
+ if(p == wbuf)
+ sq(data->sk, 0, L"201", L"No data returned", NULL);
+ } else {
+ sq(data->sk, 0, L"200", L"%%ls", p, NULL);
+ }
+ free(wbuf);
+}
+
+static void cmd_filtercmd(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ int i;
+ pid_t pid;
+ int pipe;
+ char **cargv, **pp;
+ char *filtercmd, *argbuf;
+ size_t cargvsize, cargvdata;
+ struct passwd *pwent;
+
+ haveargs(2);
+ havepriv(PERM_TRANS);
+ if((pwent = getpwuid(data->uid)) == NULL)
+ {
+ flog(LOG_WARNING, "no passwd entry for UI user %i", data->uid);
+ sq(sk, 0, L"505", L"System error - Could not fork session", "Internal error", NULL);
+ return;
+ }
+ if((filtercmd = findfile(icswcstombs(confgetstr("ui", "filtercmd"), NULL, NULL), "dcdl-filtercmd", pwent->pw_dir)) == NULL)
+ {
+ flog(LOG_WARNING, "could not find filtercmd executable for user %s", pwent->pw_name);
+ sq(sk, 0, L"505", L"System error - Could not fork session", L"Could not find filtercmd executable", NULL);
+ return;
+ }
+ cargv = NULL;
+ cargvsize = cargvdata = 0;
+ addtobuf(cargv, filtercmd);
+ for(i = 1; i < argc; i++)
+ {
+ if((argbuf = icwcstombs(argv[i], NULL)) == NULL)
+ {
+ for(i = 0; i < cargvdata; i++)
+ free(cargv[i]);
+ free(cargv);
+ sq(sk, 0, L"504", L"%Could not convert argument %i into local character set", i, L"%%s", strerror(errno), NULL);
+ return;
+ }
+ addtobuf(cargv, argbuf);
+ }
+ addtobuf(cargv, NULL);
+ if((pid = forksess(data->uid, data->auth, NULL, NULL, FD_FILE, 0, O_RDWR, "/dev/null", FD_PIPE, 1, O_RDONLY, &pipe, FD_FILE, 2, O_RDWR, "/dev/null", FD_END)) < 0)
+ {
+ flog(LOG_WARNING, "could not fork session in filtercmd: %s", strerror(errno));
+ sq(sk, 0, L"505", L"System error - Could not fork session", L"%%s", strerror(errno), NULL);
+ return;
+ }
+ if(pid == 0)
+ {
+ execv(filtercmd, cargv);
+ flog(LOG_WARNING, "could not exec filtercmd %s: %s", filtercmd, strerror(errno));
+ exit(127);
+ }
+ for(pp = cargv; *pp; pp++)
+ free(*pp);
+ free(cargv);
+ data->fcmdsk = wrapsock(pipe);
+ data->fcmdpid = pid;
+ if(data->fcmdbuf != NULL)
+ {
+ free(data->fcmdbuf);
+ data->fcmdbuf = NULL;
+ }
+ data->fcmdbufsize = data->fcmdbufdata = 0;
+ data->fcmdsk->data = data;
+ data->fcmdsk->readcb = (void (*)(struct socket *, void *))fcmdread;
+ data->fcmdsk->errcb = (void (*)(struct socket *, int, void *))fcmderr;
+}
+
+static void cmd_lstrarg(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ struct transfer *transfer;
+ struct transarg *ta;
+
+ haveargs(2);
+ havepriv(PERM_TRANS);
+ if((transfer = findtransfer(wcstol(argv[1], NULL, 0))) == NULL)
+ {
+ sq(sk, 0, L"512", L"No such transfer", NULL);
+ return;
+ }
+ if((transfer->dir == TRNSD_DOWN) && (transfer->owner != data->uid))
+ {
+ sq(sk, 0, L"502", L"You do not own that transfer", NULL);
+ return;
+ }
+ if(transfer->args == NULL)
+ {
+ sq(sk, 0, L"201", L"Transfer has no arguments", NULL);
+ } else {
+ for(ta = transfer->args; ta != NULL; ta = ta->next)
+ sq(sk, ta->next != NULL, L"200", L"%%ls", ta->rec, L"%%ls", ta->val, NULL);
+ }
+}
+
+static void cmd_hashstatus(struct socket *sk, struct uidata *data, int argc, wchar_t **argv)
+{
+ struct sharecache *node;
+ int total, hashed;
+
+ total = hashed = 0;
+ for(node = shareroot->child; node != NULL; node = nextscnode(node))
+ {
+ if(node->f.b.type == FILE_REG)
+ {
+ total++;
+ if(node->f.b.hastth)
+ hashed++;
+ }
+ }
+ sq(sk, 0, L"200", L"%%i", total, L"tth", L"%%i", hashed, NULL);
+}
+
+#undef haveargs
+#undef havepriv
+
+/*
+ * Reserved command numbers for nameless commands:
+ * 0: Issued when a client has connected
+ * 1: Issued when a named command couldn't be found
+ */
+
+static struct command commands[] =
+{
+ {NULL, cmd_connect},
+ {NULL, cmd_notfound},
+ {L"shutdown", cmd_shutdown},
+ {L"quit", cmd_quit},
+ {L"lsauth", cmd_lsauth},
+ {L"login", cmd_login},
+ {L"pass", cmd_pass},
+ {L"cnct", cmd_fnetconnect},
+ {L"lsnodes", cmd_lsnodes},
+ {L"dcnct", cmd_disconnect},
+ {L"lspa", cmd_lspa},
+ {L"lspeers", cmd_lspeers},
+ {L"download", cmd_download},
+ {L"lstrans", cmd_lstrans},
+ {L"cancel", cmd_cancel},
+ {L"notify", cmd_notify},
+ {L"sendchat", cmd_sendchat},
+ {L"search", cmd_search},
+ {L"lssrch", cmd_lssrch},
+ {L"lssr", cmd_lssr},
+ {L"cansrch", cmd_cansrch},
+ {L"filtercmd", cmd_filtercmd},
+ {L"lstrarg", cmd_lstrarg},
+ {L"hashstatus", cmd_hashstatus},
+ {NULL, NULL}
+};
+
+static void freequeuecmd(struct qcommand *qcmd)
+{
+ int i;
+
+ if(qcmd->argv != NULL)
+ {
+ for(i = 0; i < qcmd->argc; i++)
+ free(qcmd->argv[i]);
+ free(qcmd->argv);
+ }
+ free(qcmd);
+}
+
+static struct qcommand *unlinkqcmd(struct uidata *data)
+{
+ struct qcommand *qcmd;
+
+ qcmd = data->queue;
+ if(qcmd != NULL)
+ {
+ data->queue = qcmd->next;
+ if(qcmd == data->queuelast)
+ data->queuelast = qcmd->next;
+ }
+ return(qcmd);
+}
+
+static struct notif *newnotif(struct uidata *data, int code, ...)
+{
+ struct notif *notif;
+ va_list args;
+ int dt, ca;
+
+ notif = smalloc(sizeof(*notif));
+ memset(notif, 0, sizeof(*notif));
+ notif->rlimit = 0.0;
+ notif->ui = data;
+ notif->code = code;
+ va_start(args, code);
+ while((dt = va_arg(args, int)) != NOTIF_END)
+ {
+ ca = notif->argc;
+ notif->argv = realloc(notif->argv, sizeof(*notif->argv) * ++notif->argc);
+ notif->argv[ca].dt = dt;
+ switch(dt)
+ {
+ case NOTIF_INT:
+ case NOTIF_ID:
+ notif->argv[ca].d.n = va_arg(args, int);
+ break;
+ case NOTIF_STR:
+ notif->argv[ca].d.s = wcsdup(va_arg(args, wchar_t *));
+ break;
+ case NOTIF_FLOAT:
+ notif->argv[ca].d.d = va_arg(args, double);
+ break;
+ }
+ }
+ va_end(args);
+ notif->next = NULL;
+ notif->prev = data->lnotif;
+ if(data->lnotif != NULL)
+ data->lnotif->next = notif;
+ else
+ data->fnotif = notif;
+ data->lnotif = notif;
+ return(notif);
+}
+
+static void freenotif(struct notif *notif)
+{
+ int i;
+
+ if(notif->next != NULL)
+ notif->next->prev = notif->prev;
+ if(notif->prev != NULL)
+ notif->prev->next = notif->next;
+ if(notif == notif->ui->fnotif)
+ notif->ui->fnotif = notif->next;
+ if(notif == notif->ui->lnotif)
+ notif->ui->lnotif = notif->prev;
+ if(notif->exptimer != NULL)
+ canceltimer(notif->exptimer);
+ for(i = 0; i < notif->argc; i++)
+ {
+ if(notif->argv[i].dt == NOTIF_STR)
+ free(notif->argv[i].d.s);
+ }
+ if(notif->argv != NULL)
+ free(notif->argv);
+ free(notif);
+}
+
+static void notifexpire(int cancelled, struct notif *notif)
+{
+ notif->exptimer = NULL;
+ if(!cancelled)
+ freenotif(notif);
+}
+
+static struct notif *findnotif(struct notif *notif, int dir, int state, int code, int id)
+{
+ int i, cont;
+
+ for(; notif != NULL; notif = (dir?notif->next:notif->prev))
+ {
+ if((notif->code == code) && ((state < 0) || (state == notif->state)))
+ {
+ cont = 0;
+ if(id >= 0)
+ {
+ for(i = 0; i < notif->argc; i++)
+ {
+ if((notif->argv[i].dt == NOTIF_ID) && (notif->argv[i].d.n != id))
+ {
+ cont = 1;
+ break;
+ }
+ }
+ }
+ if(cont)
+ continue;
+ break;
+ }
+ }
+ return(notif);
+}
+
+static void freeuidata(struct uidata *data)
+{
+ int i;
+ struct qcommand *qcmd;
+
+ if(data->next != NULL)
+ data->next->prev = data->prev;
+ if(data->prev != NULL)
+ data->prev->next = data->next;
+ if(data == actives)
+ actives = data->next;
+ data->sk->readcb = NULL;
+ data->sk->errcb = NULL;
+ putsock(data->sk);
+ while((qcmd = unlinkqcmd(data)) != NULL)
+ freequeuecmd(qcmd);
+ iconv_close(data->ichandle);
+ if(data->cw != NULL)
+ free(data->cw);
+ if(data->cb != NULL)
+ free(data->cb);
+ if(data->argv != NULL)
+ {
+ for(i = 0; i < data->argc; i++)
+ free(data->argv[i]);
+ free(data->argv);
+ }
+ if(data->auth != NULL)
+ authputhandle(data->auth);
+ if(data->username != NULL)
+ {
+ if(data->userinfo != NULL)
+ flog(LOG_INFO, "%ls logged out", data->username);
+ free(data->username);
+ }
+ free(data->inbuf);
+ while(data->fnotif != NULL)
+ freenotif(data->fnotif);
+ if(data->fcmdbuf != NULL)
+ free(data->fcmdbuf);
+ if(data->fcmdpid != 0)
+ kill(-data->fcmdpid, SIGHUP);
+ if(data->fcmdsk != NULL)
+ putsock(data->fcmdsk);
+ free(data);
+}
+
+static void queuecmd(struct uidata *data, struct command *cmd, int argc, wchar_t **argv)
+{
+ struct qcommand *new;
+
+ new = smalloc(sizeof(*new));
+ new->cmd = cmd;
+ new->argc = argc;
+ new->argv = argv;
+ new->next = NULL;
+ if(data->queuelast != NULL)
+ data->queuelast->next = new;
+ data->queuelast = new;
+ if(data->queue == NULL)
+ data->queue = new;
+}
+
+static struct uidata *newuidata(struct socket *sk)
+{
+ struct uidata *data;
+
+ data = smalloc(sizeof(*data));
+ memset(data, 0, sizeof(*data));
+ data->sk = sk;
+ getsock(sk);
+ data->inbuf = smalloc(1024);
+ data->uid = -1;
+ if((data->ichandle = iconv_open("wchar_t", "utf-8")) == (iconv_t)-1)
+ {
+ flog(LOG_CRIT, "iconv cannot handle UTF-8: %s", strerror(errno));
+ return(NULL);
+ }
+ data->next = actives;
+ data->prev = NULL;
+ if(actives != NULL)
+ actives->prev = data;
+ actives = data;
+ return(data);
+}
+
+static void uiread(struct socket *sk, struct uidata *data)
+{
+ int ret, done;
+ char *newbuf;
+ char *p1, *p2;
+ wchar_t *porig;
+ size_t datalen, len2;
+ struct command *cur;
+
+ if(data->indata > 1024)
+ data->indata = 0;
+ if((newbuf = sockgetinbuf(sk, &datalen)) == NULL)
+ return;
+ sizebuf(&data->inbuf, &data->inbufsize, data->indata + datalen, 1, 1);
+ memcpy(data->inbuf + data->indata, newbuf, datalen);
+ free(newbuf);
+ data->indata += datalen;
+ if(data->cb == NULL)
+ {
+ data->cb = smalloc(sizeof(wchar_t) * (data->cbsize = 64));
+ data->cbdata = 0;
+ data->pp = data->cb;
+ }
+ done = 0;
+ while(!done)
+ {
+ if(data->cbsize == data->cbdata)
+ {
+ len2 = data->pp - data->cb;
+ data->cb = srealloc(data->cb, sizeof(wchar_t) * (data->cbsize *= 2));
+ data->pp = data->cb + len2;
+ }
+ p1 = data->inbuf;
+ p2 = (char *)(porig = (data->cb + data->cbdata));
+ len2 = sizeof(wchar_t) * (data->cbsize - data->cbdata);
+ ret = iconv(data->ichandle, &p1, &data->indata, &p2, &len2);
+ memmove(data->inbuf, p1, data->indata);
+ /* Just a sanity check */
+ if(((p2 - ((char *)data->cb)) % sizeof(wchar_t)) != 0)
+ {
+ flog(LOG_CRIT, "Aiya! iconv does strange things to our wchar_t's!");
+ abort();
+ }
+ data->cbdata += (((wchar_t *)p2) - porig);
+ if(ret < 0)
+ {
+ switch(errno)
+ {
+ case EILSEQ:
+ /* XXX: Should this really just ignore it? */
+ data->indata = 0;
+ done = 1;
+ break;
+ case EINVAL:
+ done = 1;
+ break;
+ case E2BIG:
+ /* Just a sanity check */
+ if(data->cbsize != data->cbdata)
+ {
+ flog(LOG_CRIT, "Aiya! iconv doesn't give us wchar_t's!");
+ abort();
+ }
+ break;
+ default:
+ flog(LOG_WARNING, "bug: strange error from iconv in uiread: %s", strerror(errno));
+ break;
+ }
+ } else {
+ done = 1;
+ }
+ }
+ done = 0;
+ while(!done && (data->pp - data->cb < data->cbdata))
+ {
+ switch(data->ps)
+ {
+ case 0:
+ if(iswspace(*data->pp))
+ {
+ if(*data->pp == L'\r')
+ {
+ if(data->pp == data->cb + data->cbdata - 1)
+ {
+ done = 1;
+ break;
+ }
+ if(*(++data->pp) == L'\n')
+ {
+ if((data->argv != NULL) && (data->argv[0] != NULL))
+ {
+ for(cur = commands; cur->handler != NULL; cur++)
+ {
+ if(cur->name == NULL)
+ continue;
+ if(!wcscasecmp(cur->name, data->argv[0]))
+ {
+ queuecmd(data, cur, data->argc, data->argv);
+ break;
+ }
+ }
+ if(cur->handler == NULL)
+ queuecmd(data, &commands[1], data->argc, data->argv);
+ } else {
+ queuecmd(data, &commands[1], data->argc, data->argv);
+ }
+ data->argv = NULL;
+ data->args = 0;
+ data->argc = 0;
+ wmemmove(data->cb, data->pp, data->cbdata -= (data->pp - data->cb));
+ data->pp = data->cb;
+ } else {
+ data->pp++;
+ }
+ } else {
+ data->pp++;
+ }
+ } else {
+ data->ps = 1;
+ data->cwdata = 0;
+ }
+ break;
+ case 1:
+ if(iswspace(*data->pp))
+ {
+ addtobuf(data->cw, L'\0');
+ sizebuf(&data->argv, &data->args, data->argc + 1, sizeof(*data->argv), 1);
+ data->argv[data->argc++] = data->cw;
+ data->cw = NULL;
+ data->cwsize = 0;
+ data->cwdata = 0;
+ data->ps = 0;
+ } else if(*data->pp == L'\"') {
+ data->ps = 2;
+ data->pp++;
+ } else if(*data->pp == L'\\') {
+ if(data->pp == data->cb + data->cbdata - 1)
+ {
+ done = 1;
+ break;
+ }
+ addtobuf(data->cw, *(++data->pp));
+ data->pp++;
+ } else {
+ addtobuf(data->cw, *(data->pp++));
+ }
+ break;
+ case 2:
+ if(*data->pp == L'\"')
+ {
+ data->ps = 1;
+ } else if(*data->pp == L'\\') {
+ if(data->pp == data->cb + data->cbdata - 1)
+ {
+ done = 1;
+ break;
+ }
+ addtobuf(data->cw, *(++(data->pp)));
+ } else {
+ addtobuf(data->cw, *data->pp);
+ }
+ data->pp++;
+ break;
+ }
+ }
+}
+
+static void uierror(struct socket *sk, int err, struct uidata *data)
+{
+ if(err)
+ flog(LOG_WARNING, "error occurred on UI socket: %s", strerror(err));
+ freeuidata(data);
+}
+
+static void uiaccept(struct socket *sk, struct socket *newsk, void *data)
+{
+ struct uidata *uidata;
+
+ newsk->data = uidata = newuidata(newsk);
+ socksettos(newsk, confgetint("ui", "uitos"));
+ if(uidata == NULL)
+ return;
+ newsk->errcb = (void (*)(struct socket *, int, void *))uierror;
+ newsk->readcb = (void (*)(struct socket *, void *))uiread;
+ queuecmd(uidata, &commands[0], 0, NULL);
+}
+
+static int srcheta(struct search *srch, void *uudata)
+{
+ struct uidata *data;
+
+ for(data = actives; data != NULL; data = data->next)
+ {
+ if(haspriv(data, PERM_SRCH) && data->notify.b.srch && !wcscmp(srch->owner, data->username))
+ newnotif(data, 620, NOTIF_ID, srch->id, NOTIF_INT, srch->eta - time(NULL), NOTIF_END);
+ }
+ return(0);
+}
+
+static int srchcommit(struct search *srch, void *uudata)
+{
+ struct uidata *data;
+
+ for(data = actives; data != NULL; data = data->next)
+ {
+ if(haspriv(data, PERM_SRCH) && data->notify.b.srch && !wcscmp(srch->owner, data->username))
+ newnotif(data, 621, NOTIF_ID, srch->id, NOTIF_END);
+ }
+ return(0);
+}
+
+static int srchres(struct search *srch, struct srchres *sr, void *uudata)
+{
+ struct uidata *data;
+
+ for(data = actives; data != NULL; data = data->next)
+ {
+ if(haspriv(data, PERM_SRCH) && data->notify.b.srch && !wcscmp(srch->owner, data->username))
+ newnotif(data, 622, NOTIF_ID, srch->id, NOTIF_STR, sr->filename, NOTIF_STR, sr->fnet->name, NOTIF_STR, sr->peerid, NOTIF_INT, sr->size, NOTIF_INT, sr->slots, NOTIF_INT, (sr->fn == NULL)?-1:(sr->fn->id), NOTIF_FLOAT, sr->time, NOTIF_END);
+ }
+ return(0);
+}
+
+static int recvchat(struct fnetnode *fn, int public, wchar_t *name, wchar_t *peer, wchar_t *string, void *uudata)
+{
+ struct uidata *data;
+
+ for(data = actives; data != NULL; data = data->next)
+ {
+ if(haspriv(data, PERM_CHAT) && data->notify.b.fnchat)
+ newnotif(data, 600, NOTIF_ID, fn->id, NOTIF_INT, public, NOTIF_STR, name, NOTIF_STR, peer, NOTIF_STR, string, NOTIF_END);
+ }
+ return(0);
+}
+
+static int fnactive(struct fnetnode *fn, wchar_t *attrib, void *uudata)
+{
+ struct uidata *data;
+ struct notif *notif;
+
+ if(!wcscmp(attrib, L"state"))
+ {
+ for(data = actives; data != NULL; data = data->next)
+ {
+ if(data->notify.b.fnact)
+ newnotif(data, 601, NOTIF_ID, fn->id, NOTIF_INT, fn->state, NOTIF_END);
+ }
+ } else if(!wcscmp(attrib, L"name")) {
+ for(data = actives; data != NULL; data = data->next)
+ {
+ if(data->notify.b.fnact)
+ newnotif(data, 602, NOTIF_ID, fn->id, NOTIF_STR, fn->name, NOTIF_END);
+ }
+ } else if(!wcscmp(attrib, L"numpeers")) {
+ for(data = actives; data != NULL; data = data->next)
+ {
+ if(data->notify.b.fnact)
+ {
+ if((notif = findnotif(data->fnotif, 1, NOTIF_PEND, 605, fn->id)) != NULL)
+ notif->argv[1].d.n = fn->numpeers;
+ else
+ newnotif(data, 605, NOTIF_ID, fn->id, NOTIF_INT, fn->numpeers, NOTIF_END);
+ }
+ }
+ }
+ return(0);
+}
+
+static int fnunlink(struct fnetnode *fn, void *uudata)
+{
+ struct uidata *data;
+
+ for(data = actives; data != NULL; data = data->next)
+ {
+ if(data->notify.b.fnact)
+ newnotif(data, 603, NOTIF_ID, fn->id, NOTIF_END);
+ }
+ return(0);
+}
+
+static int newfnetnode(struct fnetnode *fn, void *uudata)
+{
+ struct uidata *data;
+
+ for(data = actives; data != NULL; data = data->next)
+ {
+ if(data->notify.b.fnact)
+ newnotif(data, 604, NOTIF_ID, fn->id, NOTIF_STR, fn->fnet->name, NOTIF_END);
+ }
+ CBREG(fn, fnetnode_ac, fnactive, NULL, NULL);
+ CBREG(fn, fnetnode_chat, recvchat, NULL, NULL);
+ CBREG(fn, fnetnode_unlink, fnunlink, NULL, NULL);
+ return(0);
+}
+
+static int transferchattr(struct transfer *transfer, wchar_t *attrib, void *uudata)
+{
+ struct uidata *data;
+
+ if(!wcscmp(attrib, L"state"))
+ {
+ for(data = actives; data != NULL; data = data->next)
+ {
+ if(haspriv(data, PERM_TRANS) && data->notify.b.tract && ((transfer->owner == 0) || (transfer->owner == data->uid)))
+ newnotif(data, 611, NOTIF_ID, transfer->id, NOTIF_INT, transfer->state, NOTIF_END);
+ }
+ } else if(!wcscmp(attrib, L"nick")) {
+ for(data = actives; data != NULL; data = data->next)
+ {
+ if(haspriv(data, PERM_TRANS) && data->notify.b.tract && ((transfer->owner == 0) || (transfer->owner == data->uid)))
+ newnotif(data, 612, NOTIF_ID, transfer->id, NOTIF_STR, transfer->peernick, NOTIF_END);
+ }
+ } else if(!wcscmp(attrib, L"size")) {
+ for(data = actives; data != NULL; data = data->next)
+ {
+ if(haspriv(data, PERM_TRANS) && data->notify.b.tract && ((transfer->owner == 0) || (transfer->owner == data->uid)))
+ newnotif(data, 613, NOTIF_ID, transfer->id, NOTIF_INT, transfer->size, NOTIF_END);
+ }
+ } else if(!wcscmp(attrib, L"error")) {
+ for(data = actives; data != NULL; data = data->next)
+ {
+ if(haspriv(data, PERM_TRANS) && data->notify.b.tract && ((transfer->owner == 0) || (transfer->owner == data->uid)))
+ newnotif(data, 614, NOTIF_ID, transfer->id, NOTIF_INT, transfer->error, NOTIF_END);
+ }
+ } else if(!wcscmp(attrib, L"path")) {
+ for(data = actives; data != NULL; data = data->next)
+ {
+ if(haspriv(data, PERM_TRANS) && data->notify.b.tract && ((transfer->owner == 0) || (transfer->owner == data->uid)))
+ newnotif(data, 616, NOTIF_ID, transfer->id, NOTIF_STR, transfer->path, NOTIF_END);
+ }
+ }
+ return(0);
+}
+
+static int transferprog(struct transfer *transfer, void *uudata)
+{
+ struct uidata *data;
+ struct notif *notif;
+
+ for(data = actives; data != NULL; data = data->next)
+ {
+ if(haspriv(data, PERM_TRANS) && data->notify.b.trprog && ((transfer->owner == 0) || (transfer->owner == data->uid)))
+ {
+ if((notif = findnotif(data->fnotif, 1, NOTIF_PEND, 615, transfer->id)) != NULL)
+ notif->argv[1].d.n = transfer->curpos;
+ else
+ newnotif(data, 615, NOTIF_ID, transfer->id, NOTIF_INT, transfer->curpos, NOTIF_END)->rlimit = 0.5;
+ }
+ }
+ return(0);
+}
+
+static int transferdestroyed(struct transfer *transfer, void *uudata)
+{
+ struct uidata *data;
+
+ for(data = actives; data != NULL; data = data->next)
+ {
+ if(haspriv(data, PERM_TRANS) && data->notify.b.tract && ((transfer->owner == 0) || (transfer->owner == data->uid)))
+ newnotif(data, 617, NOTIF_ID, transfer->id, NOTIF_END);
+ }
+ return(0);
+}
+
+static int newtransfernotify(struct transfer *transfer, void *uudata)
+{
+ struct uidata *data;
+
+ for(data = actives; data != NULL; data = data->next)
+ {
+ if(haspriv(data, PERM_TRANS) && data->notify.b.tract && ((transfer->owner == 0) || (transfer->owner == data->uid)))
+ newnotif(data, 610, NOTIF_ID, transfer->id, NOTIF_INT, transfer->dir, NOTIF_STR, transfer->peerid, NOTIF_STR, (transfer->path == NULL)?L"":transfer->path, NOTIF_END);
+ }
+ CBREG(transfer, trans_ac, transferchattr, NULL, NULL);
+ CBREG(transfer, trans_p, transferprog, NULL, NULL);
+ CBREG(transfer, trans_destroy, transferdestroyed, NULL, NULL);
+ return(0);
+}
+
+static struct uiuser *newuser(wchar_t *name, unsigned long perms)
+{
+ struct uiuser *new;
+
+ new = smalloc(sizeof(*new));
+ new->used = 0;
+ new->name = swcsdup(name);
+ new->perms = perms;
+ new->delete = 0;
+ new->next = users;
+ new->prev = NULL;
+ if(users != NULL)
+ users->prev = new;
+ users = new;
+ return(new);
+}
+
+static void freeuser(struct uiuser *user)
+{
+ if(user->next != NULL)
+ user->next->prev = user->prev;
+ if(user->prev != NULL)
+ user->prev->next = user->next;
+ if(user == users)
+ users = user->next;
+ free(user->name);
+ free(user);
+}
+
+static int conf_user(int argc, wchar_t **argv)
+{
+ int i, perms, permmod;
+ struct uiuser *user;
+ wchar_t *p;
+
+ if(argc < 3)
+ {
+ flog(LOG_WARNING, "not enough arguments given for user command");
+ return(1);
+ }
+ perms = 0;
+ for(i = 2; i < argc; i++)
+ {
+ if(!iswalpha(argv[i][0]))
+ p = argv[i] + 1;
+ else
+ p = argv[i];
+ if(!wcscmp(p, L"disallow"))
+ permmod = PERM_DISALLOW;
+ if(!wcscmp(p, L"admin"))
+ permmod = PERM_ADMIN;
+ if(!wcscmp(p, L"fnetctl"))
+ permmod = PERM_FNETCTL;
+ if(!wcscmp(p, L"trans"))
+ permmod = PERM_TRANS;
+ if(!wcscmp(p, L"transcu"))
+ permmod = PERM_TRANSCU;
+ if(!wcscmp(p, L"chat"))
+ permmod = PERM_CHAT;
+ if(!wcscmp(p, L"srch"))
+ permmod = PERM_SRCH;
+ if(!wcscmp(p, L"all"))
+ permmod = ~0;
+ if(argv[i][0] == L'-')
+ perms &= ~permmod;
+ else
+ perms |= permmod;
+ }
+ if((user = finduser(argv[1])) == NULL)
+ {
+ newuser(argv[1], perms);
+ } else {
+ user->delete = 0;
+ user->perms = perms;
+ }
+ return(0);
+}
+
+static void preinit(int hup)
+{
+ struct uiuser *user;
+
+ if(!hup)
+ {
+ newuser(L"default", 0);
+ } else {
+ for(user = users; user != NULL; user = user->next)
+ {
+ if(!wcscmp(user->name, L"default"))
+ user->delete = 1;
+ }
+ }
+}
+
+#ifdef HAVE_IPV6
+static struct sockaddr *getnameforport(int port, socklen_t *len)
+{
+ static struct sockaddr_in6 addr;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin6_family = AF_INET6;
+ addr.sin6_port = htons(port);
+ addr.sin6_addr = in6addr_any;
+ if(len != NULL)
+ *len = sizeof(addr);
+ return((struct sockaddr *)&addr);
+}
+#else
+static struct sockaddr *getnameforport(int port, socklen_t *len)
+{
+ static struct sockaddr_in addr;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ if(len != NULL)
+ *len = sizeof(addr);
+ return((struct sockaddr *)&addr);
+}
+#endif
+
+static int portupdate(struct configvar *var, void *uudata)
+{
+ struct sockaddr *addr;
+ socklen_t addrlen;
+ struct socket *newsock;
+
+ addr = getnameforport(var->val.num, &addrlen);
+ if((uisocket = netcslistenlocal(SOCK_STREAM, addr, addrlen, uiaccept, NULL)) == NULL)
+ {
+ flog(LOG_WARNING, "could not create new UI socket, reverting to old: %s", strerror(errno));
+ return(0);
+ }
+ if(uisocket != NULL)
+ putsock(uisocket);
+ uisocket = newsock;
+ return(0);
+}
+
+static int init(int hup)
+{
+ struct sockaddr *addr;
+ socklen_t addrlen;
+ struct uiuser *user, *next;
+
+ if(hup)
+ {
+ for(user = users; user != NULL; user = next)
+ {
+ next = user->next;
+ if(user->delete)
+ freeuser(user);
+ }
+ }
+ if(!hup)
+ {
+ if(uisocket != NULL)
+ putsock(uisocket);
+ addr = getnameforport(confgetint("ui", "port"), &addrlen);
+ if((uisocket = netcslistenlocal(SOCK_STREAM, addr, addrlen, uiaccept, NULL)) == NULL)
+ {
+ flog(LOG_CRIT, "could not create UI socket: %s", strerror(errno));
+ return(1);
+ }
+ CBREG(confgetvar("ui", "port"), conf_update, portupdate, NULL, NULL);
+ GCBREG(newfncb, newfnetnode, NULL);
+ GCBREG(newtransfercb, newtransfernotify, NULL);
+ }
+ return(0);
+}
+
+static int run(void)
+{
+ int i, id;
+ struct uidata *data, *next;
+ struct qcommand *qcmd;
+ struct notif *notif, *nnotif;
+ wchar_t buf[64];
+
+ for(data = actives; data != NULL; data = next)
+ {
+ next = data->next;
+ if(data->close)
+ freeuidata(data);
+ }
+ for(data = actives; data != NULL; data = data->next)
+ {
+ for(notif = data->fnotif; notif != NULL; notif = nnotif)
+ {
+ nnotif = notif->next;
+ if(notif->state == NOTIF_WAIT)
+ continue;
+ id = -1;
+ for(i = 0; i < notif->argc; i++)
+ {
+ if(notif->argv[i].dt == NOTIF_ID)
+ {
+ id = notif->argv[i].d.n;
+ break;
+ }
+ }
+ if(findnotif(notif->prev, 0, -1, notif->code, id) != NULL)
+ continue;
+ sq(data->sk, 2, L"%%i", notif->code, NULL);
+ for(i = 0; i < notif->argc; i++)
+ {
+ switch(notif->argv[i].dt)
+ {
+ case NOTIF_INT:
+ case NOTIF_ID:
+ sq(data->sk, 2, L"%%i", notif->argv[i].d.n, NULL);
+ break;
+ case NOTIF_STR:
+ if(notif->argv[i].d.s[0] == L'%')
+ sq(data->sk, 2, L"%%s", notif->argv[i].d.s, NULL);
+ else
+ sq(data->sk, 2, notif->argv[i].d.s, NULL);
+ break;
+ case NOTIF_FLOAT:
+ swprintf(buf, 64, L"%f", notif->argv[i].d.d);
+ sq(data->sk, 2, buf, NULL);
+ break;
+ }
+ }
+ sq(data->sk, 0, NULL);
+ if(notif->rlimit != 0)
+ {
+ notif->state = NOTIF_WAIT;
+ notif->exptimer = timercallback(ntime() + notif->rlimit, (void (*)(int, void *))notifexpire, notif);
+ } else {
+ freenotif(notif);
+ }
+ }
+ if((qcmd = unlinkqcmd(data)) != NULL)
+ {
+ qcmd->cmd->handler(data->sk, data, qcmd->argc, qcmd->argv);
+ freequeuecmd(qcmd);
+ return(1);
+ }
+ }
+ return(0);
+}
+
+static void terminate(void)
+{
+ while(users != NULL)
+ freeuser(users);
+}
+
+static struct configvar myvars[] =
+{
+ {CONF_VAR_BOOL, "onlylocal", {.num = 1}},
+ {CONF_VAR_INT, "port", {.num = 1500}},
+ {CONF_VAR_INT, "uitos", {.num = SOCK_TOS_MINDELAY}},
+ {CONF_VAR_STRING, "filtercmd", {.str = L"dc-filtercmd"}},
+ {CONF_VAR_END}
+};
+
+static struct configcmd mycmds[] =
+{
+ {"user", conf_user},
+ {NULL}
+};
+
+static struct module me =
+{
+ .name = "ui",
+ .conf =
+ {
+ .vars = myvars,
+ .cmds = mycmds
+ },
+ .preinit = preinit,
+ .init = init,
+ .run = run,
+ .terminate = terminate
+};
+
+MODULE(me)
--- /dev/null
+500 - Command not found
+501 - Wrong number of arguments
+502 - Unauthorized request
+503 - Already logged in
+504 - Could not convert to character set
+505 - Internal error
+506 - Authentication error
+507 - Command not expected
+508 - No such authentication mechanism
+509 - Malformed argument
+510 - No such fnetnode
+511 - No such fnet
+512 - No such transfer
+513 - Not supported for this fnet
+514 - No such search
+
+600 - Chat receipt
+601 - FN state change
+602 - FN name change
+603 - FN destroy
+604 - FN create
+605 - FN num peers change
+610 - Transfer create
+611 - Transfer state change
+612 - Transfer nick change
+613 - Transfer size change
+614 - Transfer error update
+615 - Transfer progress
+616 - Transfer path change
+620 - Search ETA change
+621 - Search commit
+622 - Search result
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#include <malloc.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <iconv.h>
+#include <errno.h>
+#include <string.h>
+#include <wctype.h>
+#include <langinfo.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "utils.h"
+#include "log.h"
+
+static char *base64set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static int base64rev[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+};
+static char *base32set = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+static int base32rev[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+};
+
+char *vsprintf2(char *format, va_list al)
+{
+ int ret;
+ char *buf;
+
+ ret = vsnprintf(NULL, 0, format, al);
+ if((buf = malloc(ret + 1)) == NULL)
+ {
+ LOGOOM(ret + 1);
+ return(NULL);
+ }
+ vsnprintf(buf, ret + 1, format, al);
+ return(buf);
+}
+
+char *sprintf2(char *format, ...)
+{
+ va_list args;
+ char *buf;
+
+ va_start(args, format);
+ buf = vsprintf2(format, args);
+ va_end(args);
+ return(buf);
+}
+
+wchar_t *vswprintf2(wchar_t *format, va_list al)
+{
+ int ret;
+ wchar_t *buf;
+ size_t bufsize;
+
+ buf = smalloc(sizeof(wchar_t) * (bufsize = 1024));
+ while((ret = vswprintf(buf, bufsize, format, al)) < 0)
+ buf = srealloc(buf, sizeof(wchar_t) * (bufsize *= 2));
+ if(bufsize > ret + 1)
+ buf = srealloc(buf, sizeof(wchar_t) * (ret + 1));
+ return(buf);
+}
+
+wchar_t *swprintf2(wchar_t *format, ...)
+{
+ va_list args;
+ wchar_t *buf;
+
+ va_start(args, format);
+ buf = vswprintf2(format, args);
+ va_end(args);
+ return(buf);
+}
+
+wchar_t *icmbstowcs(char *mbs, char *charset)
+{
+ int ret;
+ char *buf;
+ char *p, *p2;
+ size_t len1, len2, bufsize, data;
+ iconv_t cd;
+
+ len1 = strlen(mbs) + 1;
+ bufsize = len2 = len1 * sizeof(wchar_t);
+ if((buf = malloc(bufsize)) == NULL)
+ {
+ LOGOOM(bufsize);
+ return(NULL);
+ }
+ if(charset == NULL)
+ charset = nl_langinfo(CODESET);
+ if((cd = iconv_open("wchar_t", charset)) == (iconv_t)-1)
+ {
+ flog(LOG_ERR, "icmbstowcs: could not open iconv structure for %s: %s", charset, strerror(errno));
+ free(buf);
+ return(NULL);
+ }
+ p = buf;
+ while(len1 > 0)
+ {
+ ret = iconv(cd, &mbs, &len1, &p, &len2);
+ if(ret < 0)
+ {
+ if(errno == E2BIG)
+ {
+ data = p - buf;
+ len2 += 128;
+ bufsize += 128;
+ if((p2 = realloc(buf, bufsize)) == NULL)
+ {
+ LOGOOM(bufsize);
+ free(buf);
+ iconv_close(cd);
+ return(NULL);
+ }
+ buf = p2;
+ p = buf + data;
+ } else {
+ free(buf);
+ iconv_close(cd);
+ return(NULL);
+ }
+ }
+ }
+ if(len2 > 0)
+ buf = realloc(buf, p - buf);
+ iconv_close(cd);
+ return((wchar_t *)buf);
+}
+
+wchar_t *icsmbstowcs(char *mbs, char *charset, wchar_t *def)
+{
+ static wchar_t *buf = NULL;
+
+ if(buf != NULL)
+ free(buf);
+ if((buf = icmbstowcs(mbs, charset)) == NULL)
+ {
+ if(*def == '~')
+ {
+ flog(LOG_WARNING, "icsmbstowcs: could not convert wcs string into charset %s: %s", charset, strerror(errno));
+ def++;
+ }
+ return(def);
+ }
+ return(buf);
+}
+
+char *icwcstombs(wchar_t *wcs, char *charset)
+{
+ int ret;
+ char *buf;
+ char *p, *p2;
+ size_t len1, len2, bufsize, data;
+ iconv_t cd;
+
+ len1 = sizeof(wchar_t) * (wcslen(wcs) + 1);
+ bufsize = len2 = len1;
+ if((buf = malloc(bufsize)) == NULL)
+ {
+ LOGOOM(bufsize);
+ return(NULL);
+ }
+ if(charset == NULL)
+ charset = nl_langinfo(CODESET);
+ if((cd = iconv_open(charset, "wchar_t")) == (iconv_t)-1)
+ {
+ flog(LOG_ERR, "icwcstombs: could not open iconv structure for %s: %s", charset, strerror(errno));
+ free(buf);
+ return(NULL);
+ }
+ p = buf;
+ while(len1 > 0)
+ {
+ ret = iconv(cd, (char **)&wcs, &len1, &p, &len2);
+ if(ret < 0)
+ {
+ if(errno == E2BIG)
+ {
+ data = p - buf;
+ len2 += 128;
+ bufsize += 128;
+ if((p2 = realloc(buf, bufsize)) == NULL)
+ {
+ LOGOOM(bufsize);
+ free(buf);
+ iconv_close(cd);
+ return(NULL);
+ }
+ buf = p2;
+ p = buf + data;
+ } else {
+ free(buf);
+ iconv_close(cd);
+ return(NULL);
+ }
+ }
+ }
+ if(len2 > 0)
+ buf = realloc(buf, p - buf);
+ iconv_close(cd);
+ return(buf);
+}
+
+char *icswcstombs(wchar_t *wcs, char *charset, char *def)
+{
+ static char *buf = NULL;
+
+ if(buf != NULL)
+ free(buf);
+ if((buf = icwcstombs(wcs, charset)) == NULL)
+ {
+ if(*def == '~')
+ {
+ flog(LOG_WARNING, "icswcstombs: could not convert mbs string from charset %s: %s", charset, strerror(errno));
+ def++;
+ }
+ return(def);
+ }
+ return(buf);
+}
+
+wchar_t *wcstolower(wchar_t *wcs)
+{
+ wchar_t *p;
+
+ for(p = wcs; *p != L'\0'; p++)
+ *p = towlower(*p);
+ return(wcs);
+}
+
+wchar_t ucptowc(int ucp)
+{
+ int ret;
+ unsigned long ucpbuf;
+ char *buf;
+ char *mbsp, *p, *p2;
+ wchar_t res;
+ size_t len1, len2, bufsize, data;
+ iconv_t cd;
+
+ ucpbuf = htonl(ucp);
+ mbsp = (char *)&ucpbuf;
+ len1 = 4;
+ bufsize = len2 = len1 * sizeof(wchar_t);
+ if((buf = malloc(bufsize)) == NULL)
+ {
+ LOGOOM(bufsize);
+ return(L'\0');
+ }
+ if((cd = iconv_open("wchar_t", "UCS-4BE")) == (iconv_t)-1)
+ {
+ flog(LOG_ERR, "ucptowc: could not open iconv structure for UCS-4BE: %s", strerror(errno));
+ free(buf);
+ return(L'\0');
+ }
+ p = buf;
+ while(len1 > 0)
+ {
+ ret = iconv(cd, &mbsp, &len1, &p, &len2);
+ if(ret < 0)
+ {
+ if(errno == E2BIG)
+ {
+ data = p - buf;
+ len2 += 128;
+ bufsize += 128;
+ if((p2 = realloc(buf, bufsize)) == NULL)
+ {
+ LOGOOM(bufsize);
+ free(buf);
+ iconv_close(cd);
+ return(L'\0');
+ }
+ buf = p2;
+ p = buf + data;
+ } else {
+ free(buf);
+ iconv_close(cd);
+ return(L'\0');
+ }
+ }
+ }
+ if(len2 > 0)
+ buf = realloc(buf, p - buf);
+ iconv_close(cd);
+ res = *(wchar_t *)buf;
+ free(buf);
+ return(res);
+}
+
+void _sizebuf(void **buf, size_t *bufsize, size_t reqsize, size_t elsize, int algo)
+{
+ if(*bufsize >= reqsize)
+ return;
+ switch(algo)
+ {
+ case 0:
+ *buf = srealloc(*buf, elsize * ((*bufsize) = reqsize));
+ break;
+ case 1:
+ if(*bufsize == 0)
+ *bufsize = 1;
+ while(*bufsize < reqsize)
+ *bufsize <<= 1;
+ *buf = srealloc(*buf, elsize * (*bufsize));
+ break;
+ }
+}
+
+double ntime(void)
+{
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ return((double)tv.tv_sec + ((double)tv.tv_usec / 1000000.0));
+}
+
+int wcsexists(wchar_t *h, wchar_t *n)
+{
+ int i, o, nl, hl;
+ wchar_t *ln, *lh;
+
+ ln = alloca(sizeof(*ln) * (nl = wcslen(n)));
+ for(i = 0; i < nl; i++)
+ ln[i] = towlower(n[i]);
+ lh = alloca(sizeof(*lh) * (hl = wcslen(h)));
+ if(nl > hl)
+ return(0);
+ for(i = 0; i < nl; i++)
+ lh[i] = towlower(h[i]);
+ i = 0;
+ while(1)
+ {
+ for(o = 0; o < nl; o++)
+ {
+ if(lh[i + o] != ln[o])
+ break;
+ }
+ if(o == nl)
+ return(1);
+ if(i == hl - nl)
+ return(0);
+ lh[i + nl] = towlower(h[i + nl]);
+ i++;
+ }
+}
+
+#ifndef HAVE_WCSCASECMP
+int wcscasecmp(const wchar_t *s1, const wchar_t *s2)
+{
+ while(towlower(*s1) == towlower(*s2))
+ {
+ if(*s1 == L'\0')
+ return(0);
+ }
+ return(towlower(*s1) - towlower(*s2));
+}
+#endif
+
+char *hexencode(char *data, size_t datalen)
+{
+ char *buf, this;
+ size_t bufsize, bufdata;
+ int dig;
+
+ buf = NULL;
+ bufsize = bufdata = 0;
+ for(; datalen > 0; datalen--, data++)
+ {
+ dig = (*data & 0xF0) >> 4;
+ if(dig > 9)
+ this = 'A' + dig - 10;
+ else
+ this = dig + '0';
+ addtobuf(buf, this);
+ dig = *data & 0x0F;
+ if(dig > 9)
+ this = 'A' + dig - 10;
+ else
+ this = dig + '0';
+ addtobuf(buf, this);
+ }
+ addtobuf(buf, 0);
+ return(buf);
+}
+
+char *hexdecode(char *data, size_t *len)
+{
+ char *buf, this;
+ size_t bufsize, bufdata;
+
+ buf = NULL;
+ bufsize = bufdata = 0;
+ for(; *data; data++)
+ {
+ if((*data >= 'A') && (*data <= 'F'))
+ {
+ this = (this & 0x0F) | ((*data - 'A' + 10) << 4);
+ } else if((*data >= '0') && (*data <= '9')) {
+ this = (this & 0x0F) | ((*data - '0') << 4);
+ } else {
+ if(buf != NULL)
+ free(buf);
+ return(NULL);
+ }
+ data++;
+ if(!*data)
+ {
+ if(buf != NULL)
+ free(buf);
+ return(NULL);
+ }
+ if((*data >= 'A') && (*data <= 'F'))
+ {
+ this = (this & 0xF0) | (*data - 'A' + 10);
+ } else if((*data >= '0') && (*data <= '9')) {
+ this = (this & 0xF0) | (*data - '0');
+ } else {
+ if(buf != NULL)
+ free(buf);
+ return(NULL);
+ }
+ addtobuf(buf, this);
+ }
+ addtobuf(buf, 0);
+ if(len != NULL)
+ *len = bufdata - 1;
+ return(buf);
+}
+
+char *base64encode(char *data, size_t datalen)
+{
+ char *buf;
+ size_t bufsize, bufdata;
+
+ if(datalen == 0)
+ return(sstrdup(""));
+ buf = NULL;
+ bufsize = bufdata = 0;
+ while(datalen >= 3)
+ {
+ addtobuf(buf, base64set[(data[0] & 0xfc) >> 2]);
+ addtobuf(buf, base64set[((data[0] & 0x03) << 4) | ((data[1] & 0xf0) >> 4)]);
+ addtobuf(buf, base64set[((data[1] & 0x0f) << 2) | ((data[2] & 0xc0) >> 6)]);
+ addtobuf(buf, base64set[data[2] & 0x3f]);
+ datalen -= 3;
+ data += 3;
+ }
+ if(datalen == 1)
+ {
+ addtobuf(buf, base64set[(data[0] & 0xfc) >> 2]);
+ addtobuf(buf, base64set[(data[0] & 0x03) << 4]);
+ bufcat(buf, "==", 2);
+ }
+ if(datalen == 2)
+ {
+ addtobuf(buf, base64set[(data[0] & 0xfc) >> 2]);
+ addtobuf(buf, base64set[((data[0] & 0x03) << 4) | ((data[1] & 0xf0) >> 4)]);
+ addtobuf(buf, base64set[(data[1] & 0x0f) << 2]);
+ addtobuf(buf, '=');
+ }
+ addtobuf(buf, 0);
+ return(buf);
+}
+
+char *base64decode(char *data, size_t *datalen)
+{
+ int b, c;
+ char *buf, cur;
+ size_t bufsize, bufdata;
+
+ buf = NULL;
+ bufsize = bufdata = 0;
+ cur = 0;
+ b = 8;
+ for(; *data > 0; data++)
+ {
+ c = (int)(unsigned char)*data;
+ if(c == '=')
+ break;
+ if(base64rev[c] == -1)
+ {
+ if(buf != NULL)
+ free(buf);
+ return(NULL);
+ }
+ b -= 6;
+ if(b <= 0)
+ {
+ cur |= base64rev[c] >> -b;
+ addtobuf(buf, cur);
+ b += 8;
+ cur = 0;
+ }
+ cur |= base64rev[c] << b;
+ }
+ if(datalen != NULL)
+ *datalen = bufdata;
+ addtobuf(buf, 0);
+ return(buf);
+}
+
+char *base32encode(char *data, size_t datalen)
+{
+ char *buf;
+ size_t bufsize, bufdata;
+
+ if(datalen == 0)
+ return(sstrdup(""));
+ buf = NULL;
+ bufsize = bufdata = 0;
+ while(datalen >= 5)
+ {
+ addtobuf(buf, base32set[((data[0] & 0xf8) >> 3)]);
+ addtobuf(buf, base32set[((data[0] & 0x07) << 2) | ((data[1] & 0xc0) >> 6)]);
+ addtobuf(buf, base32set[((data[1] & 0x3e) >> 1)]);
+ addtobuf(buf, base32set[((data[1] & 0x01) << 4) | ((data[2] & 0xf0) >> 4)]);
+ addtobuf(buf, base32set[((data[2] & 0x0f) << 1) | ((data[3] & 0x80) >> 7)]);
+ addtobuf(buf, base32set[((data[3] & 0x7c) >> 2)]);
+ addtobuf(buf, base32set[((data[3] & 0x03) << 3) | ((data[4] & 0xe0) >> 5)]);
+ addtobuf(buf, base32set[data[4] & 0x1f]);
+ datalen -= 5;
+ data += 5;
+ }
+ if(datalen == 1)
+ {
+ addtobuf(buf, base32set[((data[0] & 0xf8) >> 3)]);
+ addtobuf(buf, base32set[((data[0] & 0x07) << 2)]);
+ bufcat(buf, "======", 6);
+ }
+ if(datalen == 2)
+ {
+ addtobuf(buf, base32set[((data[0] & 0xf8) >> 3)]);
+ addtobuf(buf, base32set[((data[0] & 0x07) << 2) | ((data[1] & 0xc0) >> 6)]);
+ addtobuf(buf, base32set[((data[1] & 0x3e) >> 1)]);
+ addtobuf(buf, base32set[((data[1] & 0x01) << 4)]);
+ bufcat(buf, "====", 4);
+ }
+ if(datalen == 3)
+ {
+ addtobuf(buf, base32set[((data[0] & 0xf8) >> 3)]);
+ addtobuf(buf, base32set[((data[0] & 0x07) << 2) | ((data[1] & 0xc0) >> 6)]);
+ addtobuf(buf, base32set[((data[1] & 0x3e) >> 1)]);
+ addtobuf(buf, base32set[((data[1] & 0x01) << 4) | ((data[2] & 0xf0) >> 4)]);
+ addtobuf(buf, base32set[((data[2] & 0x0f) << 1)]);
+ bufcat(buf, "===", 3);
+ }
+ if(datalen == 4)
+ {
+ addtobuf(buf, base32set[((data[0] & 0xf8) >> 3)]);
+ addtobuf(buf, base32set[((data[0] & 0x07) << 2) | ((data[1] & 0xc0) >> 6)]);
+ addtobuf(buf, base32set[((data[1] & 0x3e) >> 1)]);
+ addtobuf(buf, base32set[((data[1] & 0x01) << 4) | ((data[2] & 0xf0) >> 4)]);
+ addtobuf(buf, base32set[((data[2] & 0x0f) << 1) | ((data[3] & 0x80) >> 7)]);
+ addtobuf(buf, base32set[((data[3] & 0x7c) >> 2)]);
+ addtobuf(buf, base32set[((data[3] & 0x03) << 3)]);
+ bufcat(buf, "=", 1);
+ }
+ addtobuf(buf, 0);
+ return(buf);
+}
+
+char *base32decode(char *data, size_t *datalen)
+{
+ int b, c;
+ char *buf, cur;
+ size_t bufsize, bufdata;
+
+ buf = NULL;
+ bufsize = bufdata = 0;
+ cur = 0;
+ b = 8;
+ for(; *data > 0; data++)
+ {
+ c = (int)(unsigned char)*data;
+ if(c == '=')
+ break;
+ if(base32rev[c] == -1)
+ {
+ if(buf != NULL)
+ free(buf);
+ return(NULL);
+ }
+ b -= 5;
+ if(b <= 0)
+ {
+ cur |= base32rev[c] >> -b;
+ addtobuf(buf, cur);
+ b += 8;
+ cur = 0;
+ }
+ cur |= base32rev[c] << b;
+ }
+ if(datalen != NULL)
+ *datalen = bufdata;
+ addtobuf(buf, 0);
+ return(buf);
+}
+
+void _freeparr(void **arr)
+{
+ void **buf;
+
+ if(arr == NULL)
+ return;
+ for(buf = arr; *buf != NULL; buf++)
+ free(*buf);
+ free(arr);
+}
+
+int _parrlen(void **arr)
+{
+ int i;
+
+ if(arr == NULL)
+ return(0);
+ for(i = 0; *arr != NULL; arr++)
+ i++;
+ return(i);
+}
+
+char *getetcpath(char *binpath)
+{
+ char *etcpath, *p;
+ size_t etcpathsize, etcpathdata;
+
+ etcpath = NULL;
+ etcpathsize = etcpathdata = 0;
+ do
+ {
+ for(p = binpath; *p && (*p != ':'); p++);
+ for(; (p >= binpath) && (*p != '/'); p--);
+ if(p >= binpath)
+ {
+ if(etcpathdata > 0)
+ addtobuf(etcpath, ':');
+ bufcat(etcpath, binpath, p - binpath + 1);
+ bufcat(etcpath, "etc", 3);
+ }
+ } while((binpath = strchr(binpath, ':')) != NULL);
+ addtobuf(etcpath, 0);
+ return(etcpath);
+}
+
+char *findfile(char *gname, char *uname, char *homedir)
+{
+ char *path, *binpath, *etcpath, *p;
+
+ if((homedir != NULL) && ((path = sprintf2("%s/.%s", homedir, uname)) != NULL))
+ {
+ if(!access(path, F_OK))
+ return(path);
+ free(path);
+ }
+ if(gname != NULL)
+ {
+ if(strchr(gname, '/') != NULL)
+ {
+ if(!access(gname, F_OK))
+ return(sstrdup(gname));
+ } else {
+ if((binpath = getenv("PATH")) == NULL)
+ etcpath = sstrdup("/usr/local/etc:/etc:/usr/etc");
+ else
+ etcpath = getetcpath(binpath);
+ for(p = strtok(etcpath, ":"); p != NULL; p = strtok(NULL, ":"))
+ {
+ if((path = sprintf2("%s/%s", p, gname)) != NULL)
+ {
+ if(!access(path, F_OK))
+ {
+ free(etcpath);
+ return(path);
+ }
+ free(path);
+ }
+ }
+ free(etcpath);
+ }
+ }
+ return(NULL);
+}
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#ifndef _UTILS_H
+#define _UTILS_H
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <malloc.h>
+#include "log.h"
+
+/* "Safe" functions */
+#define smalloc(size) ({void *__result__; ((__result__ = malloc(size)) == NULL)?({LOGOOM(size); abort(); (void *)0;}):__result__;})
+#define srealloc(ptr, size) ({void *__result__; ((__result__ = realloc((ptr), (size))) == NULL)?({LOGOOM(size); abort(); (void *)0;}):__result__;})
+#define swcsdup(wcs) ((wchar_t *)wcscpy(smalloc(sizeof(wchar_t) * (wcslen(wcs) + 1)), (wcs)))
+#define sstrdup(str) ((char *)strcpy(smalloc(strlen(str) + 1), (str)))
+
+#define CBCHAIN(name, args...) \
+struct cbchain_ ## name { \
+ struct cbchain_ ## name *next, *prev; \
+ int (*func)(args, void *data); \
+ void (*destroy)(void *data); \
+ void *data; \
+} * name
+
+#define GCBCHAIN(name, args...) \
+struct cbchain_ ## name * name = NULL
+
+#define EGCBCHAIN(name, args...) \
+extern struct cbchain_ ## name { \
+ struct cbchain_ ## name *next, *prev; \
+ int (*func)(args, void *data); \
+ void *data; \
+} * name
+
+extern int vswprintf (wchar_t *__restrict __s, size_t __n,
+ __const wchar_t *__restrict __format,
+ __gnuc_va_list __arg);
+extern int swprintf (wchar_t *__restrict __s, size_t __n,
+ __const wchar_t *__restrict __format, ...);
+
+char *vsprintf2(char *format, va_list al);
+char *sprintf2(char *format, ...)
+#if defined(__GNUC__) && 0
+ __attribute__ ((format (printf, 1, 2)))
+#endif
+
+;
+wchar_t *vswprintf2(wchar_t *format, va_list al);
+wchar_t *swprintf2(wchar_t *format, ...);
+wchar_t *icmbstowcs(char *mbs, char *charset);
+wchar_t *icsmbstowcs(char *mbs, char *charset, wchar_t *def);
+char *icwcstombs(wchar_t *wcs, char *charset);
+char *icswcstombs(wchar_t *wcs, char *charset, char *def);
+wchar_t *wcstolower(wchar_t *wcs);
+wchar_t ucptowc(int ucp);
+void _sizebuf(void **buf, size_t *bufsize, size_t reqsize, size_t elsize, int algo);
+double ntime(void);
+int wcsexists(wchar_t *h, wchar_t *n);
+#ifndef HAVE_WCSCASECMP
+int wcscasecmp(const wchar_t *s1, const wchar_t *s2);
+#endif
+char *hexencode(char *data, size_t datalen);
+char *hexdecode(char *data, size_t *len);
+char *base64encode(char *data, size_t datalen);
+char *base64decode(char *data, size_t *datalen);
+char *base32encode(char *data, size_t datalen);
+char *base32decode(char *data, size_t *datalen);
+void _freeparr(void **arr);
+int _parrlen(void **arr);
+char *findfile(char *gname, char *uname, char *homedir);
+
+#define sizebuf(b, bs, rs, es, a) _sizebuf((void **)(b), (bs), (rs), (es), (a))
+#define sizebuf2(b, rs, a) _sizebuf((void **)(&(b)), &(b ## size), (rs), sizeof(*(b)), (a))
+#define addtobuf(b, c) \
+do { \
+ _sizebuf((void **)(&(b)), &(b ## size), (b ## data) + 1, sizeof(*(b)), 1); \
+ (b)[(b ## data)++] = (c); \
+} while(0)
+#define bufcat(d, s, n) \
+do { \
+ size_t __bufcat_size__; \
+ __bufcat_size__ = (n); \
+ _sizebuf((void **)(&(d)), &(d ## size), (d ## data) + __bufcat_size__, sizeof(*(d)), 1); \
+ memcpy((d) + (d ## data), (s), sizeof(*(d)) * __bufcat_size__); \
+ (d ## data) += __bufcat_size__; \
+} while (0)
+
+#define freeparr(parr) _freeparr((void **)(parr))
+#define parrlen(parr) _parrlen((void **)(parr))
+
+#define CBREG(obj, name, funca, destroya, dataa) \
+do { \
+ struct cbchain_ ## name *__new_cb__; \
+ __new_cb__ = smalloc(sizeof(*__new_cb__)); \
+ __new_cb__->func = funca; \
+ __new_cb__->destroy = destroya; \
+ __new_cb__->data = dataa; \
+ __new_cb__->prev = NULL; \
+ __new_cb__->next = (obj)->name; \
+ (obj)->name = __new_cb__; \
+} while(0)
+
+#define CBUNREG(obj, name, dataa) \
+do { \
+ struct cbchain_ ## name *__cur__; \
+ for(__cur__ = (obj)->name; __cur__ != NULL; __cur__ = __cur__->next) { \
+ if(__cur__->data == (dataa)) { \
+ if(__cur__->destroy != NULL) \
+ __cur__->destroy(__cur__->data); \
+ if(__cur__->prev != NULL) \
+ __cur__->prev->next = __cur__->next; \
+ if(__cur__->next != NULL) \
+ __cur__->next->prev = __cur__->prev; \
+ if(__cur__ == (obj)->name) \
+ (obj)->name = __cur__->next; \
+ free(__cur__); \
+ break; \
+ } \
+ } \
+} while(0)
+
+#define GCBREG(name, funca, dataa) \
+do { \
+ struct cbchain_ ## name *__new_cb__; \
+ __new_cb__ = smalloc(sizeof(*__new_cb__)); \
+ __new_cb__->func = funca; \
+ __new_cb__->data = dataa; \
+ __new_cb__->prev = NULL; \
+ __new_cb__->next = name; \
+ name = __new_cb__; \
+} while(0)
+
+#define CBCHAININIT(obj, name) (obj)->name = NULL
+
+#define CBCHAINFREE(obj, name) \
+do { \
+ struct cbchain_ ## name *__cur__; \
+ while((__cur__ = (obj)->name) != NULL) { \
+ (obj)->name = __cur__->next; \
+ if(__cur__->destroy != NULL) \
+ __cur__->destroy(__cur__->data); \
+ free(__cur__); \
+ } \
+} while(0)
+
+#define CBCHAINDOCB(obj, name, args...) \
+do { \
+ struct cbchain_ ## name *__cur__, *__next__; \
+ for(__cur__ = (obj)->name; __cur__ != NULL; __cur__ = __next__) { \
+ __next__ = __cur__->next; \
+ if(__cur__->func(args, __cur__->data)) { \
+ if(__cur__->next != NULL) \
+ __cur__->next->prev = __cur__->prev; \
+ if(__cur__->prev != NULL) \
+ __cur__->prev->next = __cur__->next; \
+ if(__cur__ == (obj)->name) \
+ (obj)->name = __cur__->next; \
+ free(__cur__); \
+ } \
+ } \
+} while(0)
+
+#define GCBCHAINDOCB(name, args...) \
+({ \
+ struct cbchain_ ## name *__cur__; \
+ int __ret__; \
+ __ret__ = 0; \
+ for(__cur__ = name; __cur__ != NULL; __cur__ = __cur__->next) { \
+ if(__cur__->func(args, __cur__->data)) { \
+ __ret__ = 1; \
+ break; \
+ } \
+ } \
+ __ret__; \
+})
+
+#endif
--- /dev/null
+/Makefile.am/1.3/Wed Aug 25 13:13:58 2004//
+D/doldaconnect////
--- /dev/null
+doldaconnect/include
--- /dev/null
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
--- /dev/null
+pkginclude_HEADERS=doldaconnect/uilib.h doldaconnect/uimisc.h doldaconnect/utils.h
+EXTRA_DIST=doldaconnect
--- /dev/null
+/uilib.h/1.4/Fri Aug 6 19:11:23 2004//
+/uimisc.h/1.5/Fri Aug 13 11:42:30 2004//
+/utils.h/1.2/Wed Jul 28 00:00:38 2004//
+D
--- /dev/null
+doldaconnect/include/doldaconnect
--- /dev/null
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
--- /dev/null
+#ifndef _UILIB_H
+#define _UILIB_H
+
+#include <wchar.h>
+
+struct dc_response
+{
+ struct dc_response *next, *prev;
+ int code, tag;
+ wchar_t *cmdname;
+ void *data;
+ void *internal;
+ struct
+ {
+ int argc;
+ wchar_t **argv;
+ } *rlines;
+ int linessize;
+ int numlines;
+ int curline;
+};
+
+struct dc_intresp
+{
+ int code;
+ int argc;
+ struct
+ {
+ int type;
+ union
+ {
+ int num;
+ wchar_t *str;
+ double flnum;
+ } val;
+ } *argv;
+};
+
+int dc_init(void);
+void dc_cleanup(void);
+void dc_disconnect(void);
+void dc_freeresp(struct dc_response *resp);
+struct dc_response *dc_getresp(void);
+struct dc_response *dc_gettaggedresp(int tag);
+struct dc_response *dc_gettaggedrespsync(int tag);
+int dc_wantwrite(void);
+int dc_getstate(void);
+int dc_queuecmd(int (*callback)(struct dc_response *), void *data, ...);
+int dc_handleread(void);
+int dc_handlewrite(void);
+int dc_connect(char *host, int port);
+struct dc_intresp *dc_interpret(struct dc_response *resp);
+void dc_freeires(struct dc_intresp *ires);
+const char *dc_gethostname(void);
+
+#endif
--- /dev/null
+#ifndef _UIMISC_H
+#define _UIMISC_H
+
+#define DC_LOGIN_ERR_SUCCESS 0
+#define DC_LOGIN_ERR_NOLOGIN 1
+#define DC_LOGIN_ERR_SERVER 2
+#define DC_LOGIN_ERR_USER 3
+#define DC_LOGIN_ERR_CONV 4
+#define DC_LOGIN_ERR_AUTHFAIL 5
+
+#define DC_LOGIN_CONV_NOECHO 0
+#define DC_LOGIN_CONV_ECHO 1
+#define DC_LOGIN_CONV_INFO 2
+#define DC_LOGIN_CONV_ERROR 3
+
+#define DC_FNN_STATE_SYN 0
+#define DC_FNN_STATE_HS 1
+#define DC_FNN_STATE_EST 2
+#define DC_FNN_STATE_DEAD 3
+
+#define DC_TRNS_WAITING 0
+#define DC_TRNS_HS 1
+#define DC_TRNS_MAIN 2
+#define DC_TRNS_DONE 3
+
+#define DC_TRNSD_UNKNOWN 0
+#define DC_TRNSD_UP 1
+#define DC_TRNSD_DOWN 2
+
+#define DC_TRNSE_NOERROR 0
+#define DC_TRNSE_NOTFOUND 1
+#define DC_TRNSE_NOSLOTS 2
+
+#include <time.h>
+
+struct dc_fnetnode
+{
+ struct dc_fnetnode *next, *prev;
+ int id;
+ wchar_t *name;
+ wchar_t *fnet;
+ int state;
+ int numusers;
+ int found;
+ void (*destroycb)(struct dc_fnetnode *fn);
+ void *udata;
+};
+
+struct dc_transfer
+{
+ struct dc_transfer *next, *prev;
+ int id;
+ int dir, state;
+ wchar_t *peerid, *peernick;
+ wchar_t *path;
+ int size, curpos;
+ int found;
+ int error;
+ time_t errortime;
+ void (*destroycb)(struct dc_transfer *transfer);
+ void *udata;
+};
+
+void dc_loginasync(char *username, int useauthless, int (*conv)(int, wchar_t *, char **, void *), void (*callback)(int, wchar_t *, void *), void *udata);
+struct dc_fnetnode *dc_findfnetnode(int id);
+void dc_getfnlistasync(void (*callback)(int, void *), void *udata);
+void dc_uimisc_handlenotify(struct dc_response *resp);
+struct dc_transfer *dc_findtransfer(int id);
+void dc_gettrlistasync(void (*callback)(int, void *), void *udata);
+wchar_t **dc_lexsexpr(wchar_t *sexpr);
+void dc_freewcsarr(wchar_t **arr);
+
+extern struct dc_fnetnode *dc_fnetnodes;
+extern struct dc_transfer *dc_transfers;
+
+#endif
--- /dev/null
+#ifndef _UTILS_H
+#define _UTILS_H
+
+#include <stdarg.h>
+#include <stdlib.h>
+
+/* "Safe" functions */
+#define smalloc(size) ({void *__result__; ((__result__ = malloc(size)) == NULL)?({exit(-1); (void *)0;}):__result__;})
+#define srealloc(ptr, size) ({void *__result__; ((__result__ = realloc((ptr), (size))) == NULL)?({exit(-1); (void *)0;}):__result__;})
+#define swcsdup(wcs) ((wchar_t *)wcscpy(smalloc(sizeof(wchar_t) * (wcslen(wcs) + 1)), (wcs)))
+#define sstrdup(str) ((char *)strcpy(smalloc(strlen(str) + 1), (str)))
+
+char *vsprintf2(char *format, va_list al);
+char *sprintf2(char *format, ...);
+wchar_t *vswprintf2(wchar_t *format, va_list al);
+wchar_t *swprintf2(wchar_t *format, ...);
+wchar_t *icmbstowcs(char *mbs, char *charset);
+wchar_t *icsmbstowcs(char *mbs, char *charset, wchar_t *def);
+char *icwcstombs(wchar_t *wcs, char *charset);
+char *icswcstombs(wchar_t *wcs, char *charset, char *def);
+wchar_t *wcstolower(wchar_t *wcs);
+void _sizebuf(void **buf, size_t *bufsize, size_t reqsize, size_t elsize, int algo);
+
+#define sizebuf(b, bs, rs, es, a) _sizebuf((void **)(b), (bs), (rs), (es), (a))
+#define sizebuf2(b, rs, a) _sizebuf((void **)(&(b)), &(b ## size), (rs), sizeof(*(b)), (a))
+#define addtobuf(b, c) \
+do { \
+ _sizebuf((void **)(&(b)), &(b ## size), (b ## data) + 1, sizeof(*(b)), 1); \
+ (b)[(b ## data)++] = (c); \
+} while(0)
+#define bufcat(d, s, n) \
+do { \
+ size_t __bufcat_size__; \
+ __bufcat_size__ = (n); \
+ _sizebuf((void **)(&(d)), &(d ## size), (d ## data) + __bufcat_size__, sizeof(*(d)), 1); \
+ memcpy((d) + (d ## data), (s), sizeof(*(d)) * __bufcat_size__); \
+ (d ## data) += __bufcat_size__; \
+} while (0)
+
+#endif
--- /dev/null
+/Makefile.am/1.5/Mon Oct 4 02:05:35 2004//
+/initcmds.h/1.8/Mon Jan 24 01:31:54 2005//
+/makecmds/1.2/Wed Aug 4 12:46:13 2004//
+/uicmds/1.9/Mon Jan 24 01:32:02 2005//
+/uilib.c/1.13/Mon May 9 23:16:56 2005//
+/uilib.h/1.1.1.1/Tue May 11 15:48:00 2004//
+/uimisc.c/1.7/Mon May 9 23:20:49 2005//
+/utils.c/1.2/Wed Jul 28 00:00:47 2004//
+D/guile////
--- /dev/null
+doldaconnect/lib
--- /dev/null
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
--- /dev/null
+EXTRA_DIST = makecmds uicmds
+
+SUBDIRS=. @extlibs@
+DIST_SUBDIRS=guile
+
+lib_LTLIBRARIES = libdcui.la
+
+libdcui_la_SOURCES = uilib.c uimisc.c utils.c
+libdcui_la_LIBADD = @KRB5_LDADD@
+
+BUILT_SOURCES = initcmds.h
+
+initcmds.h: uicmds makecmds
+ ./makecmds <uicmds >initcmds.h
+
+AM_CPPFLAGS=-I$(top_srcdir)/include
--- /dev/null
+/Makefile.am/1.2/Sat May 14 14:23:25 2005//
+/autodl/1.16/Sun Oct 9 02:53:45 2005//
+/chatlog/1.2/Wed Jul 13 00:24:24 2005//
+/dolcon-guile.c/1.3/Fri Sep 24 00:48:41 2004//
+D/dolcon////
--- /dev/null
+doldaconnect/lib/guile
--- /dev/null
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
--- /dev/null
+SUBDIRS=dolcon
+
+EXTRA_DIST=autodl chatlog
+
+lib_LTLIBRARIES=libdolcon-guile.la
+
+libdolcon_guile_la_SOURCES=dolcon-guile.c
+libdolcon_guile_la_LIBADD=@GUILE_LDFLAGS@ $(top_srcdir)/lib/libdcui.la
+libdolcon_guile_la_CPPFLAGS=@GUILE_CFLAGS@
--- /dev/null
+#!/usr/bin/guile -s
+!#
+
+(use-modules (dolcon ui))
+(use-modules (ice-9 pretty-print))
+
+(define sr '())
+(define lastsearch 0)
+(define info-searcheta 0)
+(define info-numavail 0)
+(define info-numreal 0)
+(define info-numtotal 0)
+(define lastparse 0)
+(define srchid -1)
+(define session '())
+(define trans '())
+(define dpeers '())
+(define lastdl 0)
+
+(define (logf msg)
+ (write-line msg (current-output-port))
+ (catch 'system-error (lambda ()
+ (fsync (current-output-port)))
+ (lambda (key . err) #f))
+ )
+
+(define (make-getopt opts optdesc)
+ (let ((arg opts) (curpos 0) (rest '()))
+ (lambda ()
+ (if (eq? arg '()) rest
+ (let ((ret #f))
+ (while (not ret)
+ (if (= curpos 0)
+ (if (eq? (string-ref (car arg) 0) #\-)
+ (set! curpos 1)
+ (begin
+ (set! rest (append rest (list (car arg))))
+ (set! arg (cdr arg))
+ (if (eq? arg '())
+ (set! ret #t)))))
+ (if (> curpos 0)
+ (if (< curpos (string-length (car arg)))
+ (begin (set! ret (string-ref (car arg) curpos)) (set! curpos (+ curpos 1)))
+ (begin (set! curpos 0) (set! arg (cdr arg)) (if (eq? arg '()) (set! ret #t))))))
+ (if (eq? ret #t) rest
+ (let ((opt (string-index optdesc ret)))
+ (if (eq? opt #f) (throw 'illegal-option ret)
+ (if (and (< opt (- (string-length optdesc) 1)) (eq? (string-ref optdesc (+ opt 1)) #\:))
+ (let ((ret
+ (cons ret (let ((optarg
+ (if (< curpos (string-length (car arg)))
+ (substring (car arg) curpos)
+ (begin (set! arg (cdr arg)) (if (eq? arg '()) (throw 'requires-argument ret)) (car arg)))))
+ (set! arg (cdr arg)) optarg))))
+ (set! curpos 0)
+ ret)
+ (list ret))))))))))
+
+(define (ftime)
+ (let ((ctime (gettimeofday)))
+ (+ (car ctime) (/ (cdr ctime) 1000000))))
+
+(define (wanttosearch)
+ (> (- (current-time) lastsearch)
+ (if (> (length trans) 0) 300 60))
+ )
+
+(define defspeed '())
+(let ((matchlist (list
+ (cons (make-regexp "^[][{}() ]*BBB" regexp/icase) 100000))))
+ (set! defspeed
+ (lambda (sr)
+ (catch 'ret
+ (lambda ()
+ (for-each (lambda (o)
+ (if (regexp-exec (car o) (cadr (cdr (assoc 'peer sr))))
+ (throw 'ret (cdr o))))
+ matchlist)
+ 15000)
+ (lambda (sig ret)
+ ret))
+ )))
+
+(define (sr-less? sr1 sr2)
+ (let ((s1 (if (cdr (assoc 'speed sr1)) (cdr (assoc 'speed sr1)) (defspeed sr1)))
+ (s2 (if (cdr (assoc 'speed sr2)) (cdr (assoc 'speed sr2)) (defspeed sr2))))
+ (if (= s1 s2)
+ (< (cdr (assoc 'resptime sr1)) (cdr (assoc 'resptime sr2)))
+ (> s1 s2)))
+ )
+
+(define (srg-less? g1 g2)
+ (or (> (length (cdr g1)) (length (cdr g2)))
+ (and (= (length (cdr g1)) (length (cdr g2)))
+ (> (car g1) (car g2))))
+ )
+
+(define (gettrbysize size)
+ (catch 'ret
+ (lambda ()
+ (for-each (lambda (o)
+ (if (= (cdr (assoc 'size (cdr o))) size)
+ (throw 'ret (cdr o))))
+ trans)
+ #f)
+ (lambda (sig ret)
+ ret))
+ )
+
+(define (download sr)
+ (let ((resp #f))
+ (let ((args (list "download"
+ (car (cdr (assoc 'peer sr)))
+ (cadr (cdr (assoc 'peer sr)))
+ (cdr (assoc 'filename sr))
+ (cdr (assoc 'size sr)))))
+ (let ((tag (assoc 'tag session)))
+ (if tag (set! args (append args (list "tag" (cdr tag))))))
+ (let ((uarg (assoc 'uarg session)))
+ (if uarg (set! args (append args (list "user" (cdr uarg))))))
+ (set! resp (apply dc-ecmd-assert 200 args)))
+ (let ((id (car (dc-intresp resp))))
+ (set! trans
+ (cons (cons id (list (assoc 'size sr)
+ (assoc 'peer sr)
+ (assoc 'filename sr)
+ (assoc 'resptime sr)
+ '(curpos . 0)
+ '(state . wait)
+ '(curspeed . #f)
+ '(lastpos . 0)
+ (cons 'id id)
+ (cons 'lasttime (current-time))
+ (cons 'lastprog (current-time))))
+ trans))
+ (logf (string-append "downloading "
+ (cdr (assoc 'filename sr))
+ " from "
+ (cadr (cdr (assoc 'peer sr)))
+ ", "
+ (number->string (cdr (assoc 'size sr)))
+ " bytes (id "
+ (number->string id)
+ ", "
+ (number->string (cdr (assoc 'slots sr)))
+ " slots), timing out in "
+ (number->string (max 10 (* (cdr (assoc 'resptime sr)) 2)))
+ " seconds"))))
+ (set! lastdl (current-time))
+ )
+
+(define (disablepeer peer)
+ (let ((newglist '()) (numrem 0))
+ (for-each (lambda (g)
+ (let ((newlist '()))
+ (for-each (lambda (o)
+ (if (not (equal? (cdr (assoc 'peer o)) peer))
+ (set! newlist (cons o newlist))
+ (set! numrem (+ numrem 1))))
+ (cdr g))
+ (if (not (eq? newlist '()))
+ (set! newglist (cons (cons (car g) (sort newlist sr-less?)) newglist)))))
+ sr)
+ (set! sr (sort newglist srg-less?))
+ (logf (string-append "disabled " (cadr peer) " and removed " (number->string numrem) " search results")))
+ (let* ((dpa (assoc peer dpeers)) (dp (and (pair? dpa) (cdr dpa))))
+ (if dp
+ (set-cdr! (assoc 'time dp) (current-time))
+ (set! dpeers (cons (cons peer (list (cons 'time (current-time))
+ (cons 'peer peer)))
+ dpeers))))
+ )
+
+(define (checktrans)
+ (let ((time (current-time)))
+ (for-each (lambda (o)
+ (if (and (memq (cdr (assoc 'state (cdr o))) '(wait hs))
+ (> (- time (cdr (assoc 'lastprog (cdr o)))) (max 10 (* (cdr (assoc 'resptime (cdr o))) 2))))
+ (begin (logf (string-append "transfer " (number->string (car o)) " timing out"))
+ (dc-ecmd-assert 200 "cancel" (car o))
+ (disablepeer (cdr (assoc 'peer (cdr o))))
+ (set! trans (assq-remove! trans (car o)))))
+ (if (and (eq? (cdr (assoc 'state (cdr o))) 'main)
+ (> (- time (cdr (assoc 'lastprog (cdr o)))) 60))
+ (begin (logf (string-append "transfer " (number->string (car o)) " seems to have stalled"))
+ (dc-ecmd-assert 200 "cancel" (car o))
+ (set! trans (assq-remove! trans (car o)))))
+ (if (and (eq? (cdr (assoc 'state (cdr o))) 'main)
+ (> (- (cdr (assoc 'lastprog (cdr o))) (cdr (assoc 'lasttime (cdr o)))) 20))
+ (begin (set-cdr! (assoc 'curspeed (cdr o))
+ (/ (- (cdr (assoc 'curpos (cdr o))) (cdr (assoc 'lastpos (cdr o))))
+ (- (cdr (assoc 'lastprog (cdr o))) (cdr (assoc 'lasttime (cdr o))))))
+ (set-cdr! (assoc 'lastpos (cdr o)) (cdr (assoc 'curpos (cdr o))))
+ (set-cdr! (assoc 'lasttime (cdr o)) (cdr (assoc 'lastprog (cdr o)))))))
+ trans))
+ )
+
+(define (write-info-file)
+ (if (assoc 'info-file session)
+ (let ((op (open-output-file (cdr (assoc 'info-file session)))))
+ (write (list (cons 'numdl (length trans))
+ (cons 'lastdl lastdl)
+ (cons 'availsr info-numavail)
+ (cons 'realsr info-numreal)
+ (cons 'totalsr info-numtotal)
+ (cons 'lastsrch lastsearch)
+ (cons 'srcheta info-searcheta))
+ op)
+ (newline op)
+ (close-port op))))
+
+(define (parseresults)
+ (logf (string-append "entering parseresults with "
+ (number->string
+ (apply + (map (lambda (o) (length (cdr o))) sr)))
+ " results in "
+ (number->string (length sr))
+ " sizes"))
+ (let ((retval #t) (numreal 0) (numtotal 0) (numavail 0))
+ (catch 'ret
+ (lambda ()
+ (and (eq? sr '()) (throw 'ret #f))
+ (let ((numrem 0) (countrem 0) (newglist '()))
+ (for-each (lambda (g)
+ (let ((newlist '()))
+ (for-each (lambda (o)
+ (if (< (- (current-time) (cdr (assoc 'recvtime o))) 300)
+ (set! newlist (cons o newlist))
+ (set! countrem (+ countrem 1))))
+ (cdr g))
+ (if (> (length newlist) 0)
+ (set! newglist (cons (cons (car g) (sort newlist sr-less?)) newglist))
+ (set! numrem (+ numrem 1)))))
+ sr)
+ (set! sr (sort newglist srg-less?))
+ (if (> countrem 0)
+ (logf (string-append "removed " (number->string countrem) " time-outed results and " (number->string numrem) " entire sizes"))))
+ (let ((numrem 0) (newlist '()))
+ (for-each (lambda (o)
+ (if (> (- (current-time) (cdr (assoc 'time o))) 1800)
+ (set! numrem (+ numrem 1))
+ (set! newlist (cons o newlist))))
+ dpeers)
+ (set! dpeers newlist)
+ (logf (string-append "re-enabled " (number->string numrem) " disabled users")))
+ (let ((numrem 0) (countrem 0) (newglist '()))
+ (for-each (lambda (g)
+ (let ((newlist '()))
+ (for-each (lambda (o)
+ (if (not (assoc (cdr (assoc 'peer o)) dpeers))
+ (set! newlist (cons o newlist))
+ (set! countrem (+ countrem 1))))
+ (cdr g))
+ (if (> (length newlist) 0)
+ (set! newglist (cons (cons (car g) (sort newlist sr-less?)) newglist))
+ (set! numrem (+ numrem 1)))))
+ sr)
+ (set! sr (sort newglist srg-less?))
+ (if (> countrem 0)
+ (logf (string-append "removed " (number->string countrem) " results with disabled peers and " (number->string numrem) " entire sizes"))))
+ (and (eq? sr '()) (throw 'ret #f))
+ (set! numtotal (apply + (map (lambda (o) (length (cdr o))) sr)))
+ (let* ((maxsize (apply max (map (lambda (o) (length (cdr o))) sr)))
+ (minsize (/ maxsize 3)))
+ (let ((numrem 0) (countrem 0))
+ (for-each (lambda (o) (if (< (length (cdr o)) minsize)
+ (begin (set! countrem (+ countrem (length (cdr o))))
+ (set! numrem (+ numrem 1)))))
+ sr)
+ (if (> countrem 0)
+ (logf (string-append "will disregard " (number->string countrem) " results from " (number->string numrem) " sizes due to popularity lack")))
+ (set! numreal (- numtotal countrem)))
+ (let ((numrem 0) (numrrem 0))
+ (for-each (lambda (g)
+ (for-each (lambda (o)
+ (if (< (cdr (assoc 'slots o)) 1)
+ (begin (set! numrem (+ numrem 1))
+ (if (>= (length (cdr g)) minsize)
+ (set! numrrem (+ numrrem 1))))))
+ (cdr g)))
+ sr)
+ (if (> numrem 0)
+ (logf (string-append (number->string numrem) " results had no slots")))
+ (set! numavail (- numreal numrrem)))
+ (for-each (lambda (g)
+ (if (>= (length (cdr g)) minsize)
+ (catch 'found
+ (lambda ()
+ (for-each (lambda (o)
+ (and (> (cdr (assoc 'slots o)) 0)
+ (throw 'found o)))
+ (cdr g)))
+ (lambda (sig sr)
+ (let ((tr (gettrbysize (cdr (assoc 'size sr)))))
+ (if (not tr)
+ (if (< (length trans) (cdr (assoc 'maxtrans session)))
+ (download sr))
+ (if (and (cdr (assoc 'curspeed tr))
+ (not (equal? (cdr (assoc 'peer sr)) (cdr (assoc 'peer tr))))
+ (> (- (or (cdr (assoc 'speed sr)) (defspeed sr)) (cdr (assoc 'curspeed tr))) 10000))
+ (begin (logf (string-append "abandoning transfer "
+ (number->string (cdr (assoc 'id tr)))
+ " for possible faster sender"))
+ (dc-ecmd-assert 200 "cancel" (cdr (assoc 'id tr)))
+ (set! trans (assq-remove! trans (cdr (assoc 'id tr))))
+ (download sr)))))))))
+ sr)
+ )
+ )
+ (lambda (sig ret)
+ (set! retval ret)
+ ))
+ (set! info-numavail numavail)
+ (set! info-numreal numreal)
+ (set! info-numtotal numtotal)
+ retval)
+ )
+
+(define (handlesr filename fnet peer size slots resptime)
+ (let ((cl (or (assoc size sr)
+ (let ((newp (cons size '()))) (set! sr (append sr (list newp))) newp)))
+ (newsr (list
+ (cons 'filename filename)
+ (cons 'peer (list fnet peer))
+ (cons 'size size)
+ (cons 'slots slots)
+ (cons 'resptime resptime)
+ (cons 'speed (getspeed peer))
+ (cons 'recvtime (current-time))
+ (cons 'dis #f)))
+ (newlist '()))
+ (for-each (lambda (o) (if (not (and (equal? (cdr (assoc 'filename o)) filename)
+ (equal? (cdr (assoc 'peer o)) (list fnet peer))))
+ (set! newlist (cons o newlist))))
+ (cdr cl))
+ (set-cdr! cl (sort (cons newsr newlist) sr-less?))
+ )
+ )
+
+; XXX: Redefine to go through the server, once that is implemented
+(define (getspeed username)
+ (catch 'system-error
+ (lambda ()
+ (let* ((port (open-input-file (string-append (getenv "HOME") "/dc/users/" username))) (avg 0) (numdls (string->number (read-line port))) (max (string->number (read-line port))) (numents (string->number (read-line port))))
+ (do ((i 0 (+ i 1))) ((= i numents) (close-port port) (/ avg numents)) (set! avg (+ avg (string->number (read-line port)))))
+ ))
+ (lambda args
+ #f
+ )
+ )
+ )
+
+(define (validate-session session)
+ (catch 'wrong-type-arg
+ (lambda ()
+ (and
+ (assoc 'sexpr session)
+ (assoc 'prio session)
+ (assoc 'maxtrans session)
+ #t
+ )
+ )
+ (lambda (key . args)
+ (display "Session data is not an a-list\n" (current-error-port))
+ #f)
+ )
+ )
+
+(define (autodl-main args)
+ (let ((dc-server #f) (done #f) (retval 0))
+ (let ((getopt (make-getopt (cdr args) "hs:S:e:p:t:a:I:")) (arg #f))
+ (do ((arg (getopt) (getopt))) ((not (and (pair? arg) (char? (car arg)))) (set! args arg))
+ (cond ((eq? (car arg) #\h)
+ (begin (display "usage: autodl [-s server] -S sessfile\n" (current-error-port))
+ (display " autodl [-s server] -e search-expression [-p prio] [-t tag] [-a userarg]\n" (current-error-port))
+ (display " autodl [-s server]\n" (current-error-port))
+ (display " autodl -h\n" (current-error-port))
+ (exit 0)))
+ ((eq? (car arg) #\s)
+ (set! dc-server (cdr arg)))
+ ((eq? (car arg) #\S)
+ (let ((port (open-file (cdr arg)))) (set! session (read port)) (close-port port)))
+ ((eq? (car arg) #\p)
+ (let ((c (assoc 'prio session)))
+ (if c (set-cdr! c (cdr arg))
+ (set! session (cons (cons 'prio (cdr arg)) session)))))
+ ((eq? (car arg) #\t)
+ (let ((c (assoc 'tag session)))
+ (if c (set-cdr! c (cdr arg))
+ (set! session (cons (cons 'tag (cdr arg)) session)))))
+ ((eq? (car arg) #\a)
+ (let ((c (assoc 'uarg session)))
+ (if c (set-cdr! c (cdr arg))
+ (set! session (cons (cons 'uarg (cdr arg)) session)))))
+ ((eq? (car arg) #\I)
+ (let ((c (assoc 'info-file session)))
+ (if c (set-cdr! c (cdr arg))
+ (set! session (cons (cons 'info-file (cdr arg)) session)))))
+ ((eq? (car arg) #\e)
+ (set! session (cons (cons 'sexpr (dc-lexsexpr (cdr arg))) session)))
+ )
+ )
+ )
+ (if (eq? session '()) (begin (if (isatty? (current-input-port)) (display "Enter session data (s-expr):\n" (current-error-port))) (set! session (read))))
+ (if (not (assoc 'prio session))
+ (set! session (cons '(prio . 10) session)))
+ (if (not (assoc 'maxtrans session))
+ (set! session (cons '(maxtrans . 1) session)))
+ (if (not (validate-session session)) (begin (display "Invalid session!\n" (current-error-port)) (exit 1)))
+ (if (not dc-server) (set! dc-server (getenv "DCSERVER")))
+ (if (not dc-server) (set! dc-server "localhost"))
+ (catch 'system-error
+ (lambda ()
+ (dc-c&l #t dc-server #t))
+ (lambda (key . args)
+ (logf (string-append "could not connect to server: " (apply format #f (cadr args) (caddr args))))
+ (exit 2)))
+ (dc-ecmd-assert 200 "notify" "all" "on")
+ (for-each (lambda (sig) (sigaction sig (lambda (sig) (throw 'sig sig)))) (list SIGINT SIGTERM SIGHUP))
+ (catch 'sig
+ (lambda ()
+ (while #t
+ (if (and (not (= lastsearch -1)) (wanttosearch))
+ (begin
+ (if (not (= srchid -1))
+ (dc-ecmd "cansrch" srchid))
+ (let* ((resp (apply dc-ecmd-assert (append (list '(200 501 509) "search" "prio" (number->string (cdr (assoc 'prio session))) "all") (cdr (assoc 'sexpr session)))))
+ (ires (dc-intresp resp))
+ (eres (dc-extract resp)))
+ (case (cdr (assoc 'code eres))
+ ((200)
+ (begin (set! srchid (car ires))
+ (logf (string-append "search scheduled in " (number->string (cadr ires)) " seconds (id " (number->string srchid) ")"))
+ (set! info-searcheta (+ (current-time) (cadr ires)))
+ (set! lastsearch -1)))
+ ((501)
+ (begin (set! srchid -1)
+ (logf (string-append "no fnetnodes available to search on"))
+ (set! lastsearch (current-time))))
+ ((509)
+ (begin (logf "illegal search expression")
+ (set! done #t)
+ (set! retval 3)
+ (throw 'sig 0)))))))
+ (checktrans)
+ (if (> (- (current-time) lastparse) 20)
+ (begin (parseresults)
+ (set! lastparse (current-time))))
+ (write-info-file)
+ (dc-select 10000)
+ (while (let ((resp (dc-getresp)))
+ (if resp
+ (begin
+ (let* ((er (dc-extract resp)) (code (cdr (assoc 'code er))) (cmd (cdr (assoc 'cmd er))))
+ (cond
+ ((equal? cmd ".notify")
+ (case code
+ ((611) ; Transfer state change
+ (let ((ires (dc-intresp resp)) (tr #f))
+ (if (and ires (assoc (car ires) trans))
+ (begin (set! tr (cdr (assoc (car ires) trans)))
+ (set-cdr! (assoc 'state tr)
+ (cdr (assoc (cadr ires) '((0 . wait) (1 . hs) (2 . main) (3 . done)))))
+ (set-cdr! (assoc 'lastprog tr) (current-time))))))
+ ((614) ; Transfer error
+ (let ((ires (dc-intresp resp)))
+ (if (and ires (assoc (car ires) trans))
+ (begin (logf (string-append "transfer " (number->string (car ires)) " encountered error " (number->string (cadr ires))))
+ (dc-ecmd-assert 200 "cancel" (car ires))
+ (let ((tr (cdr (assoc (car ires) trans))))
+ (disablepeer (cdr (assoc 'peer tr))))
+ (set! trans (assq-remove! trans (car ires)))))))
+ ((615) ; Transfer progress
+ (let ((ires (dc-intresp resp)) (tr #f))
+ (if (and ires (assoc (car ires) trans))
+ (begin (set! tr (cdr (assoc (car ires) trans)))
+ (set-cdr! (assoc 'curpos tr) (cadr ires))
+ (set-cdr! (assoc 'lastprog tr) (current-time))))))
+ ((617) ; Transfer destroyed
+ (let* ((ires (dc-intresp resp)) (tr (and ires (assoc (car ires) trans))))
+ (if tr
+ (begin (if (eq? (cdr (assoc 'state (cdr tr))) 'done)
+ (begin (logf (string-append "transfer " (number->string (car ires)) " done"))
+ (set! trans (assq-remove! trans (car ires)))
+ (set! done #t)
+ (throw 'sig 0))
+ (begin (logf (string-append "transfer " (number->string (car ires)) " disappeared"))
+ (set! trans (assq-remove! trans (car ires)))))))))
+ ((620) ; Search rescheduled
+ (let ((ires (dc-intresp resp)))
+ (if (and ires (= (car ires) srchid))
+ (begin (set! info-searcheta (+ (current-time) (cadr ires)))
+ (logf (string-append "search rescheduled to T+" (number->string (cadr ires))))))))
+ ((621) ; Search committed
+ (let ((ires (dc-intresp resp)))
+ (if (and ires (= (car ires) srchid))
+ (begin (logf "search committed")
+ (set! info-searcheta 0)
+ (set! lastsearch (current-time))))))
+ ((622) ; Search result
+ (let ((ires (list->vector (dc-intresp resp))))
+ (if (and ires (= (vector-ref ires 0) srchid)) (apply handlesr (map (lambda (n) (vector-ref ires n)) '(1 2 3 4 5 7))))))
+
+ )
+ )
+
+ )
+ )
+ #t)
+ #f)
+ )
+ #t
+ )
+ )
+ )
+ (lambda (key sig)
+ (logf (string-append "interrupted by signal " (number->string sig)))
+ (if (not done)
+ (set! retval 1)))
+ )
+ (logf "quitting...")
+ (catch 'sig
+ (lambda ()
+ (if (dc-connected)
+ (begin (for-each (lambda (o)
+ (dc-qcmd (list "cancel" (car o))))
+ trans)
+ (if (assoc 'info-file session)
+ (catch 'system-error
+ (lambda ()
+ (delete-file (cdr (assoc 'info-file session))))
+ (lambda (key . args) #t)))
+ (if (and done (assoc 'tag session))
+ (dc-qcmd (list "filtercmd" "rmtag" (cdr (assoc 'tag session)))))
+ (if (not (= srchid -1))
+ (dc-qcmd (list "cansrch" srchid)))
+ (dc-qcmd '("quit"))
+ (while (dc-connected) (dc-select))
+ )))
+ (lambda (key sig)
+ (logf "forcing quit")))
+ (exit retval)
+ )
+ )
+
+(setlocale LC_ALL "")
+(autodl-main (command-line))
--- /dev/null
+#!/usr/bin/guile -s
+!#
+
+(use-modules (dolcon ui))
+(use-modules (ice-9 pretty-print))
+
+(define fnetnodes '())
+
+(define (make-getopt opts optdesc)
+ (let ((arg opts) (curpos 0) (rest '()))
+ (lambda ()
+ (if (eq? arg '()) rest
+ (let ((ret #f))
+ (while (not ret)
+ (if (= curpos 0)
+ (if (eq? (string-ref (car arg) 0) #\-)
+ (set! curpos 1)
+ (begin
+ (set! rest (append rest (list (car arg))))
+ (set! arg (cdr arg))
+ (if (eq? arg '())
+ (set! ret #t)))))
+ (if (> curpos 0)
+ (if (< curpos (string-length (car arg)))
+ (begin (set! ret (string-ref (car arg) curpos)) (set! curpos (+ curpos 1)))
+ (begin (set! curpos 0) (set! arg (cdr arg)) (if (eq? arg '()) (set! ret #t))))))
+ (if (eq? ret #t) rest
+ (let ((opt (string-index optdesc ret)))
+ (if (eq? opt #f) (throw 'illegal-option ret)
+ (if (and (< opt (- (string-length optdesc) 1)) (eq? (string-ref optdesc (+ opt 1)) #\:))
+ (let ((ret
+ (cons ret (let ((optarg
+ (if (< curpos (string-length (car arg)))
+ (substring (car arg) curpos)
+ (begin (set! arg (cdr arg)) (if (eq? arg '()) (throw 'requires-argument ret)) (car arg)))))
+ (set! arg (cdr arg)) optarg))))
+ (set! curpos 0)
+ ret)
+ (list ret))))))))))
+
+(define (fn-getnames)
+ (let ((resp (dc-ecmd "lsnodes")) (er #f))
+ (if (and resp (begin (set! er (dc-extract resp)) er) (= (cdr (assoc 'code er)) 200))
+ (let ((ires #f))
+ (while (begin (set! ires (dc-intresp resp)) ires)
+ (if (assoc (car ires) fnetnodes)
+ (set-cdr! (assoc (car ires) fnetnodes) (caddr ires))
+ (set! fnetnodes (cons (cons (car ires) (caddr ires)) fnetnodes))))))))
+
+(define (fn-getname id)
+ (if (not (assoc id fnetnodes))
+ (fn-getnames))
+ (if (assoc id fnetnodes)
+ (cdr (assoc id fnetnodes))
+ (number->string id)))
+
+;(define (fn-getname id)
+; (let ((resp (dc-ecmd "lsnodes")) (er #f))
+; (if (and resp (begin (set! er (dc-extract resp)) er) (= (cdr (assoc 'code er)) 200))
+; (begin
+; (catch 'found
+; (lambda ()
+; (let ((ires #f))
+; (while (begin (set! ires (dc-intresp resp)) ires)
+; (if (= (car ires) id)
+; (throw 'found (caddr ires)))
+; ))
+; (number->string id)
+; )
+; (lambda (key ret)
+; ret)))
+; (number->string id)))
+; )
+
+(define (chatlog-main args)
+ (let ((dc-server #f) (log-dir #f) (last-fn #f))
+ (let ((getopt (make-getopt (cdr args) "hs:S:e:")) (arg #f))
+ (do ((arg (getopt) (getopt))) ((not (and (pair? arg) (char? (car arg)))) (set! args arg))
+ (cond ((eq? (car arg) #\h)
+ (begin (display "usage: chatlog [-s server] [-d log-dir]\n" (current-error-port))
+ (display " chatlog -h\n" (current-error-port))
+ (exit 0)))
+ ((eq? (car arg) #\s)
+ (set! dc-server (cdr arg)))
+ ((eq? (car arg) #\d)
+ (set! log-dir (cdr arg)))
+ )
+ )
+ )
+ (if (not dc-server) (set! dc-server (getenv "DCSERVER")))
+ (if (not dc-server) (set! dc-server "localhost"))
+ (if (not log-dir) (set! log-dir (string-append (getenv "HOME") "/dc/chatlog")))
+
+ (dc-c&l #t dc-server #t)
+ (dc-ecmd-assert 200 "notify" "fn:chat" "on" "fn:act" "on")
+
+ (while #t
+ (dc-select 10000)
+ (while (let ((resp (dc-getresp)))
+ (if resp
+ (begin
+ (let* ((er (dc-extract resp)) (code (cdr (assoc 'code er))) (cmd (cdr (assoc 'cmd er))))
+ (cond
+ ((equal? cmd ".notify")
+ (case code
+ ((600)
+ (let ((ires (list->vector (dc-intresp resp))))
+ (if ires
+ (let ((p (open-file
+ (string-append log-dir "/"
+ (let ((fixedname (list->string
+ (map (lambda (c) (if (eq? c #\/) #\_ c))
+ (string->list (fn-getname (vector-ref ires 0)))))))
+ (if (= (string-length fixedname) 0) "noname" fixedname)))
+ "a")))
+ (if (not (eq? (vector-ref ires 0) last-fn))
+ (begin (write-line (string-append " -- " (fn-getname (vector-ref ires 0)) ":"))
+ (set! last-fn (vector-ref ires 0))))
+ (for-each
+ (lambda (p)
+ (write-line (string-append (strftime "%H:%M:%S" (localtime (current-time))) ": <" (vector-ref ires 3) "> " (vector-ref ires 4)) p))
+ (list p (current-output-port)))
+ (close-port p))
+ ))
+ )
+ ((602)
+ (let ((ires (dc-intresp resp)))
+ (if ires
+ (let ((ent (assoc (car ires) fnetnodes)))
+ (if ent
+ (set-cdr! ent (cadr ires))
+ (set! fnetnodes (cons (cons (car ires) (cadr ires)) fnetnodes)))))))
+
+ )
+ )
+
+ )
+ )
+ #t)
+ #f)
+ )
+ #t
+ )
+
+ )
+ )
+ )
+
+(chatlog-main (command-line))
--- /dev/null
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/poll.h>
+#include <errno.h>
+#include <libguile.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <doldaconnect/uilib.h>
+#include <doldaconnect/uimisc.h>
+#include <doldaconnect/utils.h>
+
+struct respsmob
+{
+ struct dc_response *resp;
+};
+
+struct scmcb
+{
+ SCM subr;
+};
+
+static int fd = -1;
+static scm_bits_t resptype;
+
+static SCM scm_dc_connect(SCM host, SCM port)
+{
+ int cport;
+
+ SCM_ASSERT(SCM_STRINGP(host), host, SCM_ARG1, "dc-connect");
+ if(port == SCM_UNDEFINED)
+ {
+ cport = -1;
+ } else {
+ SCM_ASSERT(SCM_INUMP(port), port, SCM_ARG2, "dc-connect");
+ cport = SCM_INUM(port);
+ }
+ if(fd >= 0)
+ dc_disconnect();
+ if((fd = dc_connect(SCM_STRING_CHARS(host), cport)) < 0)
+ scm_syserror("dc-connect");
+ return(SCM_MAKINUM(fd));
+}
+
+static SCM scm_dc_disconnect(void)
+{
+ dc_disconnect();
+ return(SCM_MAKINUM(0));
+}
+
+static SCM scm_dc_connected(void)
+{
+ return((fd == -1)?SCM_BOOL_F:SCM_BOOL_T);
+}
+
+static SCM scm_dc_select(SCM timeout)
+{
+ struct pollfd pfd;
+ int cto, ret, enob;
+
+ if(timeout == SCM_UNDEFINED)
+ {
+ cto = -1;
+ } else {
+ SCM_ASSERT(SCM_INUMP(timeout), timeout, SCM_ARG1, "dc-select");
+ cto = SCM_INUM(timeout);
+ }
+ if(fd < 0)
+ scm_syserror_msg("dc-select", "Not connected", SCM_EOL, ENOTCONN);
+ pfd.fd = fd;
+ pfd.events = POLLIN;
+ if(dc_wantwrite())
+ pfd.events |= POLLOUT;
+ if((ret = poll(&pfd, 1, cto)) < 0)
+ {
+ if(errno == EINTR)
+ return(SCM_BOOL_F);
+ enob = errno;
+ dc_disconnect();
+ errno = enob;
+ scm_syserror("dc-select");
+ }
+ if(((pfd.revents & POLLIN) && dc_handleread()) || ((pfd.revents & POLLOUT) && dc_handlewrite()))
+ {
+ if(errno == 0)
+ {
+ fd = -1;
+ return(SCM_BOOL_F);
+ }
+ scm_syserror("dc-select");
+ }
+ return(ret?SCM_BOOL_T:SCM_BOOL_F);
+}
+
+static SCM makerespsmob(struct dc_response *resp)
+{
+ struct respsmob *data;
+
+ data = scm_must_malloc(sizeof(*data), "respsmob");
+ data->resp = resp;
+ SCM_RETURN_NEWSMOB(resptype, data);
+}
+
+static SCM scm_dc_getresp(SCM tag)
+{
+ struct dc_response *resp;
+ SCM ret;
+
+ if(tag == SCM_UNDEFINED)
+ {
+ if((resp = dc_getresp()) == NULL)
+ return(SCM_BOOL_F);
+ } else {
+ SCM_ASSERT(SCM_INUMP(tag), tag, SCM_ARG1, "dc-getresp");
+ if((resp = dc_gettaggedresp(SCM_INUM(tag))) == NULL)
+ return(SCM_BOOL_F);
+ }
+ ret = makerespsmob(resp);
+ return(ret);
+}
+
+static SCM scm_dc_extract(SCM scm_resp)
+{
+ int i, o;
+ struct dc_response *resp;
+ SCM ret, l, w;
+
+ SCM_ASSERT(SCM_SMOB_PREDICATE(resptype, scm_resp), scm_resp, SCM_ARG1, "dc-extract");
+ resp = ((struct respsmob *)SCM_SMOB_DATA(scm_resp))->resp;
+ ret = SCM_EOL;
+ ret = scm_cons(scm_cons(scm_str2symbol("cmd"), scm_makfrom0str(icswcstombs(resp->cmdname, "UTF-8", NULL))), ret);
+ ret = scm_cons(scm_cons(scm_str2symbol("code"), SCM_MAKINUM(resp->code)), ret);
+ ret = scm_cons(scm_cons(scm_str2symbol("tag"), SCM_MAKINUM(resp->tag)), ret);
+ l = SCM_EOL;
+ for(i = resp->numlines - 1; i >= 0; i--)
+ {
+ w = SCM_EOL;
+ for(o = resp->rlines[i].argc - 1; o >= 0; o--)
+ w = scm_cons(scm_makfrom0str(icswcstombs(resp->rlines[i].argv[o], "UTF-8", NULL)), w);
+ l = scm_cons(w, l);
+ }
+ ret = scm_cons(scm_cons(scm_str2symbol("resp"), l), ret);
+ return(ret);
+}
+
+static SCM scm_dc_intresp(SCM scm_resp)
+{
+ int i;
+ struct dc_response *resp;
+ struct dc_intresp *ires;
+ SCM ret;
+
+ SCM_ASSERT(SCM_SMOB_PREDICATE(resptype, scm_resp), scm_resp, SCM_ARG1, "dc-intresp");
+ resp = ((struct respsmob *)SCM_SMOB_DATA(scm_resp))->resp;
+ if((ires = dc_interpret(resp)) == NULL)
+ return(SCM_BOOL_F);
+ ret = SCM_EOL;
+ for(i = ires->argc - 1; i >= 0; i--)
+ {
+ switch(ires->argv[i].type)
+ {
+ case 1:
+ ret = scm_cons(scm_makfrom0str(icswcstombs(ires->argv[i].val.str, "UTF-8", NULL)), ret);
+ break;
+ case 2:
+ ret = scm_cons(scm_int2num(ires->argv[i].val.num), ret);
+ break;
+ case 3:
+ ret = scm_cons(scm_double2num(ires->argv[i].val.flnum), ret);
+ break;
+ }
+ }
+ dc_freeires(ires);
+ return(ret);
+}
+
+static int qcmd_scmcb(struct dc_response *resp)
+{
+ struct scmcb *scmcb;
+
+ scmcb = resp->data;
+ scm_apply(scmcb->subr, scm_cons(makerespsmob(resp), SCM_EOL), SCM_EOL);
+ scm_gc_unprotect_object(scmcb->subr);
+ free(scmcb);
+ return(2);
+}
+
+static SCM scm_dc_qcmd(SCM argv, SCM callback)
+{
+ int tag, enob;
+ wchar_t **toks, *tok, *cmd;
+ size_t tokssize, toksdata;
+ SCM port;
+ struct scmcb *scmcb;
+
+ SCM_ASSERT(SCM_CONSP(argv), argv, SCM_ARG1, "dc-qcmd");
+ if(callback != SCM_UNDEFINED)
+ SCM_ASSERT(SCM_CLOSUREP(callback), callback, SCM_ARG2, "dc-qcmd");
+ cmd = NULL;
+ toks = NULL;
+ tokssize = toksdata = 0;
+ for(; argv != SCM_EOL; argv = SCM_CDR(argv))
+ {
+ port = scm_open_output_string();
+ scm_display(SCM_CAR(argv), port);
+ if((tok = icmbstowcs(SCM_STRING_CHARS(scm_get_output_string(port)), "UTF-8")) == NULL)
+ {
+ enob = errno;
+ addtobuf(toks, NULL);
+ dc_freewcsarr(toks);
+ if(cmd != NULL)
+ free(cmd);
+ errno = enob;
+ scm_syserror("dc-qcmd");
+ }
+ if(cmd == NULL)
+ cmd = tok;
+ else
+ addtobuf(toks, tok);
+ }
+ addtobuf(toks, NULL);
+ if(callback == SCM_UNDEFINED)
+ {
+ tag = dc_queuecmd(NULL, NULL, cmd, L"%%a", toks, NULL);
+ } else {
+ scmcb = scm_must_malloc(sizeof(*scmcb), "scmcb");
+ scm_gc_protect_object(scmcb->subr = callback);
+ tag = dc_queuecmd(qcmd_scmcb, scmcb, cmd, L"%%a", toks, NULL);
+ }
+ dc_freewcsarr(toks);
+ if(cmd != NULL)
+ free(cmd);
+ return(SCM_MAKINUM(tag));
+}
+
+static void login_scmcb(int err, wchar_t *reason, struct scmcb *scmcb)
+{
+ SCM errsym;
+
+ switch(err)
+ {
+ case DC_LOGIN_ERR_SUCCESS:
+ errsym = scm_str2symbol("success");
+ break;
+ case DC_LOGIN_ERR_NOLOGIN:
+ errsym = scm_str2symbol("nologin");
+ break;
+ case DC_LOGIN_ERR_SERVER:
+ errsym = scm_str2symbol("server");
+ break;
+ case DC_LOGIN_ERR_USER:
+ errsym = scm_str2symbol("user");
+ break;
+ case DC_LOGIN_ERR_CONV:
+ errsym = scm_str2symbol("conv");
+ break;
+ case DC_LOGIN_ERR_AUTHFAIL:
+ errsym = scm_str2symbol("authfail");
+ break;
+ }
+ scm_apply(scmcb->subr, scm_cons(errsym, scm_cons((reason == NULL)?SCM_BOOL_F:scm_makfrom0str(icswcstombs(reason, "UTF-8", NULL)), SCM_EOL)), SCM_EOL);
+ scm_gc_unprotect_object(scmcb->subr);
+ free(scmcb);
+}
+
+static SCM scm_dc_loginasync(SCM callback, SCM useauthless, SCM username)
+{
+ struct scmcb *scmcb;
+
+ SCM_ASSERT(SCM_CLOSUREP(callback), callback, SCM_ARG1, "dc-loginasync");
+ scmcb = scm_must_malloc(sizeof(*scmcb), "scmcb");
+ scm_gc_protect_object(scmcb->subr = callback);
+ dc_loginasync(SCM_STRINGP(username)?SCM_STRING_CHARS(username):NULL, SCM_NFALSEP(useauthless), NULL, (void (*)(int, wchar_t *, void *))login_scmcb, scmcb);
+ return(SCM_BOOL_T);
+}
+
+static SCM scm_dc_lexsexpr(SCM sexpr)
+{
+ SCM ret;
+ wchar_t **arr, **ap, *buf;
+
+ SCM_ASSERT(SCM_STRINGP(sexpr), sexpr, SCM_ARG1, "dc-lexsexpr");
+ if((buf = icmbstowcs(SCM_STRING_CHARS(sexpr), NULL)) == NULL)
+ scm_syserror("dc-lexsexpr");
+ arr = dc_lexsexpr(buf);
+ free(buf);
+ ret = SCM_EOL;
+ if(arr != NULL)
+ {
+ for(ap = arr; *ap != NULL; ap++)
+ ret = scm_cons(scm_makfrom0str(icswcstombs(*ap, "UTF-8", NULL)), ret);
+ dc_freewcsarr(arr);
+ }
+ return(scm_reverse(ret));
+}
+
+static size_t resp_free(SCM respsmob)
+{
+ struct respsmob *data;
+
+ data = (struct respsmob *)SCM_SMOB_DATA(respsmob);
+ dc_freeresp(data->resp);
+ free(data);
+ return(sizeof(*data));
+}
+
+static int resp_print(SCM respsmob, SCM port, scm_print_state *pstate)
+{
+ struct respsmob *data;
+
+ data = (struct respsmob *)SCM_SMOB_DATA(respsmob);
+ scm_puts("#<dc-response ", port);
+ scm_display(SCM_MAKINUM(data->resp->tag), port);
+ scm_puts(" ", port);
+ scm_puts(icswcstombs(data->resp->cmdname, "UTF-8", NULL), port);
+ scm_puts(" ", port);
+ scm_display(SCM_MAKINUM(data->resp->code), port);
+ scm_puts(">", port);
+ return(1);
+}
+
+void init_guiledc(void)
+{
+ scm_c_define_gsubr("dc-connect", 1, 1, 0, scm_dc_connect);
+ scm_c_define_gsubr("dc-disconnect", 0, 0, 0, scm_dc_disconnect);
+ scm_c_define_gsubr("dc-connected", 0, 0, 0, scm_dc_connected);
+ scm_c_define_gsubr("dc-select", 0, 1, 0, scm_dc_select);
+ scm_c_define_gsubr("dc-getresp", 0, 1, 0, scm_dc_getresp);
+ scm_c_define_gsubr("dc-extract", 1, 0, 0, scm_dc_extract);
+ scm_c_define_gsubr("dc-intresp", 1, 0, 0, scm_dc_intresp);
+ scm_c_define_gsubr("dc-qcmd", 1, 1, 0, scm_dc_qcmd);
+ scm_c_define_gsubr("dc-loginasync", 2, 1, 0, scm_dc_loginasync);
+ scm_c_define_gsubr("dc-lexsexpr", 1, 0, 0, scm_dc_lexsexpr);
+ resptype = scm_make_smob_type("dc-resp", sizeof(struct respsmob));
+ scm_set_smob_free(resptype, resp_free);
+ scm_set_smob_print(resptype, resp_print);
+ dc_init();
+}
--- /dev/null
+/Makefile.am/1.1/Sun Sep 19 03:51:39 2004//
+/ui.scm/1.8/Fri Jun 10 00:24:54 2005//
+/util.scm/1.1/Thu Jun 2 18:05:45 2005//
+D
--- /dev/null
+doldaconnect/lib/guile/dolcon
--- /dev/null
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
--- /dev/null
+moduledir=$(datadir)/guile/site/dolcon
+module_DATA=ui.scm
+
+EXTRA_DIST=ui.scm
--- /dev/null
+(define-module (dolcon ui))
+
+(export dc-connect dc-disconnect dc-connected dc-select dc-getresp dc-extract dc-intresp dc-qcmd dc-loginasync dc-lexsexpr)
+
+(load-extension "libdolcon-guile" "init_guiledc")
+
+(define-public dc-login
+ (lambda (useauthless . username)
+ (let ((done #f) (errsym #f))
+ (dc-loginasync
+ (lambda (err reason)
+ (set! errsym err)
+ (set! done #t))
+ useauthless (if (pair? username) (car username) #f))
+ (while (not done) (dc-select))
+ errsym)))
+
+(define-public dc-must-connect
+ (lambda args
+ (let* ((fd (apply dc-connect args)) (resp (dc-extract (do ((resp (dc-getresp) (dc-getresp)))
+ ((and resp
+ (equal? (cdr (assoc 'cmd (dc-extract resp))) ".connect"))
+ resp)
+ (dc-select)))))
+ (if (= (cdr (assoc 'code resp)) 200)
+ fd
+ (throw 'bad-return (cdr (assoc 'code resp)) (cadr (assoc 'resp resp)))
+ )
+ )
+ )
+ )
+
+(define-public dc-c&l
+ (lambda (verbose host useauthless)
+ (let ((fd -1) (print (lambda (obj) (if verbose (display obj (if (port? verbose) verbose (current-error-port)))))))
+ (print "connecting...\n")
+ (set! fd (dc-must-connect host))
+ (print "authenticating...\n")
+ (let ((ret (dc-login useauthless)))
+ (if (not (eq? ret 'success))
+ (throw 'login-failure ret)))
+ (print "authentication success\n")
+ fd)
+ )
+ )
+
+(define-public dc-ecmd
+ (lambda args
+ (let ((tag (dc-qcmd args)))
+ (do ((resp (dc-getresp tag) (dc-getresp tag)))
+ (resp resp)
+ (dc-select))
+ )
+ )
+ )
+
+(define-public dc-ecmd-assert
+ (lambda (code . args)
+ (let* ((resp (apply dc-ecmd args)) (eresp (dc-extract resp)))
+ (if (not (if (list? code)
+ (memq (cdr (assoc 'code eresp)) code)
+ (= (cdr (assoc 'code eresp)) code)))
+ (throw 'bad-return (cdr (assoc 'code eresp)) (cadr (assoc 'resp eresp)))
+ )
+ resp
+ )
+ )
+ )
+
+(define-public dc-intall
+ (lambda (resp)
+ (let ((retlist '()))
+ (do ((ires (dc-intresp resp) (dc-intresp resp))) ((not ires) retlist)
+ (set! retlist (append retlist (list ires)))))))
--- /dev/null
+(define-module (dolcon util))
+(use-modules (dolcon ui))
+
+(define fnetnodes '())
+
+(define-public dc-fn-update
+ (lambda ()
+ (set! fnetnodes
+ (let ((resp (dc-ecmd "lsnodes")) (er #f))
+ (if (and resp (begin (set! er (dc-extract resp)) er) (= (cdr (assq 'code er)) 200))
+ (map (lambda (o)
+ (apply (lambda (id net name users state)
+ (cons id
+ (list (cons 'id id)
+ (cons 'net net)
+ (cons 'name name)
+ (cons 'users users)
+ (cons 'state (list-ref '(syn hs est dead) state)))))
+ o))
+ (dc-intall resp))
+ '())))
+ fnetnodes))
+
+(define-public dc-fn-getattrib
+ (lambda (id attrib)
+ (if (not (assq id fnetnodes))
+ (dc-fn-update))
+ (let ((aform (assq id fnetnodes)))
+ (if aform
+ (cdr (assq attrib (cdr aform)))
+ #f))))
+
+(define-public dc-fn-getname
+ (lambda (id)
+ (dc-fn-getattrib id 'name)))
+
+(define-public dc-getfnetnodes
+ (lambda ()
+ (map (lambda (o) (car o))
+ fnetnodes)))
--- /dev/null
+
+/* Do not modify this file - it is autogenerated by makecmds */
+
+static void initcmds(void)
+{
+ struct command *cmd;
+
+ cmd = makecmd(L"lssr");
+ addresp(cmd, 200, RESP_STR, RESP_STR, RESP_STR, RESP_INT, RESP_INT, RESP_INT, RESP_FLOAT, RESP_END);
+ addresp(cmd, 201, RESP_END);
+ addresp(cmd, 514, RESP_END);
+ cmd = makecmd(L"lssrch");
+ addresp(cmd, 200, RESP_INT, RESP_INT, RESP_INT, RESP_INT, RESP_END);
+ addresp(cmd, 201, RESP_END);
+ cmd = makecmd(L"shutdown");
+ addresp(cmd, 200, RESP_END);
+ addresp(cmd, 502, RESP_END);
+ cmd = makecmd(L"quit");
+ addresp(cmd, 200, RESP_END);
+ cmd = makecmd(L"lsauth");
+ addresp(cmd, 200, RESP_STR, RESP_END);
+ addresp(cmd, 201, RESP_END);
+ cmd = makecmd(L"login");
+ addresp(cmd, 200, RESP_END); /* Success */
+ addresp(cmd, 300, RESP_STR, RESP_END); /* Auto (think GSS-API) */
+ addresp(cmd, 301, RESP_STR, RESP_END); /* No echo */
+ addresp(cmd, 302, RESP_STR, RESP_END); /* Echo */
+ addresp(cmd, 303, RESP_STR, RESP_END); /* Info message */
+ addresp(cmd, 304, RESP_STR, RESP_END); /* Error message */
+ addresp(cmd, 501, RESP_END);
+ addresp(cmd, 503, RESP_END); /* Already logging in */
+ addresp(cmd, 504, RESP_END); /* Charset error */
+ addresp(cmd, 505, RESP_DSC, RESP_STR, RESP_END); /* Back-end error */
+ addresp(cmd, 506, RESP_END); /* Authentication error */
+ addresp(cmd, 508, RESP_END); /* No such authentication mechanism */
+ cmd = makecmd(L"pass");
+ addresp(cmd, 200, RESP_END);
+ addresp(cmd, 300, RESP_STR, RESP_END);
+ addresp(cmd, 301, RESP_STR, RESP_END);
+ addresp(cmd, 302, RESP_STR, RESP_END);
+ addresp(cmd, 303, RESP_STR, RESP_END);
+ addresp(cmd, 304, RESP_STR, RESP_END);
+ addresp(cmd, 504, RESP_END);
+ addresp(cmd, 505, RESP_DSC, RESP_STR, RESP_END);
+ addresp(cmd, 506, RESP_END);
+ addresp(cmd, 507, RESP_END); /* Data not expected */
+ cmd = makecmd(L"cnct");
+ addresp(cmd, 200, RESP_END);
+ addresp(cmd, 502, RESP_END);
+ addresp(cmd, 504, RESP_END);
+ addresp(cmd, 509, RESP_END);
+ addresp(cmd, 511, RESP_END);
+ cmd = makecmd(L"lsnodes");
+ addresp(cmd, 200, RESP_INT, RESP_STR, RESP_STR, RESP_INT, RESP_INT, RESP_END);
+ addresp(cmd, 201, RESP_END);
+ addresp(cmd, 502, RESP_END);
+ cmd = makecmd(L"dcnct");
+ addresp(cmd, 200, RESP_END);
+ addresp(cmd, 502, RESP_END);
+ addresp(cmd, 510, RESP_END);
+ cmd = makecmd(L"lspeers");
+ addresp(cmd, 200, RESP_STR, RESP_STR, RESP_END); /* Peer ID and nick are standardized, so they can be here -- the rest have to be extracted manually */
+ addresp(cmd, 201, RESP_END);
+ addresp(cmd, 510, RESP_END);
+ cmd = makecmd(L"lspa");
+ addresp(cmd, 200, RESP_STR, RESP_INT, RESP_END);
+ addresp(cmd, 201, RESP_END);
+ addresp(cmd, 502, RESP_END);
+ addresp(cmd, 510, RESP_END);
+ cmd = makecmd(L"download");
+ addresp(cmd, 200, RESP_INT, RESP_END);
+ addresp(cmd, 502, RESP_END);
+ addresp(cmd, 510, RESP_END);
+ addresp(cmd, 511, RESP_END);
+ cmd = makecmd(L"lstrans");
+ addresp(cmd, 200, RESP_INT, RESP_INT, RESP_INT, RESP_STR, RESP_STR, RESP_STR, RESP_INT, RESP_INT, RESP_END);
+ addresp(cmd, 201, RESP_END);
+ addresp(cmd, 502, RESP_END);
+ cmd = makecmd(L"cancel");
+ addresp(cmd, 200, RESP_END);
+ addresp(cmd, 502, RESP_END);
+ addresp(cmd, 512, RESP_END);
+ cmd = makecmd(L"notify");
+ addresp(cmd, 200, RESP_END);
+ addresp(cmd, 501, RESP_END);
+ cmd = makecmd(L"sendchat");
+ addresp(cmd, 200, RESP_END);
+ addresp(cmd, 501, RESP_END);
+ addresp(cmd, 502, RESP_END);
+ addresp(cmd, 504, RESP_END);
+ addresp(cmd, 505, RESP_END);
+ addresp(cmd, 509, RESP_END);
+ addresp(cmd, 510, RESP_END);
+ addresp(cmd, 513, RESP_END);
+ cmd = makecmd(L"search");
+ addresp(cmd, 200, RESP_INT, RESP_INT, RESP_END);
+ addresp(cmd, 501, RESP_END);
+ addresp(cmd, 502, RESP_END);
+ addresp(cmd, 509, RESP_END);
+ addresp(cmd, 510, RESP_END);
+ cmd = makecmd(L"cansrch");
+ addresp(cmd, 200, RESP_END);
+ addresp(cmd, 514, RESP_END);
+ cmd = makecmd(L"filtercmd");
+ addresp(cmd, 200, RESP_STR, RESP_END);
+ addresp(cmd, 201, RESP_END);
+ addresp(cmd, 501, RESP_END);
+ addresp(cmd, 502, RESP_END);
+ addresp(cmd, 504, RESP_END);
+ addresp(cmd, 505, RESP_END);
+ cmd = makecmd(L"lstrarg");
+ addresp(cmd, 200, RESP_STR, RESP_STR, RESP_END);
+ addresp(cmd, 201, RESP_END);
+ addresp(cmd, 501, RESP_END);
+ addresp(cmd, 502, RESP_END);
+ addresp(cmd, 512, RESP_END);
+ cmd = makecmd(NULL); /* Nameless notification */
+ addresp(cmd, 600, RESP_INT, RESP_INT, RESP_STR, RESP_STR, RESP_STR, RESP_END); /* FN chat */
+ addresp(cmd, 601, RESP_INT, RESP_INT, RESP_END); /* FN state change */
+ addresp(cmd, 602, RESP_INT, RESP_STR, RESP_END); /* FN name change */
+ addresp(cmd, 603, RESP_INT, RESP_END); /* FN destroy */
+ addresp(cmd, 604, RESP_INT, RESP_STR, RESP_END); /* FN create */
+ addresp(cmd, 605, RESP_INT, RESP_INT, RESP_END); /* FN num peers change */
+ addresp(cmd, 610, RESP_INT, RESP_INT, RESP_STR, RESP_STR, RESP_END); /* Transfer create */
+ addresp(cmd, 611, RESP_INT, RESP_INT, RESP_END); /* Transfer state change */
+ addresp(cmd, 612, RESP_INT, RESP_STR, RESP_END); /* Transfer nick change */
+ addresp(cmd, 613, RESP_INT, RESP_INT, RESP_END); /* Transfer size change */
+ addresp(cmd, 614, RESP_INT, RESP_INT, RESP_END); /* Transfer error update */
+ addresp(cmd, 615, RESP_INT, RESP_INT, RESP_END); /* Transfer progress */
+ addresp(cmd, 616, RESP_INT, RESP_STR, RESP_END); /* Transfer path change */
+ addresp(cmd, 617, RESP_INT, RESP_END); /* Transfer destroy */
+ addresp(cmd, 620, RESP_INT, RESP_INT, RESP_END);
+ addresp(cmd, 621, RESP_INT, RESP_END);
+ addresp(cmd, 622, RESP_INT, RESP_STR, RESP_STR, RESP_STR, RESP_INT, RESP_INT, RESP_INT, RESP_FLOAT, RESP_END);
+ cmd = makecmd(NULL); /* Nameless connection */
+ addresp(cmd, 200, RESP_STR, RESP_END);
+ addresp(cmd, 502, RESP_END);
+}
--- /dev/null
+#!/usr/bin/perl
+
+%keys =
+ (
+ "s" => "RESP_STR",
+ "d" => "RESP_DSC",
+ "i" => "RESP_INT",
+ "f" => "RESP_FLOAT"
+ );
+
+print("
+/* Do not modify this file - it is autogenerated by makecmds */
+
+static void initcmds(void)
+{
+ struct command *cmd;
+
+");
+
+while(<>)
+{
+ ($comment) = /\s*;\s*(.*)$/;
+ s/\s*;.*$//;
+ if(($name) = /^:(\w*)/)
+ {
+ if($name)
+ {
+ print(" cmd = makecmd(L\"$name\");");
+ } else {
+ print(" cmd = makecmd(NULL);");
+ }
+ if($comment)
+ {
+ print("\t/* $comment */");
+ }
+ print("\n");
+ } elsif(/^([0-9]{3})\s+/g) {
+ print(" addresp(cmd, $1");
+ print(", " . $keys{$1}) while /\G(\S+)\s+/g;
+ print(", RESP_END);");
+ if($comment)
+ {
+ print("\t/* $comment */");
+ }
+ print("\n");
+ }
+}
+
+print("}\n");
--- /dev/null
+:lssr
+200 s s s i i i f
+201
+514
+:lssrch
+200 i i i i
+201
+:shutdown
+200
+502
+:quit
+200
+:lsauth
+200 s
+201
+:login
+200 ; Success
+300 s ; Auto (think GSS-API)
+301 s ; No echo
+302 s ; Echo
+303 s ; Info message
+304 s ; Error message
+501
+503 ; Already logging in
+504 ; Charset error
+505 d s ; Back-end error
+506 ; Authentication error
+508 ; No such authentication mechanism
+:pass
+200
+300 s
+301 s
+302 s
+303 s
+304 s
+504
+505 d s
+506
+507 ; Data not expected
+:cnct
+200
+502
+504
+509
+511
+:lsnodes
+200 i s s i i
+201
+502
+:dcnct
+200
+502
+510
+:lspeers
+200 s s ; Peer ID and nick are standardized, so they can be here -- the rest have to be extracted manually
+201
+510
+:lspa
+200 s i
+201
+502
+510
+:download
+200 i
+502
+510
+511
+:lstrans
+200 i i i s s s i i
+201
+502
+:cancel
+200
+502
+512
+:notify
+200
+501
+:sendchat
+200
+501
+502
+504
+505
+509
+510
+513
+:search
+200 i i
+501
+502
+509
+510
+:cansrch
+200
+514
+:filtercmd
+200 s
+201
+501
+502
+504
+505
+:lstrarg
+200 s s
+201
+501
+502
+512
+: ; Nameless notification
+600 i i s s s ; FN chat
+601 i i ; FN state change
+602 i s ; FN name change
+603 i ; FN destroy
+604 i s ; FN create
+605 i i ; FN num peers change
+610 i i s s ; Transfer create
+611 i i ; Transfer state change
+612 i s ; Transfer nick change
+613 i i ; Transfer size change
+614 i i ; Transfer error update
+615 i i ; Transfer progress
+616 i s ; Transfer path change
+617 i ; Transfer destroy
+620 i i
+621 i
+622 i s s s i i i f
+: ; Nameless connection
+200 s
+502
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+/*
+ * Note: This code is still ugly, since I copied it almost verbatim
+ * from the daemon's parser. It would need serious cleanups, but at
+ * least it works for now.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <iconv.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <sys/poll.h>
+#ifdef HAVE_RESOLVER
+#include <arpa/nameser.h>
+#include <resolv.h>
+#endif
+
+#include <doldaconnect/uilib.h>
+#include <doldaconnect/utils.h>
+
+#define RESP_END -1
+#define RESP_DSC 0
+#define RESP_STR 1
+#define RESP_INT 2
+#define RESP_FLOAT 3
+
+struct respclass
+{
+ struct respclass *next;
+ int code;
+ int nwords;
+ int *wordt;
+};
+
+struct command
+{
+ struct command *next;
+ wchar_t *name;
+ struct respclass *classes;
+};
+
+struct qcmd
+{
+ struct qcmd *next;
+ struct command *cmd;
+ int tag;
+ char *buf;
+ size_t buflen;
+ int (*callback)(struct dc_response *resp);
+ void *data;
+};
+
+void dc_uimisc_disconnected(void);
+
+/* The first command must be the nameless connect command and second
+ * the notification command. */
+static struct command *commands = NULL;
+static struct qcmd *queue = NULL, *queueend = NULL;
+static struct dc_response *respqueue = NULL, *respqueueend = NULL;
+static int state = -1;
+static int fd = -1;
+static iconv_t ichandle;
+static int resetreader = 1;
+static char *dchostname = NULL;
+static struct addrinfo *hostlist = NULL, *curhost = NULL;
+static int servport;
+
+static struct dc_response *makeresp(void)
+{
+ struct dc_response *new;
+
+ new = smalloc(sizeof(*new));
+ new->next = NULL;
+ new->prev = NULL;
+ new->code = 0;
+ new->cmdname = NULL;
+ new->rlines = NULL;
+ new->linessize = 0;
+ new->numlines = 0;
+ new->curline = 0;
+ new->data = NULL;
+ return(new);
+}
+
+static void freeqcmd(struct qcmd *qcmd)
+{
+ if(qcmd->buf != NULL)
+ free(qcmd->buf);
+ free(qcmd);
+}
+
+static void unlinkqueue(void)
+{
+ struct qcmd *qcmd;
+
+ if((qcmd = queue) == NULL)
+ return;
+ queue = qcmd->next;
+ if(queue == NULL)
+ queueend = NULL;
+ freeqcmd(qcmd);
+}
+
+static struct qcmd *makeqcmd(wchar_t *name)
+{
+ struct qcmd *new;
+ struct command *cmd;
+ static int tag = 0;
+
+ if(name == NULL)
+ {
+ cmd = commands;
+ } else {
+ for(cmd = commands; cmd != NULL; cmd = cmd->next)
+ {
+ if((cmd->name != NULL) && !wcscmp(cmd->name, name))
+ break;
+ }
+ if(cmd == NULL)
+ return(NULL);
+ }
+ new = smalloc(sizeof(*new));
+ new->tag = tag++;
+ new->cmd = cmd;
+ new->buf = NULL;
+ new->buflen = 0;
+ new->callback = NULL;
+ new->next = NULL;
+ new->data = NULL;
+ if(queueend == NULL)
+ {
+ queue = queueend = new;
+ } else {
+ queueend->next = new;
+ queueend = new;
+ }
+ return(new);
+}
+
+static wchar_t *quoteword(wchar_t *word)
+{
+ wchar_t *wp, *buf, *bp;
+ int dq, numbs, numc;
+
+ dq = 0;
+ numbs = 0;
+ numc = 0;
+ if(*word == L'\0')
+ {
+ dq = 1;
+ } else {
+ for(wp = word; *wp != L'\0'; wp++)
+ {
+ if(!dq && iswspace(*wp))
+ dq = 1;
+ if((*wp == L'\\') || (*wp == L'\"'))
+ numbs++;
+ numc++;
+ }
+ }
+ if(!dq && !numbs)
+ return(NULL);
+ bp = buf = smalloc(sizeof(wchar_t) * (numc + numbs + (dq?2:0) + 1));
+ if(dq)
+ *(bp++) = L'\"';
+ for(wp = word; *wp != L'\0'; wp++)
+ {
+ if((*wp == L'\\') || (*wp == L'\"'))
+ *(bp++) = L'\\';
+ *(bp++) = *wp;
+ }
+ if(dq)
+ *(bp++) = L'\"';
+ *(bp++) = L'\0';
+ return(buf);
+}
+
+static struct command *makecmd(wchar_t *name)
+{
+ struct command *new;
+
+ new = smalloc(sizeof(*new));
+ new->name = name;
+ new->classes = NULL;
+ new->next = commands;
+ commands = new;
+ return(new);
+}
+
+static struct respclass *addresp(struct command *cmd, int code, ...)
+{
+ struct respclass *new;
+ int i;
+ int resps[128];
+ va_list args;
+
+ va_start(args, code);
+ i = 0;
+ while((resps[i++] = va_arg(args, int)) != RESP_END);
+ i--;
+ va_end(args);
+ new = smalloc(sizeof(*new));
+ new->code = code;
+ new->nwords = i;
+ if(i > 0)
+ {
+ new->wordt = smalloc(sizeof(int) * i);
+ memcpy(new->wordt, resps, sizeof(int) * i);
+ } else {
+ new->wordt = NULL;
+ }
+ new->next = cmd->classes;
+ cmd->classes = new;
+ return(new);
+}
+
+#include "initcmds.h"
+
+int dc_init(void)
+{
+ if((ichandle = iconv_open("wchar_t", "utf-8")) == (iconv_t)-1)
+ return(-1);
+ initcmds();
+ return(0);
+}
+
+void dc_cleanup(void)
+{
+ iconv_close(ichandle);
+}
+
+void dc_disconnect(void)
+{
+ struct dc_response *resp;
+
+ state = -1;
+ if(fd >= 0)
+ close(fd);
+ fd = -1;
+ while(queue != NULL)
+ unlinkqueue();
+ while((resp = dc_getresp()) != NULL)
+ dc_freeresp(resp);
+ dc_uimisc_disconnected();
+ if(dchostname != NULL)
+ free(dchostname);
+ dchostname = NULL;
+}
+
+void dc_freeresp(struct dc_response *resp)
+{
+ int i, o;
+
+ for(i = 0; i < resp->numlines; i++)
+ {
+ for(o = 0; o < resp->rlines[i].argc; o++)
+ free(resp->rlines[i].argv[o]);
+ free(resp->rlines[i].argv);
+ }
+ free(resp->rlines);
+ free(resp);
+}
+
+struct dc_response *dc_getresp(void)
+{
+ struct dc_response *ret;
+
+ if((ret = respqueue) == NULL)
+ return(NULL);
+ respqueue = ret->next;
+ if(respqueue == NULL)
+ respqueueend = NULL;
+ else
+ respqueue->prev = NULL;
+ return(ret);
+}
+
+struct dc_response *dc_gettaggedresp(int tag)
+{
+ struct dc_response *resp;
+
+ for(resp = respqueue; resp != NULL; resp = resp->next)
+ {
+ if(resp->tag == tag)
+ {
+ if(resp->prev != NULL)
+ resp->prev->next = resp->next;
+ if(resp->next != NULL)
+ resp->next->prev = resp->prev;
+ if(resp == respqueue)
+ respqueue = resp->next;
+ if(resp == respqueueend)
+ respqueueend = resp->prev;
+ return(resp);
+ }
+ }
+ return(NULL);
+}
+
+struct dc_response *dc_gettaggedrespsync(int tag)
+{
+ struct pollfd pfd;
+ struct dc_response *resp;
+
+ while((resp = dc_gettaggedresp(tag)) == NULL)
+ {
+ pfd.fd = fd;
+ pfd.events = POLLIN;
+ if(dc_wantwrite())
+ pfd.events |= POLLOUT;
+ if(poll(&pfd, 1, -1) < 0)
+ return(NULL);
+ if((pfd.revents & POLLIN) && dc_handleread())
+ return(NULL);
+ if((pfd.revents & POLLOUT) && dc_handlewrite())
+ return(NULL);
+ }
+ return(resp);
+}
+
+int dc_wantwrite(void)
+{
+ switch(state)
+ {
+ case 1:
+ if((queue != NULL) && (queue->buflen > 0))
+ return(1);
+ break;
+ }
+ return(0);
+}
+
+int dc_getstate(void)
+{
+ return(state);
+}
+
+int dc_queuecmd(int (*callback)(struct dc_response *), void *data, ...)
+{
+ struct qcmd *qcmd;
+ int num, freepart;
+ va_list al;
+ char *final;
+ wchar_t **toks;
+ wchar_t *buf;
+ wchar_t *part, *tpart;
+ size_t bufsize, bufdata;
+
+ buf = NULL;
+ bufsize = bufdata = 0;
+ num = 0;
+ va_start(al, data);
+ while((part = va_arg(al, wchar_t *)) != NULL)
+ {
+ if(!wcscmp(part, L"%%a"))
+ {
+ for(toks = va_arg(al, wchar_t **); *toks != NULL; toks++)
+ {
+ part = *toks;
+ freepart = 0;
+ if((tpart = quoteword(part)) != NULL)
+ {
+ freepart = 1;
+ part = tpart;
+ }
+ addtobuf(buf, L' ');
+ bufcat(buf, part, wcslen(part));
+ num++;
+ if(freepart)
+ free(part);
+ }
+ } else {
+ if(*part == L'%')
+ {
+ /* This demands that all arguments that are passed to the
+ * function are of equal length, that of an int. I know
+ * that GCC does that on IA32 platforms, but I do not know
+ * which other platforms and compilers that it applies
+ * to. If this breaks your platform, please mail me about
+ * it.
+ */
+ part = vswprintf2(tpart = (part + 1), al);
+ for(; *tpart != L'\0'; tpart++)
+ {
+ if(*tpart == L'%')
+ {
+ if(tpart[1] == L'%')
+ tpart++;
+ else
+ va_arg(al, int);
+ }
+ }
+ freepart = 1;
+ } else {
+ freepart = 0;
+ }
+ if((tpart = quoteword(part)) != NULL)
+ {
+ if(freepart)
+ free(part);
+ part = tpart;
+ freepart = 1;
+ }
+ if(num > 0)
+ addtobuf(buf, L' ');
+ if(num == 0)
+ {
+ if((qcmd = makeqcmd(part)) == NULL)
+ {
+ if(freepart)
+ free(part);
+ if(buf != NULL)
+ free(buf);
+ return(-1);
+ } else {
+ qcmd->callback = callback;
+ qcmd->data = data;
+ }
+ }
+ bufcat(buf, part, wcslen(part));
+ num++;
+ if(freepart)
+ free(part);
+ }
+ }
+ bufcat(buf, L"\r\n\0", 3);
+ if((final = icwcstombs(buf, "utf-8")) == NULL)
+ {
+ free(buf);
+ return(-1);
+ }
+ va_end(al);
+ free(buf);
+ qcmd->buf = final;
+ qcmd->buflen = strlen(final);
+ return(qcmd->tag);
+}
+
+int dc_handleread(void)
+{
+ int ret, done;
+ char *p1, *p2;
+ size_t len;
+ int errnobak;
+ /* Ewww... this really is soo ugly. I need to clean this up some day. */
+ static int pstate = 0;
+ static char inbuf[128];
+ static size_t inbufdata = 0;
+ static wchar_t *cbuf = NULL;
+ static size_t cbufsize = 0, cbufdata = 0;
+ static wchar_t *pptr = NULL;
+ static wchar_t **argv = NULL;
+ static int argc = 0, args = 0;
+ static wchar_t *cw = NULL;
+ static size_t cwsize = 0, cwdata = 0;
+ static struct dc_response *curresp = NULL;
+ static int cont = 0;
+ static int unlink = 0;
+
+ switch(state)
+ {
+ case -1:
+ return(-1);
+ case 0:
+ len = sizeof(ret);
+ getsockopt(fd, SOL_SOCKET, SO_ERROR, &ret, &len);
+ if(ret)
+ {
+ int newfd;
+ struct sockaddr_storage addr;
+ struct sockaddr_in *ipv4;
+ struct sockaddr_in6 *ipv6;
+
+ for(curhost = curhost->ai_next; curhost != NULL; curhost = curhost->ai_next)
+ {
+ if((newfd = socket(curhost->ai_family, curhost->ai_socktype, curhost->ai_protocol)) < 0)
+ {
+ errnobak = errno;
+ dc_disconnect();
+ errno = errnobak;
+ return(-1);
+ }
+ dup2(newfd, fd);
+ close(newfd);
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
+ memcpy(&addr, curhost->ai_addr, curhost->ai_addrlen);
+ if(addr.ss_family == AF_INET)
+ {
+ ipv4 = (struct sockaddr_in *)&addr;
+ ipv4->sin_port = htons(servport);
+ }
+#ifdef HAVE_IPV6
+ if(addr.ss_family == AF_INET6)
+ {
+ ipv6 = (struct sockaddr_in6 *)&addr;
+ ipv6->sin6_port = htons(servport);
+ }
+#endif
+ if(connect(fd, (struct sockaddr *)&addr, curhost->ai_addrlen))
+ {
+ if(errno == EINPROGRESS)
+ return(0);
+ } else {
+ break;
+ }
+ }
+ if(curhost == NULL)
+ {
+ dc_disconnect();
+ errno = ret;
+ return(-1);
+ }
+ }
+ state = 1;
+ resetreader = 1;
+ break;
+ case 1:
+ if(resetreader)
+ {
+ inbufdata = 0;
+ cbufdata = 0;
+ pptr = NULL;
+ pstate = 0;
+ resetreader = 0;
+ cont = 0;
+ if(curresp != NULL)
+ dc_freeresp(curresp);
+ curresp = NULL;
+ }
+ if(inbufdata == 128)
+ inbufdata = 0;
+ ret = read(fd, inbuf + inbufdata, 128 - inbufdata);
+ if(ret < 0)
+ {
+ if((errno == EAGAIN) || (errno == EINTR))
+ return(0);
+ errnobak = errno;
+ dc_disconnect();
+ errno = errnobak;
+ return(-1);
+ } else if(ret == 0) {
+ dc_disconnect();
+ errno = 0;
+ return(-1);
+ }
+ inbufdata += ret;
+ done = 0;
+ while(!done)
+ {
+ if(cbufsize == cbufdata)
+ {
+ if(pptr != NULL)
+ len = pptr - cbuf;
+ if((cbuf = realloc(cbuf, sizeof(wchar_t) * (cbufsize += 256))) == NULL)
+ {
+ dc_disconnect();
+ errno = ENOMEM;
+ return(-1);
+ }
+ if(pptr != NULL)
+ pptr = cbuf + len;
+ }
+ p1 = inbuf;
+ p2 = (char *)(cbuf + cbufdata);
+ len = sizeof(wchar_t) * (cbufsize - cbufdata);
+ ret = iconv(ichandle, &p1, &inbufdata, &p2, &len);
+ memmove(inbuf, p1, inbufdata);
+ cbufdata = cbufsize - (len / sizeof(wchar_t));
+ if(ret < 0)
+ {
+ switch(errno)
+ {
+ case EILSEQ:
+ /* XXX Is this really OK? */
+ inbufdata = 0;
+ done = 1;
+ break;
+ case EINVAL:
+ done = 1;
+ break;
+ case E2BIG:
+ break;
+ default:
+ errnobak = errno;
+ dc_disconnect();
+ errno = errnobak;
+ return(-1);
+ }
+ } else {
+ done = 1;
+ }
+ }
+ if(pptr == NULL)
+ pptr = cbuf;
+ done = 0;
+ while(!done && (pptr - cbuf < cbufdata))
+ {
+ switch(pstate)
+ {
+ case 0:
+ if(iswspace(*pptr))
+ {
+ if(*pptr == L'\r')
+ {
+ if(pptr == cbuf + cbufdata - 1)
+ {
+ done = 1;
+ break;
+ }
+ if(*(++pptr) == L'\n')
+ {
+ if(curresp == NULL)
+ {
+ curresp = makeresp();
+ if((argc > 0) && ((curresp->code = wcstol(argv[0], NULL, 10)) >= 600))
+ {
+ curresp->cmdname = L".notify";
+ curresp->internal = commands->next;
+ curresp->tag = -1;
+ unlink = 0;
+ } else {
+ if((curresp->cmdname = queue->cmd->name) == NULL)
+ curresp->cmdname = L".connect";
+ curresp->data = queue->data;
+ curresp->tag = queue->tag;
+ curresp->internal = (void *)(queue->cmd);
+ unlink = 1;
+ }
+ }
+ sizebuf(&curresp->rlines, &curresp->linessize, curresp->numlines + 1, sizeof(*(curresp->rlines)), 1);
+ curresp->rlines[curresp->numlines].argc = argc;
+ curresp->rlines[curresp->numlines].argv = argv;
+ argv = NULL;
+ argc = args = 0;
+ curresp->numlines++;
+ if(!cont)
+ {
+ if((curresp->code >= 600) || (queue == NULL) || (queue->callback == NULL))
+ ret = 0;
+ else
+ ret = queue->callback(curresp);
+ if(ret == 0)
+ {
+ if(respqueue == NULL)
+ {
+ respqueue = respqueueend = curresp;
+ } else {
+ curresp->next = NULL;
+ curresp->prev = respqueueend;
+ respqueueend->next = curresp;
+ respqueueend = curresp;
+ }
+ } else if(ret == 1) {
+ dc_freeresp(curresp);
+ }
+ curresp = NULL;
+ if(unlink)
+ unlinkqueue();
+ }
+ wmemmove(cbuf, pptr, cbufdata -= (pptr - cbuf));
+ pptr = cbuf;
+ } else {
+ pptr++;
+ }
+ } else {
+ pptr++;
+ }
+ } else {
+ pstate = 1;
+ cwdata = 0;
+ }
+ break;
+ case 1:
+ if(iswspace(*pptr) || ((argc == 0) && (*pptr == L'-')))
+ {
+ if(argc == 0)
+ {
+ if(*pptr == L'-')
+ {
+ cont = 1;
+ pptr++;
+ } else {
+ cont = 0;
+ }
+ }
+ addtobuf(cw, L'\0');
+ sizebuf(&argv, &args, argc + 1, sizeof(*argv), 1);
+ argv[argc++] = cw;
+ cw = NULL;
+ cwsize = cwdata = 0;
+ pstate = 0;
+ } else if(*pptr == L'\"') {
+ pstate = 2;
+ pptr++;
+ } else if(*pptr == L'\\') {
+ if(pptr == cbuf + cbufdata - 1)
+ {
+ done = 1;
+ break;
+ }
+ addtobuf(cw, *(++pptr));
+ pptr++;
+ } else {
+ addtobuf(cw, *(pptr++));
+ }
+ break;
+ case 2:
+ if(*pptr == L'\"')
+ {
+ pstate = 1;
+ } else if(*pptr == L'\\') {
+ addtobuf(cw, *(++pptr));
+ } else {
+ addtobuf(cw, *pptr);
+ }
+ pptr++;
+ break;
+ }
+ }
+ break;
+ }
+ return(0);
+}
+
+int dc_handlewrite(void)
+{
+ int ret;
+ int errnobak;
+
+ switch(state)
+ {
+ case 1:
+ if(queue->buflen > 0)
+ {
+ ret = send(fd, queue->buf, queue->buflen, MSG_NOSIGNAL | MSG_DONTWAIT);
+ if(ret < 0)
+ {
+ if((errno == EAGAIN) || (errno == EINTR))
+ return(0);
+ errnobak = errno;
+ dc_disconnect();
+ errno = errnobak;
+ return(-1);
+ }
+ if(ret > 0)
+ memmove(queue->buf, queue->buf + ret, queue->buflen -= ret);
+ }
+ break;
+ }
+ return(0);
+}
+
+#ifdef HAVE_RESOLVER
+static char *readname(unsigned char *msg, unsigned char *eom, unsigned char **p)
+{
+ char *name, *tname;
+ unsigned char *tp;
+ size_t namesize, namedata, len;
+
+ name = NULL;
+ namesize = namedata = 0;
+ while(1)
+ {
+ len = *((*p)++);
+ if(len == 0)
+ {
+ addtobuf(name, 0);
+ return(name);
+ } else if(len == 0xc0) {
+ tp = msg + *((*p)++);
+ if((tname = readname(msg, eom, &tp)) == NULL)
+ {
+ if(name != NULL)
+ free(name);
+ return(NULL);
+ }
+ bufcat(name, tname, strlen(tname));
+ addtobuf(name, 0);
+ free(tname);
+ return(name);
+ } else if(*p + len >= eom) {
+ if(name != NULL)
+ free(name);
+ return(NULL);
+ }
+ bufcat(name, *p, len);
+ *p += len;
+ addtobuf(name, '.');
+ }
+}
+
+static int skipname(unsigned char *msg, unsigned char *eom, unsigned char **p)
+{
+ size_t len;
+
+ while(1)
+ {
+ len = *((*p)++);
+ if(len == 0)
+ {
+ return(0);
+ } else if(len == 0xc0) {
+ (*p)++;
+ return(0);
+ } else if(*p + len >= eom) {
+ return(-1);
+ }
+ *p += len;
+ }
+}
+
+static int getsrvrr(char *name, char **host, int *port)
+{
+ int i;
+ char *name2, *rrname;
+ unsigned char *eom, *p;
+ unsigned char buf[1024];
+ int flags, num, class, type;
+ size_t len;
+ int ret;
+
+ if(!(_res.options & RES_INIT))
+ {
+ if(res_init() < 0)
+ return(-1);
+ }
+ /* res_querydomain doesn't work for some reason */
+ name2 = smalloc(strlen("_dolcon._tcp.") + strlen(name) + 2);
+ strcpy(name2, "_dolcon._tcp.");
+ strcat(name2, name);
+ len = strlen(name2);
+ if(name2[len - 1] != '.')
+ {
+ name2[len] = '.';
+ name2[len + 1] = 0;
+ }
+ ret = res_query(name2, C_IN, T_SRV, buf, sizeof(buf));
+ if(ret < 0)
+ {
+ free(name2);
+ return(-1);
+ }
+ eom = buf + ret;
+ flags = (buf[2] << 8) + buf[3];
+ if((flags & 0xfa0f) != 0x8000)
+ {
+ free(name2);
+ return(-1);
+ }
+ num = (buf[4] << 8) + buf[5];
+ p = buf + 12;
+ for(i = 0; i < num; i++)
+ {
+ if(skipname(buf, eom, &p))
+ {
+ free(name2);
+ return(-1);
+ }
+ p += 4;
+ }
+ num = (buf[6] << 8) + buf[7];
+ for(i = 0; i < num; i++)
+ {
+ if((rrname = readname(buf, eom, &p)) == NULL)
+ {
+ free(name2);
+ return(-1);
+ }
+ type = *(p++) << 8;
+ type += *(p++);
+ class = *(p++) << 8;
+ class += *(p++);
+ p += 4;
+ len = *(p++) << 8;
+ len += *(p++);
+ if((class == C_IN) && (type == T_SRV) && !strcmp(rrname, name2))
+ {
+ free(rrname);
+ free(name2);
+ /* Noone will want to have alternative DC servers, so
+ * don't care about priority and weigth */
+ p += 4;
+ if(port == NULL)
+ {
+ p += 2;
+ } else {
+ *port = *(p++) << 8;
+ *port += *(p++);
+ }
+ if(host != NULL)
+ {
+ if((rrname = readname(buf, eom, &p)) == NULL)
+ return(-1);
+ *host = rrname;
+ }
+ return(0);
+ }
+ p += len;
+ free(rrname);
+ }
+ free(name2);
+ return(-1);
+}
+#else
+static int getsrvrr(char *name, char **host, int *port)
+{
+ errno = EOPNOTSUP;
+ return(-1);
+}
+#endif
+
+int dc_connect(char *host, int port)
+{
+ struct addrinfo hint;
+ struct sockaddr_storage addr;
+ struct sockaddr_in *ipv4;
+#ifdef HAVE_IPV6
+ struct sockaddr_in6 *ipv6;
+#endif
+ struct qcmd *qcmd;
+ char *newhost;
+ int getsrv, freehost;
+ int errnobak;
+
+ if(fd >= 0)
+ dc_disconnect();
+ state = -1;
+ freehost = 0;
+ if(port < 0)
+ {
+ port = 1500;
+ getsrv = 1;
+ } else {
+ getsrv = 0;
+ }
+ memset(&hint, 0, sizeof(hint));
+ hint.ai_socktype = SOCK_STREAM;
+ if(getsrv)
+ {
+ if(!getsrvrr(host, &newhost, &port))
+ {
+ host = newhost;
+ freehost = 1;
+ }
+ }
+ servport = port;
+ if(hostlist != NULL)
+ freeaddrinfo(hostlist);
+ if(getaddrinfo(host, NULL, &hint, &hostlist))
+ {
+ errno = ENONET;
+ if(freehost)
+ free(host);
+ return(-1);
+ }
+ for(curhost = hostlist; curhost != NULL; curhost = curhost->ai_next)
+ {
+ if((fd = socket(curhost->ai_family, curhost->ai_socktype, curhost->ai_protocol)) < 0)
+ {
+ errnobak = errno;
+ if(freehost)
+ free(host);
+ errno = errnobak;
+ return(-1);
+ }
+ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
+ memcpy(&addr, curhost->ai_addr, curhost->ai_addrlen);
+ if(addr.ss_family == AF_INET)
+ {
+ ipv4 = (struct sockaddr_in *)&addr;
+ ipv4->sin_port = htons(port);
+ }
+#ifdef HAVE_IPV6
+ if(addr.ss_family == AF_INET6)
+ {
+ ipv6 = (struct sockaddr_in6 *)&addr;
+ ipv6->sin6_port = htons(port);
+ }
+#endif
+ if(connect(fd, (struct sockaddr *)&addr, curhost->ai_addrlen))
+ {
+ if(errno == EINPROGRESS)
+ {
+ state = 0;
+ break;
+ }
+ close(fd);
+ fd = -1;
+ } else {
+ state = 1;
+ break;
+ }
+ }
+ qcmd = makeqcmd(NULL);
+ resetreader = 1;
+ if(dchostname != NULL)
+ free(dchostname);
+ dchostname = sstrdup(host);
+ if(freehost)
+ free(host);
+ return(fd);
+}
+
+struct dc_intresp *dc_interpret(struct dc_response *resp)
+{
+ int i;
+ struct dc_intresp *iresp;
+ struct command *cmd;
+ struct respclass *cls;
+ int code;
+ int args;
+
+ if((resp->numlines == 0) || (resp->rlines[0].argc == 0) || (resp->curline >= resp->numlines))
+ return(NULL);
+ code = wcstol(resp->rlines[0].argv[0], NULL, 10);
+ cmd = (struct command *)(resp->internal);
+ for(cls = cmd->classes; cls != NULL; cls = cls->next)
+ {
+ if(cls->code == code)
+ break;
+ }
+ if(cls == NULL)
+ return(NULL);
+ if(cls->nwords >= resp->rlines[resp->curline].argc)
+ return(NULL);
+ iresp = smalloc(sizeof(*iresp));
+ iresp->code = code;
+ iresp->argv = NULL;
+ iresp->argc = 0;
+ args = 0;
+ for(i = 0; i < cls->nwords; i++)
+ {
+ switch(cls->wordt[i])
+ {
+ case RESP_DSC:
+ break;
+ case RESP_STR:
+ sizebuf(&(iresp->argv), &args, iresp->argc + 1, sizeof(*(iresp->argv)), 1);
+ iresp->argv[iresp->argc].val.str = swcsdup(resp->rlines[resp->curline].argv[i + 1]);
+ iresp->argv[iresp->argc].type = cls->wordt[i];
+ iresp->argc++;
+ break;
+ case RESP_INT:
+ sizebuf(&(iresp->argv), &args, iresp->argc + 1, sizeof(*(iresp->argv)), 1);
+ iresp->argv[iresp->argc].val.num = wcstol(resp->rlines[resp->curline].argv[i + 1], NULL, 0);
+ iresp->argv[iresp->argc].type = cls->wordt[i];
+ iresp->argc++;
+ break;
+ case RESP_FLOAT:
+ sizebuf(&(iresp->argv), &args, iresp->argc + 1, sizeof(*(iresp->argv)), 1);
+ iresp->argv[iresp->argc].val.flnum = wcstod(resp->rlines[resp->curline].argv[i + 1], NULL);
+ iresp->argv[iresp->argc].type = cls->wordt[i];
+ iresp->argc++;
+ break;
+ }
+ }
+ resp->curline++;
+ return(iresp);
+}
+
+void dc_freeires(struct dc_intresp *ires)
+{
+ int i;
+
+ for(i = 0; i < ires->argc; i++)
+ {
+ if(ires->argv[i].type == RESP_STR)
+ free(ires->argv[i].val.str);
+ }
+ free(ires->argv);
+ free(ires);
+}
+
+const char *dc_gethostname(void)
+{
+ return(dchostname);
+}
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#ifndef _UILIB_H
+#define _UILIB_H
+
+#endif
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <unistd.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <pwd.h>
+#include <string.h>
+#include <malloc.h>
+#include <stdio.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <doldaconnect/uilib.h>
+#include <doldaconnect/uimisc.h>
+#include <doldaconnect/utils.h>
+
+#ifdef HAVE_KRB5
+#include <krb5.h>
+#include <com_err.h>
+#endif
+
+struct logindata;
+
+struct authmech
+{
+ wchar_t *name;
+ void (*process)(struct dc_response *resp, struct logindata *data);
+ int (*init)(struct logindata *data);
+ void (*release)(struct logindata *data);
+};
+
+struct logindata
+{
+ int (*conv)(int type, wchar_t *text, char **resp, void *data);
+ void (*callback)(int err, wchar_t *reason, void *data);
+ char *username;
+ int freeusername;
+ int useauthless;
+ void *data;
+ void *mechdata;
+ struct authmech *mech;
+};
+
+struct gencbdata
+{
+ void (*callback)(int resp, void *data);
+ void *data;
+};
+
+struct dc_fnetnode *dc_fnetnodes = NULL;
+struct dc_transfer *dc_transfers = NULL;
+
+static void freelogindata(struct logindata *data)
+{
+ if((data->mech != NULL) && (data->mech->release != NULL))
+ data->mech->release(data);
+ if(data->freeusername)
+ free(data->username);
+ free(data);
+}
+
+static int logincallback(struct dc_response *resp);
+
+static void process_authless(struct dc_response *resp, struct logindata *data)
+{
+ switch(resp->code)
+ {
+ case 200:
+ data->callback(DC_LOGIN_ERR_SUCCESS, NULL, data->data);
+ freelogindata(data);
+ break;
+/*
+ case 303:
+ case 304:
+ if((ires = dc_interpret(resp)) != NULL)
+ {
+ buf = NULL;
+ if(data->conv((resp->code == 303)?DC_LOGIN_CONV_INFO:DC_LOGIN_CONV_ERROR, ires->argv[0].val.str, &buf, data->data))
+ {
+ data->callback(DC_LOGIN_ERR_CONV, NULL, data->data);
+ freelogindata(data);
+ } else {
+ dc_queuecmd(logincallback, data, L"pass", L"", NULL);
+ }
+ if(buf != NULL)
+ {
+ memset(buf, 0, strlen(buf));
+ free(buf);
+ }
+ dc_freeires(ires);
+ }
+ break;
+*/
+ case 505:
+ data->callback(DC_LOGIN_ERR_SERVER, NULL, data->data);
+ freelogindata(data);
+ break;
+ case 506:
+ data->callback(DC_LOGIN_ERR_AUTHFAIL, NULL, data->data);
+ freelogindata(data);
+ break;
+ default:
+ data->callback(DC_LOGIN_ERR_USER, NULL, data->data);
+ freelogindata(data);
+ break;
+ }
+}
+
+static void process_pam(struct dc_response *resp, struct logindata *data)
+{
+ struct dc_intresp *ires;
+ int convtype;
+ char *buf;
+
+ switch(resp->code)
+ {
+ case 200:
+ data->callback(DC_LOGIN_ERR_SUCCESS, NULL, data->data);
+ freelogindata(data);
+ break;
+ case 301:
+ case 302:
+ case 303:
+ case 304:
+ if(resp->code == 301)
+ convtype = DC_LOGIN_CONV_NOECHO;
+ else if(resp->code == 302)
+ convtype = DC_LOGIN_CONV_ECHO;
+ else if(resp->code == 303)
+ convtype = DC_LOGIN_CONV_INFO;
+ else if(resp->code == 304)
+ convtype = DC_LOGIN_CONV_ERROR;
+ if((ires = dc_interpret(resp)) != NULL)
+ {
+ buf = NULL;
+ if(data->conv(convtype, ires->argv[0].val.str, &buf, data->data))
+ {
+ data->callback(DC_LOGIN_ERR_CONV, NULL, data->data);
+ freelogindata(data);
+ } else {
+ dc_queuecmd(logincallback, data, L"pass", L"%%s", buf, NULL);
+ }
+ if(buf != NULL)
+ {
+ memset(buf, 0, strlen(buf));
+ free(buf);
+ }
+ dc_freeires(ires);
+ }
+ break;
+ case 505:
+ data->callback(DC_LOGIN_ERR_SERVER, NULL, data->data);
+ freelogindata(data);
+ break;
+ case 506:
+ data->callback(DC_LOGIN_ERR_AUTHFAIL, NULL, data->data);
+ freelogindata(data);
+ break;
+ default:
+ data->callback(DC_LOGIN_ERR_USER, NULL, data->data);
+ freelogindata(data);
+ break;
+ }
+}
+
+#ifdef HAVE_KRB5
+struct krb5data
+{
+ int state;
+ krb5_context context;
+ krb5_principal sprinc, myprinc;
+ krb5_ccache ccache;
+ krb5_auth_context authcon;
+ krb5_data reqbuf;
+ krb5_creds *servcreds;
+ int valid, fwd, fwded;
+};
+
+static char *hexencode(char *data, size_t datalen)
+{
+ char *buf, this;
+ size_t bufsize, bufdata;
+ int dig;
+
+ buf = NULL;
+ bufsize = bufdata = 0;
+ for(; datalen > 0; datalen--, data++)
+ {
+ dig = (*data & 0xF0) >> 4;
+ if(dig > 9)
+ this = 'A' + dig - 10;
+ else
+ this = dig + '0';
+ addtobuf(buf, this);
+ dig = *data & 0x0F;
+ if(dig > 9)
+ this = 'A' + dig - 10;
+ else
+ this = dig + '0';
+ addtobuf(buf, this);
+ }
+ addtobuf(buf, 0);
+ return(buf);
+}
+
+static char *hexdecode(char *data, size_t *len)
+{
+ char *buf, this;
+ size_t bufsize, bufdata;
+
+ buf = NULL;
+ bufsize = bufdata = 0;
+ for(; *data; data++)
+ {
+ if((*data >= 'A') && (*data <= 'F'))
+ {
+ this = (this & 0x0F) | ((*data - 'A' + 10) << 4);
+ } else if((*data >= '0') && (*data <= '9')) {
+ this = (this & 0x0F) | ((*data - '0') << 4);
+ } else {
+ if(buf != NULL)
+ free(buf);
+ return(NULL);
+ }
+ data++;
+ if(!*data)
+ {
+ if(buf != NULL)
+ free(buf);
+ return(NULL);
+ }
+ if((*data >= 'A') && (*data <= 'F'))
+ {
+ this = (this & 0xF0) | (*data - 'A' + 10);
+ } else if((*data >= '0') && (*data <= '9')) {
+ this = (this & 0xF0) | (*data - '0');
+ } else {
+ if(buf != NULL)
+ free(buf);
+ return(NULL);
+ }
+ addtobuf(buf, this);
+ }
+ addtobuf(buf, 0);
+ if(len != NULL)
+ *len = bufdata - 1;
+ return(buf);
+}
+
+static void process_krb5(struct dc_response *resp, struct logindata *data)
+{
+ int ret;
+ struct dc_intresp *ires;
+ struct krb5data *krb;
+ krb5_data k5d;
+ krb5_ap_rep_enc_part *repl;
+ char *buf;
+
+ krb = data->mechdata;
+ switch(resp->code)
+ {
+ case 200:
+ data->callback(DC_LOGIN_ERR_SUCCESS, NULL, data->data);
+ freelogindata(data);
+ break;
+ case 300:
+ switch(krb->state)
+ {
+ case 0:
+ buf = hexencode(krb->reqbuf.data, krb->reqbuf.length);
+ dc_queuecmd(logincallback, data, L"pass", L"%%s", buf, NULL);
+ free(buf);
+ krb->state = 1;
+ break;
+ case 1:
+ if((ires = dc_interpret(resp)) != NULL)
+ {
+ k5d.data = hexdecode(icswcstombs(ires->argv[0].val.str, NULL, NULL), &k5d.length);
+ if(!krb->valid)
+ {
+ if((ret = krb5_rd_rep(krb->context, krb->authcon, &k5d, &repl)) != 0)
+ {
+ data->callback(DC_LOGIN_ERR_SERVER, NULL, data->data);
+ freelogindata(data);
+ break;
+ }
+ /* XXX: Do I need to do something with this? */
+ krb->valid = 1;
+ krb5_free_ap_rep_enc_part(krb->context, repl);
+ }
+ if(krb->fwd && !krb->fwded)
+ {
+ if(krb->reqbuf.data != NULL)
+ free(krb->reqbuf.data);
+ krb->reqbuf.data = NULL;
+ if((ret = krb5_fwd_tgt_creds(krb->context, krb->authcon, NULL, krb->servcreds->client, krb->servcreds->server, 0, 1, &krb->reqbuf)) != 0)
+ {
+ fprintf(stderr, "krb5_fwd_tgt_creds reported an error: %s\n", error_message(ret));
+ dc_queuecmd(logincallback, data, L"pass", L"31", NULL);
+ krb->fwd = 0;
+ krb->state = 2;
+ } else {
+ dc_queuecmd(logincallback, data, L"pass", L"32", NULL);
+ krb->state = 0;
+ krb->fwded = 1;
+ }
+ } else {
+ dc_queuecmd(logincallback, data, L"pass", L"31", NULL);
+ krb->state = 2;
+ }
+ free(k5d.data);
+ dc_freeires(ires);
+ }
+ break;
+ default:
+ data->callback(DC_LOGIN_ERR_USER, NULL, data->data);
+ freelogindata(data);
+ break;
+ }
+ break;
+ case 505:
+ data->callback(DC_LOGIN_ERR_SERVER, NULL, data->data);
+ freelogindata(data);
+ break;
+ case 506:
+ data->callback(DC_LOGIN_ERR_AUTHFAIL, NULL, data->data);
+ freelogindata(data);
+ break;
+ default:
+ data->callback(DC_LOGIN_ERR_USER, NULL, data->data);
+ freelogindata(data);
+ break;
+ }
+}
+
+static int init_krb5(struct logindata *data)
+{
+ int ret;
+ struct krb5data *krb;
+ krb5_data cksum;
+ krb5_creds creds;
+
+ krb = smalloc(sizeof(*krb));
+ memset(krb, 0, sizeof(*krb));
+ krb->fwd = 1;
+ krb->fwded = 0;
+ data->mechdata = krb;
+ if((ret = krb5_init_context(&krb->context)) != 0)
+ {
+ fprintf(stderr, "krb5_init_context reported an error: %s\n", error_message(ret));
+ return(1);
+ }
+ if((ret = krb5_auth_con_init(krb->context, &krb->authcon)) != 0)
+ {
+ fprintf(stderr, "krb5_auth_con_init reported an error: %s\n", error_message(ret));
+ return(1);
+ }
+ krb5_auth_con_setflags(krb->context, krb->authcon, KRB5_AUTH_CONTEXT_DO_SEQUENCE);
+ if((ret = krb5_sname_to_principal(krb->context, dc_gethostname(), "doldacond", KRB5_NT_SRV_HST, &krb->sprinc)) != 0)
+ {
+ fprintf(stderr, "krb5_sname_to_principal reported an error: %s\n", error_message(ret));
+ return(1);
+ }
+ if((ret = krb5_cc_default(krb->context, &krb->ccache)) != 0)
+ {
+ fprintf(stderr, "krb5_cc_default reported an error: %s\n", error_message(ret));
+ return(1);
+ }
+ if((ret = krb5_cc_get_principal(krb->context, krb->ccache, &krb->myprinc)) != 0)
+ {
+ fprintf(stderr, "krb5_cc_default reported an error: %s\n", error_message(ret));
+ return(1);
+ }
+ memset(&creds, 0, sizeof(creds));
+ creds.client = krb->myprinc;
+ creds.server = krb->sprinc;
+ if((ret = krb5_get_credentials(krb->context, 0, krb->ccache, &creds, &krb->servcreds)) != 0)
+ {
+ fprintf(stderr, "krb5_get_credentials reported an error: %s\n", error_message(ret));
+ return(1);
+ }
+ /* WTF is this checksum stuff?! The Krb docs don't say a word about it! */
+ cksum.data = sstrdup(dc_gethostname());
+ cksum.length = strlen(cksum.data);
+ if((ret = krb5_mk_req_extended(krb->context, &krb->authcon, AP_OPTS_MUTUAL_REQUIRED, &cksum, krb->servcreds, &krb->reqbuf)) != 0)
+ {
+ fprintf(stderr, "krb5_mk_req_extended reported an error: %s\n", error_message(ret));
+ return(1);
+ }
+ free(cksum.data);
+ krb->state = 0;
+ return(0);
+}
+
+static void release_krb5(struct logindata *data)
+{
+ struct krb5data *krb;
+
+ if((krb = data->mechdata) == NULL)
+ return;
+ if(krb->servcreds != NULL)
+ krb5_free_creds(krb->context, krb->servcreds);
+ if(krb->reqbuf.data != NULL)
+ free(krb->reqbuf.data);
+ if(krb->sprinc != NULL)
+ krb5_free_principal(krb->context, krb->sprinc);
+ if(krb->myprinc != NULL)
+ krb5_free_principal(krb->context, krb->myprinc);
+ if(krb->ccache != NULL)
+ krb5_cc_close(krb->context, krb->ccache);
+ if(krb->authcon != NULL)
+ krb5_auth_con_free(krb->context, krb->authcon);
+ if(krb->context != NULL)
+ krb5_free_context(krb->context);
+ free(krb);
+}
+#endif
+
+/* Arranged in order of priority */
+static struct authmech authmechs[] =
+{
+#ifdef HAVE_KRB5
+ {
+ .name = L"krb5",
+ .process = process_krb5,
+ .init = init_krb5,
+ .release = release_krb5
+ },
+#endif
+ {
+ .name = L"authless",
+ .process = process_authless,
+ .init = NULL,
+ .release = NULL
+ },
+ {
+ .name = L"pam",
+ .process = process_pam,
+ .init = NULL,
+ .release = NULL
+ },
+ {
+ .name = NULL
+ }
+};
+
+static int builtinconv(int type, wchar_t *text, char **resp, void *data)
+{
+ char *buf, *pass;
+
+ if(isatty(0))
+ {
+ if((buf = icwcstombs(text, NULL)) == NULL)
+ return(1);
+ pass = getpass(buf);
+ free(buf);
+ *resp = sstrdup(pass);
+ memset(pass, 0, strlen(pass));
+ return(0);
+ }
+ return(1);
+}
+
+static int logincallback(struct dc_response *resp)
+{
+ int i;
+ struct dc_intresp *ires;
+ struct logindata *data;
+ int mech;
+ char *username;
+ struct passwd *pwent;
+ void *odata, *ndata;
+
+ data = resp->data;
+ if(!wcscmp(resp->cmdname, L"lsauth"))
+ {
+ if(resp->code == 201)
+ {
+ data->callback(DC_LOGIN_ERR_NOLOGIN, NULL, data->data);
+ freelogindata(data);
+ } else {
+ mech = -1;
+ while((ires = dc_interpret(resp)) != NULL)
+ {
+ if(!data->useauthless && !wcscmp(ires->argv[0].val.str, L"authless"))
+ {
+ dc_freeires(ires);
+ continue;
+ }
+ for(i = 0; authmechs[i].name != NULL; i++)
+ {
+ if(!wcscmp(authmechs[i].name, ires->argv[0].val.str) && ((i < mech) || (mech == -1)))
+ {
+ odata = data->mechdata;
+ data->mechdata = NULL;
+ if((authmechs[i].init != NULL) && authmechs[i].init(data))
+ {
+ if(authmechs[i].release != NULL)
+ authmechs[i].release(data);
+ data->mechdata = odata;
+ fprintf(stderr, "authentication mechanism %ls failed, trying further...\n", authmechs[i].name);
+ } else {
+ if((data->mech != NULL) && data->mech->release != NULL)
+ {
+ ndata = data->mechdata;
+ data->mechdata = odata;
+ data->mech->release(data);
+ data->mechdata = ndata;
+ }
+ mech = i;
+ data->mech = authmechs + i;
+ }
+ break;
+ }
+ }
+ dc_freeires(ires);
+ }
+ if(mech == -1)
+ {
+ data->callback(DC_LOGIN_ERR_NOLOGIN, NULL, data->data);
+ freelogindata(data);
+ } else {
+ if((username = data->username) == NULL)
+ {
+ if((pwent = getpwuid(getuid())) == NULL)
+ {
+ data->callback(DC_LOGIN_ERR_USER, NULL, data->data);
+ freelogindata(data);
+ return(1);
+ }
+ username = pwent->pw_name;
+ }
+ dc_queuecmd(logincallback, data, L"login", data->mech->name, L"%%s", username, NULL);
+ }
+ }
+ } else if(!wcscmp(resp->cmdname, L"login") || !wcscmp(resp->cmdname, L"pass")) {
+ data->mech->process(resp, data);
+ }
+ return(1);
+}
+
+void dc_loginasync(char *username, int useauthless, int (*conv)(int, wchar_t *, char **, void *), void (*callback)(int, wchar_t *, void *), void *udata)
+{
+ struct logindata *data;
+
+ data = smalloc(sizeof(*data));
+ if(conv == NULL)
+ conv = builtinconv;
+ data->conv = conv;
+ data->mech = NULL;
+ data->data = udata;
+ data->mechdata = NULL;
+ data->callback = callback;
+ data->useauthless = useauthless;
+ data->freeusername = 0;
+ if(username == NULL)
+ {
+ data->username = NULL;
+ } else {
+ data->username = sstrdup(username);
+ data->freeusername = 1;
+ }
+ dc_queuecmd(logincallback, data, L"lsauth", NULL);
+}
+
+static struct dc_fnetnode *newfn(void)
+{
+ struct dc_fnetnode *fn;
+
+ fn = smalloc(sizeof(*fn));
+ memset(fn, 0, sizeof(*fn));
+ fn->id = -1;
+ fn->name = NULL;
+ fn->fnet = NULL;
+ fn->state = fn->numusers = fn->found = 0;
+ fn->destroycb = NULL;
+ fn->udata = NULL;
+ fn->next = dc_fnetnodes;
+ fn->prev = NULL;
+ if(dc_fnetnodes != NULL)
+ dc_fnetnodes->prev = fn;
+ dc_fnetnodes = fn;
+ return(fn);
+}
+
+static void freefn(struct dc_fnetnode *fn)
+{
+ if(fn->next != NULL)
+ fn->next->prev = fn->prev;
+ if(fn->prev != NULL)
+ fn->prev->next = fn->next;
+ if(fn == dc_fnetnodes)
+ dc_fnetnodes = fn->next;
+ if(fn->destroycb != NULL)
+ fn->destroycb(fn);
+ if(fn->name != NULL)
+ free(fn->name);
+ if(fn->fnet != NULL)
+ free(fn->fnet);
+ free(fn);
+}
+
+struct dc_fnetnode *dc_findfnetnode(int id)
+{
+ struct dc_fnetnode *fn;
+
+ for(fn = dc_fnetnodes; fn != NULL; fn = fn->next)
+ {
+ if(fn->id == id)
+ break;
+ }
+ return(fn);
+}
+
+static struct dc_transfer *newtransfer(void)
+{
+ struct dc_transfer *transfer;
+
+ transfer = smalloc(sizeof(*transfer));
+ memset(transfer, 0, sizeof(*transfer));
+ transfer->id = -1;
+ transfer->peerid = transfer->peernick = transfer->path = NULL;
+ transfer->state = DC_TRNS_WAITING;
+ transfer->dir = DC_TRNSD_UNKNOWN;
+ transfer->size = -1;
+ transfer->curpos = -1;
+ transfer->destroycb = NULL;
+ transfer->udata = NULL;
+ transfer->next = dc_transfers;
+ transfer->prev = NULL;
+ if(dc_transfers != NULL)
+ dc_transfers->prev = transfer;
+ dc_transfers = transfer;
+ return(transfer);
+}
+
+static void freetransfer(struct dc_transfer *transfer)
+{
+ if(transfer->next != NULL)
+ transfer->next->prev = transfer->prev;
+ if(transfer->prev != NULL)
+ transfer->prev->next = transfer->next;
+ if(transfer == dc_transfers)
+ dc_transfers = transfer->next;
+ if(transfer->destroycb != NULL)
+ transfer->destroycb(transfer);
+ if(transfer->peerid != NULL)
+ free(transfer->peerid);
+ if(transfer->peernick != NULL)
+ free(transfer->peernick);
+ if(transfer->path != NULL)
+ free(transfer->path);
+ free(transfer);
+}
+
+struct dc_transfer *dc_findtransfer(int id)
+{
+ struct dc_transfer *transfer;
+
+ for(transfer = dc_transfers; transfer != NULL; transfer = transfer->next)
+ {
+ if(transfer->id == id)
+ break;
+ }
+ return(transfer);
+}
+
+static int getfnlistcallback(struct dc_response *resp)
+{
+ struct dc_intresp *ires;
+ struct gencbdata *data;
+ struct dc_fnetnode *fn, *next;
+
+ data = resp->data;
+ if(resp->code == 200)
+ {
+ for(fn = dc_fnetnodes; fn != NULL; fn = fn->next)
+ fn->found = 0;
+ while((ires = dc_interpret(resp)) != NULL)
+ {
+ if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
+ {
+ fn->found = 1;
+ if(fn->fnet != NULL)
+ free(fn->fnet);
+ fn->fnet = swcsdup(ires->argv[1].val.str);
+ if(fn->name != NULL)
+ free(fn->name);
+ fn->name = swcsdup(ires->argv[2].val.str);
+ fn->numusers = ires->argv[3].val.num;
+ fn->state = ires->argv[4].val.num;
+ } else {
+ fn = newfn();
+ fn->id = ires->argv[0].val.num;
+ fn->fnet = swcsdup(ires->argv[1].val.str);
+ fn->name = swcsdup(ires->argv[2].val.str);
+ fn->numusers = ires->argv[3].val.num;
+ fn->state = ires->argv[4].val.num;
+ fn->found = 1;
+ }
+ dc_freeires(ires);
+ }
+ for(fn = dc_fnetnodes; fn != NULL; fn = next)
+ {
+ next = fn->next;
+ if(!fn->found)
+ freefn(fn);
+ }
+ data->callback(200, data->data);
+ free(resp->data);
+ } else if(resp->code == 201) {
+ while(dc_fnetnodes != NULL)
+ freefn(dc_fnetnodes);
+ data->callback(201, data->data);
+ free(resp->data);
+ } else if(resp->code == 502) {
+ while(dc_fnetnodes != NULL)
+ freefn(dc_fnetnodes);
+ data->callback(502, data->data);
+ free(resp->data);
+ }
+ return(1);
+}
+
+static int gettrlistcallback(struct dc_response *resp)
+{
+ struct dc_intresp *ires;
+ struct gencbdata *data;
+ struct dc_transfer *transfer, *next;
+
+ data = resp->data;
+ if(resp->code == 200)
+ {
+ for(transfer = dc_transfers; transfer != NULL; transfer = transfer->next)
+ transfer->found = 0;
+ while((ires = dc_interpret(resp)) != NULL)
+ {
+ if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
+ {
+ transfer->found = 1;
+ if((transfer->path == NULL) || wcscmp(transfer->path, ires->argv[5].val.str))
+ {
+ if(transfer->path != NULL)
+ free(transfer->path);
+ transfer->path = swcsdup(ires->argv[5].val.str);
+ }
+ if((transfer->peerid == NULL) || wcscmp(transfer->peerid, ires->argv[3].val.str))
+ {
+ if(transfer->peerid != NULL)
+ free(transfer->peerid);
+ transfer->peerid = swcsdup(ires->argv[3].val.str);
+ }
+ if((transfer->peernick == NULL) || wcscmp(transfer->peernick, ires->argv[4].val.str))
+ {
+ if(transfer->peernick != NULL)
+ free(transfer->peernick);
+ transfer->peernick = swcsdup(ires->argv[4].val.str);
+ }
+ transfer->dir = ires->argv[1].val.num;
+ transfer->state = ires->argv[2].val.num;
+ transfer->size = ires->argv[6].val.num;
+ transfer->curpos = ires->argv[7].val.num;
+ } else {
+ transfer = newtransfer();
+ transfer->id = ires->argv[0].val.num;
+ transfer->dir = ires->argv[1].val.num;
+ transfer->state = ires->argv[2].val.num;
+ transfer->peerid = swcsdup(ires->argv[3].val.str);
+ transfer->peernick = swcsdup(ires->argv[4].val.str);
+ transfer->path = swcsdup(ires->argv[5].val.str);
+ transfer->size = ires->argv[6].val.num;
+ transfer->curpos = ires->argv[7].val.num;
+ transfer->found = 1;
+ }
+ dc_freeires(ires);
+ }
+ for(transfer = dc_transfers; transfer != NULL; transfer = next)
+ {
+ next = transfer->next;
+ if(!transfer->found)
+ freetransfer(transfer);
+ }
+ data->callback(200, data->data);
+ free(data);
+ } else if(resp->code == 201) {
+ while(dc_transfers != NULL)
+ freetransfer(dc_transfers);
+ data->callback(201, data->data);
+ free(data);
+ } else if(resp->code == 502) {
+ while(dc_transfers != NULL)
+ freetransfer(dc_transfers);
+ data->callback(502, data->data);
+ free(data);
+ }
+ return(1);
+}
+
+void dc_getfnlistasync(void (*callback)(int, void *), void *udata)
+{
+ struct gencbdata *data;
+
+ data = smalloc(sizeof(*data));
+ data->callback = callback;
+ data->data = udata;
+ dc_queuecmd(getfnlistcallback, data, L"lsnodes", NULL);
+}
+
+void dc_gettrlistasync(void (*callback)(int, void *), void *udata)
+{
+ struct gencbdata *data;
+
+ data = smalloc(sizeof(*data));
+ data->callback = callback;
+ data->data = udata;
+ dc_queuecmd(gettrlistcallback, data, L"lstrans", NULL);
+}
+
+void dc_uimisc_disconnected(void)
+{
+ while(dc_fnetnodes != NULL)
+ freefn(dc_fnetnodes);
+ while(dc_transfers != NULL)
+ freetransfer(dc_transfers);
+}
+
+void dc_uimisc_handlenotify(struct dc_response *resp)
+{
+ struct dc_fnetnode *fn;
+ struct dc_transfer *transfer;
+ struct dc_intresp *ires;
+
+ if((ires = dc_interpret(resp)) == NULL)
+ return;
+ switch(resp->code)
+ {
+ case 601:
+ if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
+ fn->state = ires->argv[1].val.num;
+ break;
+ case 602:
+ if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
+ {
+ if(fn->name != NULL)
+ free(fn->name);
+ fn->name = swcsdup(ires->argv[1].val.str);
+ }
+ break;
+ case 603:
+ if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
+ freefn(fn);
+ break;
+ case 604:
+ fn = newfn();
+ fn->id = ires->argv[0].val.num;
+ if(fn->fnet != NULL)
+ free(fn->fnet);
+ fn->fnet = swcsdup(ires->argv[1].val.str);
+ fn->state = DC_FNN_STATE_SYN;
+ fn->numusers = 0;
+ break;
+ case 605:
+ if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
+ fn->numusers = ires->argv[1].val.num;
+ break;
+ case 610:
+ transfer = newtransfer();
+ transfer->id = ires->argv[0].val.num;
+ transfer->dir = ires->argv[1].val.num;
+ if(transfer->dir == DC_TRNSD_UP)
+ transfer->state = DC_TRNS_HS;
+ transfer->peerid = swcsdup(ires->argv[2].val.str);
+ if(ires->argv[3].val.str[0])
+ transfer->path = swcsdup(ires->argv[3].val.str);
+ break;
+ case 611:
+ if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
+ transfer->state = ires->argv[1].val.num;
+ break;
+ case 612:
+ if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
+ {
+ if(transfer->peernick != NULL)
+ free(transfer->peernick);
+ transfer->peernick = swcsdup(ires->argv[1].val.str);
+ }
+ break;
+ case 613:
+ if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
+ transfer->size = ires->argv[1].val.num;
+ break;
+ case 614:
+ if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
+ {
+ transfer->error = ires->argv[1].val.num;
+ time(&transfer->errortime);
+ }
+ break;
+ case 615:
+ if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
+ transfer->curpos = ires->argv[1].val.num;
+ break;
+ case 616:
+ if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
+ {
+ if(transfer->path != NULL)
+ free(transfer->path);
+ transfer->path = swcsdup(ires->argv[1].val.str);
+ }
+ break;
+ case 617:
+ if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
+ freetransfer(transfer);
+ break;
+ default:
+ break;
+ }
+ dc_freeires(ires);
+ resp->curline = 0;
+}
+
+/* Note the backspace handling - it's not as elegant as possible, but
+ * it helps avoid the "box-of-toothpicks" syndrome when writing search
+ * expressions manually. */
+wchar_t **dc_lexsexpr(wchar_t *sexpr)
+{
+ wchar_t **ret;
+ wchar_t *buf;
+ size_t retsize, retdata, bufsize, bufdata;
+ int state;
+
+ ret = NULL;
+ buf = NULL;
+ retsize = retdata = bufsize = bufdata = 0;
+ state = 0;
+ while(*sexpr != L'\0')
+ {
+ switch(state)
+ {
+ case 0:
+ if(!iswspace(*sexpr))
+ state = 1;
+ else
+ sexpr++;
+ break;
+ case 1:
+ if(iswspace(*sexpr))
+ {
+ if(buf != NULL)
+ {
+ addtobuf(buf, L'\0');
+ addtobuf(ret, buf);
+ buf = NULL;
+ bufsize = bufdata = 0;
+ }
+ state = 0;
+ } else if((*sexpr == L'(') ||
+ (*sexpr == L')') ||
+ (*sexpr == L'&') ||
+ (*sexpr == L'|') ||
+ (*sexpr == L'!')) {
+ if(buf != NULL)
+ {
+ addtobuf(buf, L'\0');
+ addtobuf(ret, buf);
+ buf = NULL;
+ bufsize = bufdata = 0;
+ }
+ addtobuf(buf, *sexpr);
+ addtobuf(buf, L'\0');
+ addtobuf(ret, buf);
+ buf = NULL;
+ bufsize = bufdata = 0;
+ sexpr++;
+ } else if(*sexpr == L'\"') {
+ sexpr++;
+ state = 2;
+ } else if(*sexpr == L'\\') {
+ sexpr++;
+ if(*sexpr == L'\0')
+ {
+ addtobuf(buf, *sexpr);
+ } else if((*sexpr == L'\\') || (*sexpr == L'\"')) {
+ addtobuf(buf, *sexpr);
+ sexpr++;
+ } else {
+ addtobuf(buf, L'\\');
+ addtobuf(buf, *sexpr);
+ sexpr++;
+ }
+ } else {
+ addtobuf(buf, *(sexpr++));
+ }
+ break;
+ case 2:
+ if(*sexpr == L'\\')
+ {
+ sexpr++;
+ if(*sexpr == L'\0')
+ {
+ addtobuf(buf, *sexpr);
+ } else if((*sexpr == L'\\') || (*sexpr == L'\"')) {
+ addtobuf(buf, *sexpr);
+ sexpr++;
+ } else {
+ addtobuf(buf, L'\\');
+ addtobuf(buf, *sexpr);
+ sexpr++;
+ }
+ } else if(*sexpr == L'\"') {
+ state = 1;
+ sexpr++;
+ } else {
+ addtobuf(buf, *(sexpr++));
+ }
+ break;
+ }
+ }
+ if(buf != NULL)
+ {
+ addtobuf(buf, L'\0');
+ addtobuf(ret, buf);
+ }
+ addtobuf(ret, NULL);
+ return(ret);
+}
+
+void dc_freewcsarr(wchar_t **arr)
+{
+ wchar_t **buf;
+
+ if(arr == NULL)
+ return;
+ for(buf = arr; *buf != NULL; buf++)
+ free(*buf);
+ free(arr);
+}
--- /dev/null
+/*
+ * Dolda Connect - Modular multiuser Direct Connect-style client
+ * Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <malloc.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <iconv.h>
+#include <errno.h>
+#include <string.h>
+#include <wctype.h>
+#include <langinfo.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <doldaconnect/utils.h>
+
+extern int vswprintf (wchar_t *__restrict __s, size_t __n,
+ __const wchar_t *__restrict __format,
+ __gnuc_va_list __arg);
+
+char *vsprintf2(char *format, va_list al)
+{
+ int ret;
+ char *buf;
+
+ ret = vsnprintf(NULL, 0, format, al);
+ if((buf = malloc(ret + 1)) == NULL)
+ {
+ return(NULL);
+ }
+ vsnprintf(buf, ret + 1, format, al);
+ return(buf);
+}
+
+char *sprintf2(char *format, ...)
+{
+ va_list args;
+ char *buf;
+
+ va_start(args, format);
+ buf = vsprintf2(format, args);
+ va_end(args);
+ return(buf);
+}
+
+wchar_t *vswprintf2(wchar_t *format, va_list al)
+{
+ int ret;
+ wchar_t *buf;
+ size_t bufsize;
+
+ buf = smalloc(sizeof(wchar_t) * (bufsize = 1024));
+ while((ret = vswprintf(buf, bufsize, format, al)) < 0)
+ buf = srealloc(buf, sizeof(wchar_t) * (bufsize *= 2));
+ if(bufsize > ret + 1)
+ buf = srealloc(buf, sizeof(wchar_t) * (ret + 1));
+ return(buf);
+}
+
+wchar_t *swprintf2(wchar_t *format, ...)
+{
+ va_list args;
+ wchar_t *buf;
+
+ va_start(args, format);
+ buf = vswprintf2(format, args);
+ va_end(args);
+ return(buf);
+}
+
+wchar_t *icmbstowcs(char *mbs, char *charset)
+{
+ int ret;
+ char *buf;
+ char *p, *p2;
+ size_t len1, len2, bufsize, data;
+ iconv_t cd;
+
+ len1 = strlen(mbs) + 1;
+ bufsize = len2 = len1 * sizeof(wchar_t);
+ if((buf = malloc(bufsize)) == NULL)
+ {
+ return(NULL);
+ }
+ if(charset == NULL)
+ charset = nl_langinfo(CODESET);
+ if((cd = iconv_open("wchar_t", charset)) == (iconv_t)-1)
+ {
+ free(buf);
+ return(NULL);
+ }
+ p = buf;
+ while(len1 > 0)
+ {
+ ret = iconv(cd, &mbs, &len1, &p, &len2);
+ if(ret < 0)
+ {
+ if(errno == E2BIG)
+ {
+ data = p - buf;
+ len2 += 128;
+ bufsize += 128;
+ if((p2 = realloc(buf, bufsize)) == NULL)
+ {
+ free(buf);
+ return(NULL);
+ }
+ buf = p2;
+ p = buf + data;
+ } else {
+ free(buf);
+ return(NULL);
+ }
+ }
+ }
+ if(len2 > 0)
+ buf = realloc(buf, p - buf);
+ iconv_close(cd);
+ return((wchar_t *)buf);
+}
+
+wchar_t *icsmbstowcs(char *mbs, char *charset, wchar_t *def)
+{
+ static wchar_t *buf = NULL;
+
+ if(buf != NULL)
+ free(buf);
+ if((buf = icmbstowcs(mbs, charset)) == NULL)
+ return(def);
+ return(buf);
+}
+
+char *icwcstombs(wchar_t *wcs, char *charset)
+{
+ int ret;
+ char *buf;
+ char *p, *p2;
+ size_t len1, len2, bufsize, data;
+ iconv_t cd;
+
+ len1 = sizeof(wchar_t) * (wcslen(wcs) + 1);
+ bufsize = len2 = len1;
+ if((buf = malloc(bufsize)) == NULL)
+ {
+ return(NULL);
+ }
+ if(charset == NULL)
+ charset = nl_langinfo(CODESET);
+ if((cd = iconv_open(charset, "wchar_t")) == (iconv_t)-1)
+ {
+ free(buf);
+ return(NULL);
+ }
+ p = buf;
+ while(len1 > 0)
+ {
+ ret = iconv(cd, (char **)&wcs, &len1, &p, &len2);
+ if(ret < 0)
+ {
+ if(errno == E2BIG)
+ {
+ data = p - buf;
+ len2 += 128;
+ bufsize += 128;
+ if((p2 = realloc(buf, bufsize)) == NULL)
+ {
+ free(buf);
+ return(NULL);
+ }
+ buf = p2;
+ p = buf + data;
+ } else {
+ free(buf);
+ return(NULL);
+ }
+ }
+ }
+ if(len2 > 0)
+ buf = realloc(buf, p - buf);
+ iconv_close(cd);
+ return(buf);
+}
+
+char *icswcstombs(wchar_t *wcs, char *charset, char *def)
+{
+ static char *buf = NULL;
+
+ if(buf != NULL)
+ free(buf);
+ if((buf = icwcstombs(wcs, charset)) == NULL)
+ return(def);
+ return(buf);
+}
+
+wchar_t *wcstolower(wchar_t *wcs)
+{
+ wchar_t *p;
+
+ for(p = wcs; *p != L'\0'; p++)
+ *p = towlower(*p);
+ return(wcs);
+}
+
+void _sizebuf(void **buf, size_t *bufsize, size_t reqsize, size_t elsize, int algo)
+{
+ if(*bufsize >= reqsize)
+ return;
+ switch(algo)
+ {
+ case 0:
+ *buf = srealloc(*buf, elsize * ((*bufsize) = reqsize));
+ break;
+ case 1:
+ if(*bufsize == 0)
+ *bufsize = 1;
+ while(*bufsize < reqsize)
+ *bufsize *= 2;
+ *buf = srealloc(*buf, elsize * (*bufsize));
+ break;
+ }
+}
--- /dev/null
+/ChangeLog/1.1/Fri Aug 13 18:08:40 2004//
+/LINGUAS/1.2/Fri Aug 13 23:00:24 2004//
+/Makevars/1.1/Fri Aug 13 18:08:41 2004//
+/POTFILES.in/1.2/Sat Jan 1 17:37:01 2005//
+/sv.po/1.12/Fri Oct 14 23:22:19 2005//
+D
--- /dev/null
+doldaconnect/po
--- /dev/null
+:ext:dolda2000@cvs.sourceforge.net:/cvsroot/doldaconnect
--- /dev/null
+2004-08-13 gettextize <bug-gnu-gettext@gnu.org>
+
+ * Makefile.in.in: New file, from gettext-0.12.1.
+ * boldquot.sed: New file, from gettext-0.12.1.
+ * en@boldquot.header: New file, from gettext-0.12.1.
+ * en@quot.header: New file, from gettext-0.12.1.
+ * insert-header.sin: New file, from gettext-0.12.1.
+ * quot.sed: New file, from gettext-0.12.1.
+ * remove-potcdate.sin: New file, from gettext-0.12.1.
+ * Rules-quot: New file, from gettext-0.12.1.
+
--- /dev/null
+# Makefile variables for PO directory in any package using GNU gettext.
+
+# Usually the message domain is the same as the package name.
+DOMAIN = $(PACKAGE)
+
+# These two variables depend on the location of this directory.
+subdir = po
+top_builddir = ..
+
+# These options get passed to xgettext.
+XGETTEXT_OPTIONS = --keyword=_ --keyword=N_
+
+# This is the copyright holder that gets inserted into the header of the
+# $(DOMAIN).pot file. Set this to the copyright holder of the surrounding
+# package. (Note that the msgstr strings, extracted from the package's
+# sources, belong to the copyright holder of the package.) Translators are
+# expected to transfer the copyright for their translations to this person
+# or entity, or to disclaim their copyright. The empty string stands for
+# the public domain; in this case the translators are expected to disclaim
+# their copyright.
+COPYRIGHT_HOLDER = Fredrik Tolf
+
+# This is the email address or URL to which the translators shall report
+# bugs in the untranslated strings:
+# - Strings which are not entire sentences, see the maintainer guidelines
+# in the GNU gettext documentation, section 'Preparing Strings'.
+# - Strings which use unclear terms or require additional context to be
+# understood.
+# - Strings which make invalid assumptions about notation of date, time or
+# money.
+# - Pluralisation problems.
+# - Incorrect English spelling.
+# - Incorrect formatting.
+# It can be your email address, or a mailing list address where translators
+# can write to without being subscribed, or the URL of a web page through
+# which the translators can contact you.
+MSGID_BUGS_ADDRESS = fredrik@dolda2000.com
+
+# This is the list of locale categories, beyond LC_MESSAGES, for which the
+# message catalogs shall be used. It is usually empty.
+EXTRA_LOCALE_CATEGORIES =
--- /dev/null
+clients/gtk2/main.c
+clients/gtk2/inpdialog.gtk
+clients/gtk2/mainwnd.gtk
+clients/gtk2/pref.gtk
+clients/gnome-trans-applet/dolcon-trans-applet.c
+clients/gnome-trans-applet/conduit.c
+clients/gnome-trans-applet/conduit-pipe.c
--- /dev/null
+# Swedish translations for doldaconnect package
+# Svenska översättningar för paket doldaconnect.
+# Copyright (C) 2004 Fredrik Tolf
+# This file is distributed under the same license as the doldaconnect package.
+# Fredrik Tolf <fredrik@dolda2000.com>, 2004.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: doldaconnect 0.1.1\n"
+"Report-Msgid-Bugs-To: fredrik@dolda2000.com\n"
+"POT-Creation-Date: 2005-07-09 05:31+0200\n"
+"PO-Revision-Date: 2005-08-15 03:48+0200\n"
+"Last-Translator: Fredrik Tolf <fredrik@dolda2000.com>\n"
+"Language-Team: Swedish <sv@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: clients/gtk2/main.c:215 clients/gtk2/main.c:238 clients/gtk2/main.c:270
+#: clients/gtk2/main.c:304 clients/gtk2/main.c:400 clients/gtk2/main.c:419
+msgid "Unknown"
+msgstr "Okänt"
+
+#: clients/gtk2/main.c:319
+msgid "Not found"
+msgstr "Kunde inte hittas"
+
+#: clients/gtk2/main.c:321
+msgid "No slots"
+msgstr "Inga slots lediga"
+
+#: clients/gtk2/main.c:322
+msgid "%H:%M:%S"
+msgstr "%H:%M:%S"
+
+#: clients/gtk2/main.c:323
+#, c-format
+msgid "%s (reported at %s)"
+msgstr "%s (rapporterades kl. %s)"
+
+#: clients/gtk2/main.c:484 clients/gtk2/main.c:1807
+msgid "Disconnected"
+msgstr "Frånkopplad"
+
+#: clients/gtk2/main.c:607
+msgid "Could not get your home directory!"
+msgstr "Kunde inte hitta din hemkatalog!"
+
+#: clients/gtk2/main.c:614
+#, c-format
+msgid "Could not open configuration file for writing: %s"
+msgstr "Kunde inte skriva till konfigurationsfilen: %s"
+
+#: clients/gtk2/main.c:655 clients/gtk2/main.c:659
+msgid "Login"
+msgstr "Logga in"
+
+#: clients/gtk2/main.c:695
+msgid "Could not negotiate an acceptable authentication mechanism"
+msgstr "Kunde inte förhandla fram en acceptabel autentiseringsmekanism"
+
+#: clients/gtk2/main.c:700
+msgid "The server has encountered an error"
+msgstr "Servern har råkat ut för ett fel"
+
+#: clients/gtk2/main.c:705
+msgid "Internal client error"
+msgstr "Internt fel i klienten"
+
+#: clients/gtk2/main.c:714
+msgid "Login attempt failed!"
+msgstr "Inloggningsförsöket misslyckades!"
+
+#: clients/gtk2/main.c:760
+msgid "Discrete sizes"
+msgstr "Enskilda storlekar"
+
+#: clients/gtk2/main.c:881
+msgid "Connected"
+msgstr "Ansluten"
+
+#: clients/gtk2/main.c:884
+msgid "The server refused the connection"
+msgstr "Servern vägrade förbindelsen"
+
+#: clients/gtk2/main.c:1083
+msgid "The server has closed the connection"
+msgstr "Servern har stängt förbindelsen"
+
+#: clients/gtk2/main.c:1085
+#, c-format
+msgid ""
+"The connection to the server failed:\n"
+"\n"
+"%s"
+msgstr ""
+"Anslutning till servern misslyckades:\n"
+"\n"
+"%s"
+
+#: clients/gtk2/main.c:1105
+msgid "Preferences"
+msgstr "Inställningar"
+
+#: clients/gtk2/main.c:1131
+#, c-format
+msgid ""
+"Could not connect:\n"
+"\n"
+"%s"
+msgstr ""
+"Kunde inte ansluta:\n"
+"\n"
+"%s"
+
+#: clients/gtk2/main.c:1138
+#: clients/gnome-trans-applet/dolcon-trans-applet.c:84
+msgid "Connecting..."
+msgstr "Ansluter..."
+
+#: clients/gtk2/main.c:1147
+msgid "Connect"
+msgstr "Anslut"
+
+#: clients/gtk2/main.c:1147
+msgid "Server address:"
+msgstr "Serverns adress:"
+
+#: clients/gtk2/main.c:1160 clients/gtk2/main.c:1181 clients/gtk2/main.c:1360
+#: clients/gtk2/main.c:1402 clients/gtk2/main.c:1464 clients/gtk2/main.c:1552
+#: clients/gtk2/main.c:1647
+msgid "Not connected to DC server"
+msgstr "Ej ansluten till DC-servern"
+
+#: clients/gtk2/main.c:1167 clients/gtk2/main.c:1195 clients/gtk2/main.c:1373
+#: clients/gtk2/main.c:1420 clients/gtk2/main.c:1477 clients/gtk2/main.c:1570
+#: clients/gtk2/main.c:1625 clients/gtk2/main.c:1687
+msgid "You do not have permission to do that"
+msgstr "Du har inte tillstånd att göra det"
+
+#: clients/gtk2/main.c:1197 clients/gtk2/main.c:1422
+msgid "The server could not parse that address"
+msgstr "Servern kunde inte tolka den adressen"
+
+#: clients/gtk2/main.c:1225
+#, c-format
+msgid "Could not read from public hub listing process: %s"
+msgstr "Kunde inte läsa från processen som hämtar den publika hublistan: %s"
+
+#: clients/gtk2/main.c:1262
+#, c-format
+msgid "Could not decode hublist - aborting at this point: %s"
+msgstr "Kunde inte dekoda hublistan - stannar här: %s"
+
+#: clients/gtk2/main.c:1365 clients/gtk2/main.c:1469
+msgid "No hub selected"
+msgstr "Ingen hub vald"
+
+#: clients/gtk2/main.c:1479
+msgid ""
+"This hub could not support all the types of characters in your chat message"
+msgstr "Den här hubben klarar inte av alla sorters bokstäver i ditt meddelande"
+
+#: clients/gtk2/main.c:1481
+msgid "This hub does not support chatting"
+msgstr "Den här hubben klarar inte av att chatta"
+
+#: clients/gtk2/main.c:1483
+#, c-format
+msgid "An error occurred while trying to chat (%i)"
+msgstr "Ett fel uppstod under försöket att chatta (%i)"
+
+#: clients/gtk2/main.c:1560
+msgid "Please enter a search expression before searching"
+msgstr "Skriv in ett sökuttryck först"
+
+#: clients/gtk2/main.c:1568
+msgid "Could not find any hubs to search on"
+msgstr "Kunde inte hitta någon hub att söka på"
+
+#: clients/gtk2/main.c:1572
+msgid "The server could not parse your search expression"
+msgstr "Servern kunde inte tolka ditt sökuttryck"
+
+#: clients/gtk2/main.c:1574
+#, c-format
+msgid "An error occurred while trying to search (%i)"
+msgstr "Ett fel uppstod under försöket att söka (%i)"
+
+#: clients/gtk2/main.c:1627
+#, c-format
+msgid "An error occurred while trying to cancel (%i)"
+msgstr "Ett fel uppstod under försöket att avbryta (%i)"
+
+#: clients/gtk2/main.c:1689
+#, c-format
+msgid "An error occurred while trying to queue the download (%i)"
+msgstr "Ett fel uppstod under försöket att lägga till nerladdningen (%i)"
+
+#: clients/gtk2/main.c:1720 clients/gtk2/mainwnd.gtk:398
+#, c-format
+msgid "Ready to search"
+msgstr "Redo att söka"
+
+#: clients/gtk2/main.c:1722
+#, c-format
+msgid "Search scheduled and will be submitted in %i seconds"
+msgstr "Sökningen är schemalagd och kommer genomföras om %i sekunder"
+
+#: clients/gtk2/inpdialog.gtk:16
+msgid " "
+msgstr " "
+
+#: clients/gtk2/mainwnd.gtk:45
+msgid "_Main"
+msgstr "Huvud_meny"
+
+#: clients/gtk2/mainwnd.gtk:50
+msgid "_Connect"
+msgstr "_Anslut"
+
+#: clients/gtk2/mainwnd.gtk:57
+msgid "_Disconnect"
+msgstr "_Koppla från"
+
+#: clients/gtk2/mainwnd.gtk:69
+msgid "_Shut down daemon"
+msgstr "_Stäng av demonen"
+
+#: clients/gtk2/mainwnd.gtk:84
+msgid "Op_tions"
+msgstr "Alternati_v"
+
+#: clients/gtk2/mainwnd.gtk:112
+msgid "Connected hu_bs"
+msgstr "_Anslutna hubbar"
+
+#: clients/gtk2/mainwnd.gtk:124 clients/gtk2/mainwnd.gtk:260
+msgid "Hub name"
+msgstr "Hubnamn"
+
+#: clients/gtk2/mainwnd.gtk:138 clients/gtk2/mainwnd.gtk:175
+msgid "# users"
+msgstr "Antal användare"
+
+#: clients/gtk2/mainwnd.gtk:149
+msgid "D_isconnect"
+msgstr "_Koppla från"
+
+#: clients/gtk2/mainwnd.gtk:160
+msgid "_Public hub list"
+msgstr "_Publik hublista"
+
+#: clients/gtk2/mainwnd.gtk:180
+msgid "Name"
+msgstr "Namn"
+
+#: clients/gtk2/mainwnd.gtk:185
+msgid "Description"
+msgstr "Beskrivning"
+
+#: clients/gtk2/mainwnd.gtk:198
+msgid "_Filter:"
+msgstr "_Filter:"
+
+#: clients/gtk2/mainwnd.gtk:209
+msgid "_Get public hub list"
+msgstr "Hämta _publik hublista"
+
+#: clients/gtk2/mainwnd.gtk:214
+msgid "_Address:"
+msgstr "_Adress:"
+
+#: clients/gtk2/mainwnd.gtk:225
+msgid "C_onnect"
+msgstr "_Anslut"
+
+#: clients/gtk2/mainwnd.gtk:239
+msgid "_Hub connections"
+msgstr "_Hubanslutningar"
+
+#: clients/gtk2/mainwnd.gtk:247
+msgid "Hu_bs"
+msgstr "Hu_bbar"
+
+#: clients/gtk2/mainwnd.gtk:298
+msgid "Chat st_ring:"
+msgstr "Chatm_eddelande:"
+
+#: clients/gtk2/mainwnd.gtk:309
+msgid "S_end"
+msgstr "S_kicka"
+
+#: clients/gtk2/mainwnd.gtk:321
+msgid "_Chat"
+msgstr "_Chat"
+
+#: clients/gtk2/mainwnd.gtk:328
+msgid "S_imple search:"
+msgstr "_Enkel sökning:"
+
+#: clients/gtk2/mainwnd.gtk:340
+msgid "S_earch"
+msgstr "S_ök"
+
+#: clients/gtk2/mainwnd.gtk:346
+msgid "C_ancel"
+msgstr "_Avbryt"
+
+#: clients/gtk2/mainwnd.gtk:357
+msgid "Displa_y results with free slots only"
+msgstr "_Visa endast resultat med fria slots"
+
+#: clients/gtk2/mainwnd.gtk:363
+msgid "Ad_vanced"
+msgstr "A_vancerat"
+
+#: clients/gtk2/mainwnd.gtk:368
+msgid "C_omplete search expression:"
+msgstr "_Komplett sökuttryck:"
+
+#: clients/gtk2/mainwnd.gtk:380
+msgid "Filter ar_gument:"
+msgstr "Filterar_gument:"
+
+#: clients/gtk2/mainwnd.gtk:407
+msgid "Search _results:"
+msgstr "Sök_resultat:"
+
+#: clients/gtk2/mainwnd.gtk:422
+msgid "#"
+msgstr "#"
+
+#: clients/gtk2/mainwnd.gtk:432
+msgid "Peer name"
+msgstr "Användare"
+
+#: clients/gtk2/mainwnd.gtk:437
+msgid "File name"
+msgstr "Filnamn"
+
+#: clients/gtk2/mainwnd.gtk:444 clients/gtk2/mainwnd.gtk:520
+#: clients/gtk2/mainwnd.gtk:590
+msgid "Size"
+msgstr "Storlek"
+
+#: clients/gtk2/mainwnd.gtk:454
+msgid "Slots"
+msgstr "Slots"
+
+#: clients/gtk2/mainwnd.gtk:460
+msgid "Known speed"
+msgstr "Känd hastighet"
+
+#: clients/gtk2/mainwnd.gtk:470
+msgid "Rsp. time"
+msgstr "Svarstid"
+
+#: clients/gtk2/mainwnd.gtk:482
+msgid "_Search"
+msgstr "_Sök"
+
+#: clients/gtk2/mainwnd.gtk:492
+msgid "_List of downloads:"
+msgstr "_Lista över nerladdningar:"
+
+#: clients/gtk2/mainwnd.gtk:504 clients/gtk2/mainwnd.gtk:574
+msgid "User Name"
+msgstr "Användarnamn"
+
+#: clients/gtk2/mainwnd.gtk:516 clients/gtk2/mainwnd.gtk:586
+msgid "File Name"
+msgstr "Filenamn"
+
+#: clients/gtk2/mainwnd.gtk:529 clients/gtk2/mainwnd.gtk:599
+msgid "Position"
+msgstr "Position"
+
+#: clients/gtk2/mainwnd.gtk:542
+msgid "Error"
+msgstr "Felmeddelande"
+
+#: clients/gtk2/mainwnd.gtk:557
+msgid "_Downloads"
+msgstr "_Nerladdningar"
+
+#: clients/gtk2/mainwnd.gtk:562
+msgid "_List of uploads:"
+msgstr "_Lista över uppladdningar:"
+
+#: clients/gtk2/mainwnd.gtk:618
+msgid "_Uploads"
+msgstr "_Uppladdningar"
+
+#: clients/gtk2/pref.gtk:12
+msgid "_Public hub list URL:"
+msgstr "_URL för publik hublista:"
+
+#: clients/gtk2/pref.gtk:22
+msgid "_Dolda connect user name:"
+msgstr "Användarnamn för _Dolda Connect:"
+
+#: clients/gtk2/pref.gtk:32
+msgid "Dolda Connect _server:"
+msgstr "Dolda Connect-_server:"
+
+#: clients/gtk2/pref.gtk:42
+msgid "Connect _automatically on startup"
+msgstr "Anslut au_tomatiskt vid uppstart"
+
+#: clients/gnome-trans-applet/dolcon-trans-applet.c:57
+msgid "Calculating remaining time..."
+msgstr "Beräknar återstående tid..."
+
+#: clients/gnome-trans-applet/dolcon-trans-applet.c:62
+msgid "Time left: Infinite (Transfer is standing still)"
+msgstr "Tid kvar: Oändlig (Överföringen står still)"
+
+#: clients/gnome-trans-applet/dolcon-trans-applet.c:66
+#, c-format
+msgid "Time left: %i:%02i"
+msgstr "Tid kvar: %i:%02i"
+
+#: clients/gnome-trans-applet/dolcon-trans-applet.c:80
+msgid "Not connected"
+msgstr "Ej ansluten"
+
+#: clients/gnome-trans-applet/dolcon-trans-applet.c:92
+msgid "No transfers to display"
+msgstr "Det finns inga överföringar att visa"
+
+#: clients/gnome-trans-applet/dolcon-trans-applet.c:96
+msgid "No transfer selected"
+msgstr "Ingen överföring vald"
+
+#: clients/gnome-trans-applet/dolcon-trans-applet.c:105
+msgid "Initializing"
+msgstr "Förbereder"