[
  {
    "path": ".github/CODE-OF-CONDUCT.md",
    "content": "Contributor Code of Conduct\n===========================\n\nAs contributors and maintainers of this project, and in the interest of\nfostering an open and welcoming community, we pledge to respect all\npeople who contribute through reporting issues, posting feature\nrequests, updating documentation, submitting pull requests or patches,\nand other activities.\n\nWe are committed to making participation in this project a\nharassment-free experience for everyone, regardless of level of\nexperience, gender, gender identity and expression, sexual orientation,\ndisability, personal appearance, body size, race, ethnicity, age,\nreligion, or nationality.\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery\n* Personal attacks\n* Trolling or insulting/derogatory comments\n* Public or private harassment\n* Publishing other's private information, such as physical or electronic\n  addresses, without explicit permission\n* Other unethical or unprofessional conduct.\n\nProject maintainers have the right and responsibility to remove, edit,\nor reject comments, commits, code, wiki edits, issues, and other\ncontributions that are not aligned to this Code of Conduct. By adopting\nthis Code of Conduct, project maintainers commit themselves to fairly\nand consistently applying these principles to every aspect of managing\nthis project. Project maintainers who do not follow or enforce the Code\nof Conduct may be permanently removed from the project team.\n\nThis code of conduct applies both within project spaces and in public\nspaces when an individual is representing the project or its community.\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may\nbe reported by opening an issue or contacting one or more of the project\nmaintainers.\n\nThis Code of Conduct is adapted from the [Contributor Covenant][1],\n[version 1.2.0][2].\n\n[1]: http://contributor-covenant.org\n[2]: http://contributor-covenant.org/version/1/2/0/\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "Contributing to uftpd\n=====================\n\nWe welcome any and all help in the form of bug reports, fixes, patches\nfor new features -- *preferably as GitHub pull requests*.  Other methods\nare of course also possible: emailing the maintainer a patch or even a\nraw file, or simply emailing a feature request or an alert of a problem.\nFor email questions/requests/alerts there is always the risk of memory\nexhaustion on the part of the maintainer(s), so use GitHub :)\n\nIf you are unsure of what to do, or how to implement an idea or bugfix,\nopen an issue with `\"[RFC: Unsure if this is a bug ... ?\"`, or similar,\nso we can discuss it.  Talking about the code first is the best way to\nget started before submitting a pull request.\n\nEither way, when sending an email, patch, or pull request, start by\nstating the version the change is made against, what it does, and why.\n\nPlease take care to ensure you follow the project coding style and the\ncommit message format.  If you follow these recommendations you help\nthe maintainer(s) and make it easier for them to include your code.\n\n\nCoding Style\n------------\n\n> **Tip:** Always submit code that follows the style of surrounding code!\n\nFirst of all, lines are allowed to be longer than 72 characters these\ndays.  In fact, there exist no enforced maximum, but keeping it around\n100 chars is OK.\n\nThe coding style itself is strictly Linux [KNF][].\n\n\nCommit Messages\n---------------\n\nCommit messages exist to track *why* a change was made.  Try to be as\nclear and concise as possible in your commit messages, and always, be\nproud of your work and set up a proper GIT identity for your commits:\n\n    git config --global user.name \"Jane Doe\"\n    git config --global user.email jane.doe@example.com\n\nExample commit message from the [Pro Git][gitbook] online book, notice\nhow `git commit -s` is used to automatically add a `Signed-off-by`:\n\n    Brief, but clear and concise summary of changes\n    \n    More detailed explanatory text, if necessary.  Wrap it to about 72\n    characters or so.  In some contexts, the first line is treated as\n    the subject of an email and the rest of the text as the body.  The\n    blank line separating the ummary from the body is critical (unless\n    you omit the body entirely); tools like rebase can get confused if\n    you run the two together.\n    \n    Further paragraphs come after blank lines.\n    \n     - Bullet points are okay, too\n    \n     - Typically a hyphen or asterisk is used for the bullet, preceded\n       by a single space, with blank lines in between, but conventions\n       vary here\n    \n    Signed-off-by: Jane Doe <jane.doe@example.com>\n\n\n[github]:   https://github.com/troglobit/uftpd/\n[KNF]:      https://en.wikipedia.org/wiki/Kernel_Normal_Form\n[gitbook]:  https://git-scm.com/book/ch5-2.html\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nuftpd is a small project, as such we have no possibility to support older versions.\nThe only supported version is the latest released on GitHub:\n\n<https://github.com/troglobit/uftpd/releases>\n\n## Reporting a Vulnerability\n\nContact the project's main author and owner to report and discuss vulnerabilities.\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Bob the Builder\n\n# Run on all branches, including all pull requests, except the 'dev'\n# branch since that's where we run Coverity Scan (limited tokens/day)\non:\n  push:\n    branches:\n      - '**'\n      - '!dev'\n  pull_request:\n    branches:\n      - '**'\n\njobs:\n  build:\n    # Verify we can build on latest Ubuntu with both gcc and clang\n    name: ${{ matrix.compiler }}\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        compiler: [gcc, clang]\n      fail-fast: false\n    env:\n      MAKEFLAGS: -j3\n      CC: ${{ matrix.compiler }}\n    steps:\n      - name: Install dependencies\n        run: |\n          curl -sS https://deb.troglobit.com/pubkey.gpg | sudo apt-key add -\n          echo \"deb [arch=amd64] https://deb.troglobit.com/debian stable main\" \\\n               | sudo tee /etc/apt/sources.list.d/troglobit.list\n          sudo apt-get -y update\n          sudo apt-get -y install tree ftp tnftp tftp-hpa libuev-dev libite-dev\n      - uses: actions/checkout@v2\n      - name: Configure\n        run: |\n          ./autogen.sh\n          ./configure --prefix=\n      - name: Build\n        run: |\n          make V=1\n      - name: Install\n        run: |\n          DESTDIR=~/tmp make install-strip\n          tree ~/tmp\n          ldd ~/tmp/sbin/uftpd\n          size ~/tmp/sbin/uftpd\n          ~/tmp/sbin/uftpd -h\n      - name: Test\n        run: |\n          ulimit -n 1024\n          # Tests must currently not run in parallel\n          LD_LIBRARY_PATH=/tmp/lib make -j1 check\n"
  },
  {
    "path": ".github/workflows/coverity.yml",
    "content": "name: Coverity Scan\n\non:\n  push:\n    branches:\n      - 'dev'\n\nenv:\n  PROJECT_NAME: uftpd\n  CONTACT_EMAIL: troglobit@gmail.com\n  COVERITY_NAME: troglobit-uftpd\n  COVERITY_PROJ: troglobit%2Fuftpd\n\njobs:\n  coverity:\n    runs-on: ubuntu-latest\n    env:\n      MAKEFLAGS: -j3\n    steps:\n      - name: Fetch latest Coverity Scan MD5\n        id: var\n        env:\n          TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}\n        run: |\n          wget -q https://scan.coverity.com/download/cxx/linux64         \\\n               --post-data \"token=$TOKEN&project=${COVERITY_PROJ}&md5=1\" \\\n               -O coverity-latest.tar.gz.md5\n          export MD5=$(cat coverity-latest.tar.gz.md5)\n          echo \"Got MD5 $MD5\"\n          echo ::set-output name=md5::${MD5}\n      - uses: actions/cache@v2\n        id: cache\n        with:\n          path: coverity-latest.tar.gz\n          key: ${{ runner.os }}-coverity-${{ steps.var.outputs.md5 }}\n          restore-keys: |\n            ${{ runner.os }}-coverity-${{ steps.var.outputs.md5 }}\n            ${{ runner.os }}-coverity-\n            ${{ runner.os }}-coverity\n      - name: Download Coverity Scan\n        env:\n          TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}\n        run: |\n          if [ ! -f coverity-latest.tar.gz ]; then\n            wget -q https://scan.coverity.com/download/cxx/linux64   \\\n                 --post-data \"token=$TOKEN&project=${COVERITY_PROJ}\" \\\n                 -O coverity-latest.tar.gz\n          else\n            echo \"Latest Coverity Scan available from cache :-)\"\n            md5sum coverity-latest.tar.gz\n          fi\n          mkdir coverity\n          tar xzf coverity-latest.tar.gz --strip 1 -C coverity\n      - name: Install dependencies\n        run: |\n          curl -sS https://deb.troglobit.com/pubkey.gpg | sudo apt-key add -\n          echo \"deb [arch=amd64] https://deb.troglobit.com/debian stable main\" \\\n               | sudo tee /etc/apt/sources.list.d/troglobit.list\n          sudo apt-get -y update\n          sudo apt-get -y install pkg-config libuev-dev libite-dev\n      - uses: actions/checkout@v2\n      - name: Configure\n        run: |\n          ./autogen.sh\n          ./configure\n      - name: Build\n        run: |\n          export PATH=`pwd`/coverity/bin:$PATH\n          cov-build --dir cov-int make\n      - name: Submit results to Coverity Scan\n        env:\n          TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}\n        run: |\n          tar czvf ${PROJECT_NAME}.tgz cov-int\n          curl \\\n            --form project=${COVERITY_NAME} \\\n            --form token=$TOKEN \\\n            --form email=${CONTACT_EMAIL} \\\n            --form file=@${PROJECT_NAME}.tgz \\\n            --form version=trunk \\\n            --form description=\"${PROJECT_NAME} $(git rev-parse HEAD)\" \\\n            https://scan.coverity.com/builds?project=${COVERITY_PROJ}\n      - name: Upload build.log\n        uses: actions/upload-artifact@v2\n        with:\n          name: coverity-build.log\n          path: cov-int/build-log.txt\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release General\n\non:\n  push:\n    tags:\n      - 'v[0-9]+.[0-9]+*'\n\njobs:\n  release:\n    name: Create GitHub release\n    runs-on: ubuntu-latest\n    if: startsWith(github.ref, 'refs/tags/')\n    outputs:\n      upload_url: ${{ steps.create_release.outputs.upload_url }}\n      release_id: ${{ steps.create_release.outputs.id }}\n    steps:\n      - uses: actions/checkout@v2\n      - name: Extract ChangeLog entry ...\n        # Hack to extract latest entry for body_path below\n        run: |\n          awk '/-----*/{if (x == 1) exit; x=1;next}x' ChangeLog.md \\\n              |head -n -1 > release.md\n          cat release.md\n      - name: Create release ...\n        id: create_release\n        uses: actions/create-release@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          tag_name: ${{ github.ref }}\n          release_name: uftpd ${{ github.ref }}\n          body_path: release.md\n          draft: false\n          prerelease: false\n  tarball:\n    name: Build and upload release tarball\n    needs: release\n    if: startsWith(github.ref, 'refs/tags/')\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - name: Installing dependencies ...\n        run: |\n          curl -sS https://deb.troglobit.com/pubkey.gpg | sudo apt-key add -\n          echo \"deb [arch=amd64] https://deb.troglobit.com/debian stable main\" \\\n               | sudo tee /etc/apt/sources.list.d/troglobit.list\n          sudo apt-get -y update\n          sudo apt-get -y install ftp tnftp tftp-hpa libuev-dev libite-dev\n      - name: Creating Makefiles ...\n        run: |\n          ./autogen.sh\n          ./configure\n      - name: Build release ...\n        run: |\n          make release\n          ls -lF ../\n          mkdir -p artifacts/\n          mv ../*.tar.* artifacts/\n      - name: Upload release artifacts ...\n        uses: skx/github-action-publish-binaries@release-0.15\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          releaseId: ${{ needs.release.outputs.release_id }}\n          args: artifacts/*\n"
  },
  {
    "path": ".gitignore",
    "content": "*~\n*.o\n*.d\n*.map\n.deps\n.gdb_history\n.libs\n.stamp\n.unpacked\nMakefile\nMakefile.in\naclocal.m4\nar-lib\nautom4te.cache/*\naux/\ncompile\nconfig.*\nconfigure\ndepcomp\ninstall-sh\nlibtool\nltmain.sh\nmissing\nstamp-h1\nTAGS\nID\nGPATH\nGRTAGS\nGSYMS\nGTAGS\n"
  },
  {
    "path": "ChangeLog.md",
    "content": "Change Log\n==========\n\nAll notable changes to the project are documented in this file.\n\n\n[v2.15][] - 2021-12-20\n----------------------\n\n### Changes\n- CI status badge now to points to GitHub Actions, no more Travis-CI\n- Silence some developer debug messages\n- Always skip `.` and `..` in FTP listings\n- Internal refactoring and code cleanup\n\n### Fixes\n- Fix mdoc warning, found by lintian\n- Fix regression introduced in v2.14, server directory name shown in FTP\n  listings instead of `.`, e.g. `MLST .` showed the directory name \n- Fix #36: for real this time, now also with a test case to verify\n- Fix #38: duplicate entries in FTP listings, regression in v2.14.\n  Caused by (initially unintentional) removal of sorted listings, where\n  directories prior to v2.14 were listed first.  This change, albeit an\n  accident, actually helped clean up the code base and speed up replies\n\n\n[v2.14][] - 2021-12-11\n----------------------\n\n### Changes\n- Add support for `-o pasv_addr=ADDR` command line argument to override\n- Add support for `-p PIDFILE` command line argument\n  the address passed to the client in passive mode, useful for some\n  types of NAT setup\n- Add support for new libite (-lite) library header namespace\n- Restored .tar.gz release archives\n- Replaced Travis-CI with GitHub Actions\n\n### Fixes\n- Issue #36: MLST command without any argument blocks\n- Fix memory leak in MLST/MLSD, only affects no-MMU systems where the\n  kernel cannot free memory of processes on exit\n\n\n[v2.13][] - 2020-06-30\n----------------------\n\n### Changes\n- Unit test framework in place, with regression test for issue #31\n\n### Fixes\n- Issue #31: Socket leak in daemon accept() handling causing \"Too many\n  open files\".  Effectively causing denial of service\n- Minor memory leak fixed, only allocated once at startup.  Affects\n  only non-MMU systems\n\n\n[v2.12][] - 2020-05-25\n----------------------\n\n### Changes\n- Use common log message format and log level when user enters an\n  invalid path.  This unfortunately affects changes introduced in\n  [v2.11][] to increase logging at default log level.\n\n### Fixes\n- Issue #30: When entering an invalid directory with the FTP command CWD,\n  a NULL ptr was deref. in a DBG() message even though the log level is\n  set to a value lower than `LOG_DEBUG`.  This caused uftpd to crash\n  and cause denial of service.  Depending on the init/inetd system used\n  this could be permanent.\n\n\n[v2.11][] - 2020-01-05\n----------------------\n\n### Changes\n- Increased logging at default log level.  Now users logging in,\n  downloading, uploading, directory creation/removal is logged by\n  default.  Start with `-l error` to silence uftpd again\n\n### Fixes\n- Fix buffer overflow in FTP PORT parser, reported by Aaron Esau\n- Fix TFTP/FTP directory traversal regression , reported by Aaron Esau\n- Fix potential DOS through non-busy loop and segfault, by Aaron Esau\n- Fix potential segfault through empty FTP password, by Aaron Esau\n- Fix potential segfault through FTP PORT command, by Aaron Esau\n\n\n[v2.10][] - 2019-08-15\n----------------------\n\n### Changes\n- Issue #25: Add support for TFTP write support (WRQ)\n- Slightly improved debug messages.\n\n### Fixes\n- Minor fix to TFTP error codes, only use standardized codes, and\n  code 0 + custom error message for everything else\n\n\n[v2.9][] - 2019-07-29\n---------------------\n\n### Changes\n- Reduced log level for \"Invalid path\" and \"Failed realpath()\" syslog\n  messages.  Only relevant when debugging.  For use on the Internet it\n  will otherwise cause an excessive amount of logs due to GXHLGSL.txt\n- Debian packaging fixes and updates:\n  - Reverts `-o writable`, due to fixing issue #22\n  - Fixes failing `dpkg -P uftpd` due to bug in postrm script\n\n### Fixes\n- Issue #21: Check for `pkg-config` before looking for deps.\n- Issue #22: Check FTP root security *after* having dropped privs.\n  This means no longer having to run with `-o writable` by default\n- Issue #23: FTP command `CWD /` does not work, affects all clients.\n  This is a regression introduced in v2.8 while fixing #18\n\n\n[v2.8][] - 2019-05-28\n---------------------\n\n### Changes\n- The FTP command processor now always converts all inbound commands\n  to uppercase to handle clients sending commands in lowercase\n- Any arguments to the FTP `LIST` command are now ignored\n- Improved user feedback on bad FTP root error message\n\n### Fixes\n- Fix #18: KDE Dolphin, FTP client interop problems.\n\n\n[v2.7][] - 2019-03-03\n---------------------\n\n### Changes\n- Documentation updates, commands added in v2.5 and `writable` opt\n- Require libuEv v2.2, or later\n\n### Fixes\n- Issue #17: Issues with relative FTP root when running unprivileged\n\n\n[v2.6][] - 2018-07-03\n---------------------\n\nBug fix release.\n\n### Fixes\n- Issue #16: 100% CPU when client session exits\n- Add missing include file for `gettimeofday()`\n- Flush stdout logging when running in the foreground\n\n\n[v2.5][] - 2018-06-06\n---------------------\n\nThe VLC Android app release.\n\n### Changes\n- Support for `ABOR` FTP command, issue #14\n- Support for `REST` FTP command, issue #13\n- Support for `EPSV` and `EPSV ALL` FTP commands, issue #11\n- Basic support for `MLST` and `MLSD` FTP commands to provide support\n  for the VLC android app., issue #9 and #12\n- Add `OPTS MLST <ARG>` to let client manage order of facts listed\n  in `MLST` and `MLSD` calls\n- Add `CDUP` FTP convenience command, alias to `CWD ..`\n- Add `DELE` FTP command to delete files\n- Add `MKD` and `RMD` FTP commands to create and remove directories\n- Refactor `LIST`, `RETR`, `STOR` and `PASV` FTP commands for speed\n\n### Fixes\n- Really fix 100% CPU problem, issue #9.  Multiple failure modes in\n  libuEv and improper handling of `waitpid()` in event loop callback\n- Use libuEv callback also for `PASV` FTP connections\n- Fix `NLST` + `LIST` line endings, must be \\r\\n\n\n\n[v2.4][] - 2017-09-03\n---------------------\n\nBug fix release.\n\n### Changes\n- Handle non-chrooted use-cases better, ensure CWD starts with /\n- Increased default inactivity timer: 20 sec --> 180 sec\n- Ensure FTP `PASV` and `PORT` sockets are set non-blocking to prevent\n  blocking the event loop\n- [README.md][] updates, add usage section and improve build + install\n\n### Fixes\n- Fix 100% CPU issue.  Triggered sometimes when a user issued `CWD ..`\n\n\n[v2.3][] - 2017-03-22\n---------------------\n\nBug fix release.\n\n### Changes\n- Add support for `MDTM`, modify time, some clients rely this\n- Add support for correct `SIZE` when in ASCII mode\n- Add basic code of conduct to project\n- Add contributing guidelines, automatically referenced by GitHub\n  when filing a bug report or pull request\n\n### Fixes\n- Fix 100% CPU bug caused by `RETR` of non-regular file or directory\n- Fix segfault on missing FTP home\n- Fix ordering issue in fallback FTP user handling, introduced in v2.2\n- Fix error message on `CWD` to non-directory\n- Fix `.deb` generation and debconf installation/reconfigure issues\n\n\n[v2.2][] - 2017-03-14\n---------------------\n\n### Changes\n- Sort directories first in FTP `LIST` command\n- Make sure to exit all lingering FTP sessions on exit\n- Logging: reduced verbosity of common FTP commands\n- Logging: show client address on failed file retrieval\n- Full Debian/Ubuntu `.deb` build support, including debconf,\n  asking user what services (FTP and/or TFTP) to run.\n- Verify FTP/TFTP root directory is not writable by default\n- New option to allow writable FTP/TFTP root, disabled by default\n\n### Fixes\n- Fix FTP directory listings, was off-by-one, one entry missing\n- Issue #7: Spelling error in `README.md`\n- Issue #8: Install missing symlinks for `in.ftpd.8` and `in.tftpd.8`\n\n\n[v2.1][] - 2016-06-05\n---------------------\n\n### Changes\n- Remove GIT submodules for libuEv and libite, these two libraries\n  are now required to be installed separately.\n- The output from `uftpd -v` now only shows the version.\n\n\n[v2.0.2][] - 2016-02-02\n-----------------------\n\nMinor fix release.\n\n### Fixes\n- Distribution build fixes for companion libraries\n- Missing critical files in uftpd distribution\n\n\n[v2.0.1][] - 2016-02-02\n-----------------------\n\nMinor fix release.\n\n### Changes\n- Upgrade to [libite][] v1.4.2 (GCC 6 bug fixes)\n\n### Fixes\n- IPv6 address conversion error, found by GCC 6\n- Make install of symlinks for `in.tftpd` & `in.ftpd` idempotent. Check\n  any existing `in.ftpd` and `in.tftpd` symlinks before bugging out.\n  Fixes problem of uftpd install failing on already existing symlinks.\n\n\n[v2.0][] - 2016-01-22\n---------------------\n\nSleek, smart, simple ... UNIX\n\n### Changes\n- Greatly simplified command line syntax\n- Run inetd services by calling `in.ftpd` and `in.tftpd` symlinks\n- Migrate to GNU configure and build system\n- Update and simplify man page\n- Build statically against bundled versions of libite (LITE) and libuEv\n- Update bundled libuEv to v1.3.0\n- Update bundled libite to v1.4.1\n\n### Fixes\n- Do not allow VERSION to be overloaded by build system\n- Do not enforce any optimization in Makefile, this is up to the user\n- Minor fixes to redundant error messages when running as a regular user\n\n\n[v1.9.1][] - 2015-09-27\n-----------------------\n\nMinor fix release.\n\n### Changes\n- Upgrade to [libuEv][] v1.2.3 (bug fixes)\n- Upgrade to [libite][] v1.1.1 (bug fixes)\n- Add support for linking against external libuEv and libite\n\n### Fixes\n- Misc. README updates\n- Check if libite or libuEv are missing as submodules\n\n\n[v1.9][] - 2015-07-23\n---------------------\n\nBug fix release.  FTP and TFTP sessions can now run fully in parallel,\nindependent of each other.  Also improved compatibility with Firefox\nbuilt-in FTP client and wget.\n\n### Changes\n- Upgrade to [libuEv][] v1.2.1+ for improved error handling and a much\n  cleaner API.\n- Major refactor of both FTP and TFTP servers to use libuEv better.\n- Move to use [libite][] v1.0.0 for `strlcpy()`, `strlcat()`, `pidfile()`\n  and more.\n- Add proper session timeout to TFTP, like what FTP already has.\n- Add support for `NLST` FTP command, needed for multiple get operations.\n  This fixes issue #2, thanks to @oz123 on GitHub for pointing this out!\n- Add support for `FEAT` and `HELP` FTP commands used by some clients.\n\n### Fixes\n- Fix issue #3: do not sleep 2 sec before exiting.  Simply forward the\n  `SIGTERM` to any FTP/TFTP session in progress, yield the CPU to let\n  the child sessions handle the signal, and then exit.  Much quicker!\n- Fix issue #4: due to an ordering bug between the main process calling\n  `daemon()` and `sig_init()`, we never got the `SIGCHILD` to be able to\n  reap any exiting FTP/TFTP sessions.  This resulted in zombies(!) when\n  *not* being called as `uftpd -n`\n- Fix issue #5: `LIST` and `NLST` ignores path argument sent by client.\n- Fix issue #6: FTP clients not detecting session timeout.  Caused by\n  uftpd not performing a proper `shutdown()` on the client socket(s)\n  before `close()`.\n- Fix problem with [libuEv][] not being properly cleaned on `distclean`.\n- Fix problem with uftpd not exiting client session properly when client\n  simply closes the connection.\n\n\n[v1.8][] - 2015-02-02\n---------------------\n\n### Changes\n- Updated [README.md][]\n- Add [TODO.md][]\n- Add [CHANGELOG.md][], attempt to align with <http://keepachangelog.com>\n- From now on [Travis-CI][] only runs when pushing to the dev branch,\n  so all new development must be done there.\n- Upgrade to [libuEv][] v1.0.4\n\n### Fixes\n- Fix insecure `chroot()` reported in [Coverity Scan][] CID #54523.\n- Minor cleanup fixes.\n\n\n[v1.7][] - 2014-12-21\n---------------------\n\nThe TFTP Blocksize Negotiation release.\n\n### Changes\n- Support for [RFC 2348][], TFTP blocksize negotiation\n- Support for custom server directory, instead of FTP user's `$HOME`\n- Log to `stderr` when running in foreground and debug is enabled\n\n\n[v1.6][] - 2014-09-12\n---------------------\n\nFix missing [libuEv][] directory content generated by <kbd>make dist</kbd>\nin [v1.3][], [v1.4][], and [v1.5][].\n\n### Fixes\n- Since the introduction of the event library [libuEv][] the <kbd>make\n  dist</kbd> target has failed to include the libuev sub-directory.\n  This is due to the `git archive` command unfortunately not supporting\n  git sub-modules.\n\n\n[v1.5][] - 2014-09-12 [YANKED]\n------------------------------\n\nMajor fix release, lots of issues reported by [Coverity Scan][] fixed.\nFor details, see <https://scan.coverity.com/projects/2947>\n\n**Note:** This release has been *yanked* from distribution due to the\ntarball (generated by the <kbd>make dist</kbd>) missing the required\nlibuEv library.  Instead, use [v1.6][] or later, where this is fixed, or\nroll your own build of this release from the GIT source tree.\n\n### Changes\n- Add support for [Travis-CI][], continuous integration with GitHub\n- Add support for [Coverity Scan][], the best static code analyzer,\n  integrated with [Travis-CI][] -- scan runs for each push to master\n\n### Fixes\n- Fix nasty invalid `sizeof()` argument to `recv()` causing uftpd to\n  only read 4/8 bytes (32/64 bit arch) at a time from the FTP socket.\n  This should greatly reduce CPU utilization and improve xfer speeds.\n  Found by [Coverity Scan][].\n- Fix minor resource leak in `ftp_session()` when `getsockname()` or\n  `getpeername()` fail.  Minor fix because the session exits and the OS\n  usually frees resources at that point, unless you're using uClinux.\n  Found by [Coverity Scan][].\n- Various fixes for unchecked API return values, prevents propagation of\n  errors.  Also, make sure to clear input data before calling API's.\n  Found by [Coverity Scan][].\n- Fix oversight in checking for invalid/missing FTP username.\n  Found by [Coverity Scan][].\n- Fix potential attack vector.  Make sure to always store a NUL string\n  terminator in all received FTP commands so the parser does not go out\n  of bounds. Found by [Coverity Scan][].\n- Fix parallel build problems in `Makefile`.\n\n\n[v1.4][] - 2014-09-04 [YANKED]\n------------------------------\n\n**Note:** This release has been *yanked* from distribution due to the\ntarball (generated by the <kbd>make dist</kbd>) missing the required\nlibuEv library.  Instead, use [v1.6][] or later, where this is fixed, or\nroll your own build of this release from the GIT source tree.\n\n### Changes\n- Update documentation, both built-in usage text and man page.\n\n### Fixes\n- Fix bug in inetd.conf installed by .deb package for TFTP service.\n  Inetd forked off a new TFTP session for each connection attempt.\n\n\n[v1.3][] - 2014-09-04 [YANKED]\n------------------------------\n\nAdded support for TFTP, [RFC 1350][].  Integration of the asynchronous\nevent library [libuEv][], to serialize all events.  Massive refactoring.\n\n**Note:** This release has been *yanked* from distribution due to the\ntarball (generated by the <kbd>make dist</kbd>) missing the required\nlibuEv library.  Instead, use [v1.6][] or later, where this is fixed, or\nroll your own build of this release from the GIT source tree.\n\n### Changes\n- Incompatible changes to the command line arguments, compared to v1.2!\n- Add libuEv as a GIT submodule, handles signals, timers, and all I/O.\n- Refactor all signal handling, timers, and socket `poll()` calls to\n  use libuEv instead.  Much cleaner and maintainable code as a result.\n- Clarify copyright claims, not much remains of the original [FtpServer][]\n  code, by [Xu Wang][].\n\n\n[v1.2][] - 2014-05-19\n---------------------\n\n### Changes\n- Add support for logging to stdout as well as syslog.\n\n### Fixes\n- Fix embarrassing problem with listing big/average sized directories.\n\n\n[v1.1][] - 2014-05-04\n---------------------\n\nHaunted zombie (¬°-°)¬ release.\n\n### Changes\n- Add strict FTP session inactivity timer, 20 sec.\n- Change some logs to informational, only seen in verbose `-V` mode.\n- Revise .deb package slightly and add support for creating an FTP user\n  and group on the system.  This is used to both find the default FTP\n  home directory, to serve files from, and also the UID/GID to drop to\n  when being started as root.\n\n### Fixes\n- Fix zombie problem.  Forked off FTP sessions did not exit properly and\n  were not `wait()`'ed for properly, so uftpd left a zombie processes\n  lingering after each session.\n- Fix ordering bug in security mechanism \"drop privs\"\n\n\n[v1.0][] - 2014-05-04\n---------------------\n\nFirst official uftpd release! :-)\n\n### Changes\n- Forked from [FtpServer][], by [Xu Wang][].\n- Add permissive [ISC license][].\n- Massive refactor, code cleanup/renaming and \"UNIX'ification\":\n  - Add actual command line parser.\n  - Cleanup all log messages.\n  - Reindent to use Linux KNF.\n  - Use system's FTP user to figure out FTP home directory, with\n    built-in fallback to `/srv/ftp`\n  - Use system's `ftp/tcp` port from `/etc/services`.\n  - Chroot to FTP home directory.\n  - Support for dropping privileges if a valid FTP user exists.\n  - Use `fork()` instead of pthreads for FTP client sessions.\n  - Daemonize uftpd by default, detach from controlling terminal and\n    reparent to PID 1 (init).\n  - Add support for running as an `inetd` service.\n  - Add wrapper for `syslog()` instead of using `stdout/stderr`.\n  - Add basic `uftpd.8` man page.\n- Add OpenBSD `strlcat()` and `strlcpy()` safe string functions.\n- Add support for NOOP (keepalive sent by some clients).\n- Add support for SIZE.\n- Add support for TYPE, at least `IMAGE/BINARY`.\n- Add basic dependency handling to Makefile.\n- Add support for building Debian .deb packages.\n\n### Fixes\n- Handle \"walking up to parent\" attacks in several FTP functions.\n- Fix memory leaks in `recv_mesg()` caused by dangerous homegrown string\n  functions.  Replaced with safer OpenBSD variants.\n- Fix absolute paths in FTP `LIST` command.\n- Fix Firefox FTP mode `LIST` compatibility issue.\n- Fix \"bare linefeeds\" warning from certain FTP clients in ASCII mode.\n  Lines must end in the old `\\r\\n` format, rather than UNIX `\\n`.\n\n\n[UNRELEASED]:    https://github.com/troglobit/uftpd/compare/v2.15...HEAD\n[v2.15]:         https://github.com/troglobit/uftpd/compare/v2.14...v2.15\n[v2.14]:         https://github.com/troglobit/uftpd/compare/v2.13...v2.14\n[v2.13]:         https://github.com/troglobit/uftpd/compare/v2.12...v2.13\n[v2.12]:         https://github.com/troglobit/uftpd/compare/v2.11...v2.12\n[v2.11]:         https://github.com/troglobit/uftpd/compare/v2.10...v2.11\n[v2.10]:         https://github.com/troglobit/uftpd/compare/v2.9...v2.10\n[v2.9]:          https://github.com/troglobit/uftpd/compare/v2.8...v2.9\n[v2.8]:          https://github.com/troglobit/uftpd/compare/v2.7...v2.8\n[v2.7]:          https://github.com/troglobit/uftpd/compare/v2.6...v2.7\n[v2.6]:          https://github.com/troglobit/uftpd/compare/v2.5...v2.6\n[v2.5]:          https://github.com/troglobit/uftpd/compare/v2.4...v2.5\n[v2.4]:          https://github.com/troglobit/uftpd/compare/v2.3...v2.4\n[v2.3]:          https://github.com/troglobit/uftpd/compare/v2.2...v2.3\n[v2.2]:          https://github.com/troglobit/uftpd/compare/v2.1...v2.2\n[v2.1]:          https://github.com/troglobit/uftpd/compare/v2.0.2...v2.1\n[v2.0.2]:        https://github.com/troglobit/uftpd/compare/v2.0.1...v2.0.2\n[v2.0.1]:        https://github.com/troglobit/uftpd/compare/v2.0...v2.0.1\n[v2.0]:          https://github.com/troglobit/uftpd/compare/v1.9.1...v2.0\n[v1.9.1]:        https://github.com/troglobit/uftpd/compare/v1.9...v1.9.1\n[v1.9]:          https://github.com/troglobit/uftpd/compare/v1.8...v1.9\n[v1.8]:          https://github.com/troglobit/uftpd/compare/v1.7...v1.8\n[v1.7]:          https://github.com/troglobit/uftpd/compare/v1.6...v1.7\n[v1.6]:          https://github.com/troglobit/uftpd/compare/v1.5...v1.6\n[v1.5]:          https://github.com/troglobit/uftpd/compare/v1.4...v1.5\n[v1.4]:          https://github.com/troglobit/uftpd/compare/v1.3...v1.4\n[v1.3]:          https://github.com/troglobit/uftpd/compare/v1.2...v1.3\n[v1.2]:          https://github.com/troglobit/uftpd/compare/v1.1...v1.2\n[v1.1]:          https://github.com/troglobit/uftpd/compare/v1.0...v1.1\n[v1.0]:          https://github.com/troglobit/uftpd/compare/v0.1...v1.1\n[libuEv]:        https://github.com/troglobit/libuev\n[libite]:        https://github.com/troglobit/libite\n[ISC license]:   http://en.wikipedia.org/wiki/ISC_license\n[RFC 1350]:      http://tools.ietf.org/html/rfc1350\n[RFC 2348]:      http://tools.ietf.org/html/rfc2348\n[Xu Wang]:       https://github.com/xu-wang11/\n[FtpServer]:     https://github.com/xu-wang11/FtpServer\n[Travis-CI]:     https://travis-ci.org/troglobit/uftpd\n[Coverity Scan]: https://scan.coverity.com/projects/2947\n[TODO.md]:       https://github.com/troglobit/uftpd/blob/master/docs/TODO.md\n[README.md]:     https://github.com/troglobit/uftpd/blob/master/README.md\n[CHANGELOG.md]:  https://github.com/troglobit/uftpd/blob/master/CHANGELOG.md\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (C) 2014-2021  Joachim Wiberg <troglobit@gmail.com>\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n"
  },
  {
    "path": "Makefile.am",
    "content": "SUBDIRS            = src man test\ndoc_DATA           = README.md LICENSE ChangeLog.md\nEXTRA_DIST         = README.md LICENSE ChangeLog.md\n\n## Check if tagged in git\nrelease-hook:\n\tif [ ! `git tag -l v$(PACKAGE_VERSION) | grep $(PACKAGE_VERSION)` ]; then\t\\\n\t\techo;\t\t\t\t\t\t\t\t\t\\\n\t\tprintf \"\\e[1m\\e[41mCannot find release tag $(PACKAGE_VERSION)\\e[0m\\n\";\t\\\n\t\tprintf \"\\e[1m\\e[5mDo release anyway?\\e[0m \"; read yorn;\t\t\t\\\n\t\tif [ \"$$yorn\" != \"y\" -a \"$$yorn\" != \"Y\" ]; then\t\t\t\t\\\n\t\t\tprintf \"OK, aborting release.\\n\";\t\t\t\t\\\n\t\t\texit 1;\t\t\t\t\t\t\t\t\\\n\t\tfi;\t\t\t\t\t\t\t\t\t\\\n\t\techo;\t\t\t\t\t\t\t\t\t\\\n\telse\t\t\t\t\t\t\t\t\t\t\\\n\t\techo;\t\t\t\t\t\t\t\t\t\\\n\t\tprintf \"\\e[1m\\e[42mFound GIT release tag $(PACKAGE_VERSION)\\e[0m\\n\";\t\\\n\t\tprintf \"\\e[1m\\e[44m>>Remember to push tags!\\e[0m\\n\";\t\t\t\\\n\t\techo;\t\t\t\t\t\t\t\t\t\\\n\tfi\n\n## Generate .deb package\npackage build-deb:\n\t@debuild -uc -us -B --lintian-opts --profile debian -i -I --show-overrides\n\n## Target to run when building a release\nrelease: release-hook distcheck\n\t@for file in $(DIST_ARCHIVES); do\t\t\\\n\t\tmd5sum    $$file > ../$$file.md5;\t\\\n\t\tsha256sum $$file > ../$$file.sha256;\t\\\n\tdone\n\t@mv $(DIST_ARCHIVES) ../\n\t@echo\n\t@echo \"Resulting release files:\"\n\t@echo \"=================================================================\"\n\t@for file in $(DIST_ARCHIVES); do\t\t\t\t\t\t\\\n\t\tprintf \"%-32s Distribution tarball\\n\" $$file;\t\t\t\t\\\n\t\tprintf \"%-32s \" $$file.md5;    cat ../$$file.md5    | cut -f1 -d' ';\t\\\n\t\tprintf \"%-32s \" $$file.sha256; cat ../$$file.sha256 | cut -f1 -d' ';\t\\\n\tdone\n"
  },
  {
    "path": "README.md",
    "content": "No Nonsense FTP/TFTP Server\n===========================\n[![License Badge][]][License] [![GitHub Status][]][GitHub] [![Coverity Status][]][Coverity Scan]\n\nuftpd is a UNIX daemon with sane built-in defaults.  It just works.\n\n\nFeatures\n--------\n\n* FTP and/or TFTP\n* No complex configuration file\n* Runs from standard UNIX inetd, or standalone\n* Uses `ftp` user's `$HOME`, from `/etc/passwd`, or custom path\n* Uses `ftp/tcp` and `tftp/udp` from `/etc/services`, or custom ports\n* Privilege separation, drops root privileges having bound to ports\n* Possible to use symlinks outside of the FTP home directory\n* Possible to have group writable FTP home directory\n\n\nUsage\n-----\n\n```\nuftpd [-hnsv] [-l LEVEL] [-o OPTS] [PATH]\n\n  -h         Show this help text\n  -l LEVEL   Set log level: none, err, notice (default), info, debug\n  -n         Run in foreground, do not detach from controlling terminal\n  -o OPT     Options:\n                      ftp=PORT\n                      tftp=PORT\n                      pasv_addr=ADDR\n                      writable\n  -s         Use syslog, even if running in foreground, default w/o -n\n  -v         Show program version\n\nThe optional 'PATH' defaults to the $HOME of the /etc/passwd user 'ftp'\nBug report address: https://github.com/troglobit/uftpd/issues\n```\n\nTo start uftpd in the background as an FTP/TFTP server:\n\n    uftpd\n\nIf the `ftp` user does not exist on your system, `uftpd` defaults to\nserve files from the `/srv/ftp` directory.  To serve another directory,\nsimply append that directory to the argument list.\n\nUse `sudo`, or set `CAP_NET_BIND_SERVICE` capabilities, on `uftpd` to\nallow regular users to start `uftpd` on privileged (standard) ports,\ni.e. `< 1024`:\n\n    sudo setcap cap_net_bind_service+ep uftpd\n\nTo change port on either FTP or TFTP, use:\n\n    uftpd -o ftp=PORT,tftp=PORT\n\nSet `PORT` to zero (0) to disable either service.\n\nNew sessions are droppbed by default if uftpd detects the FTP root is\nwritable.  To allow writable FTP root:\n\n    uftpd -o writable PATH\n\n> **Note:** since v2.11 uftpd logs a lot more events by default.  Set up\n> your syslogd to redirect `LOG_FTP` to a separate log file, or reduce\n> the log level of uftpd using `-l error` to only log errors and higher.\n\n\nRunning from inetd\n------------------\n\nRarely used services like FTP/TFTP are good candidates to run from the\nInternet super server, inetd.  On Debian and Ubuntu based distributions\nwe recommend `openbsd-inetd`.\n\nUse the following two lines in `/etc/inetd.conf`, notice how `in.ftpd`\nand `in.tftpd` are symlinks to the `uftpd` binary:\n\n    ftp     stream  tcp nowait  root    /usr/sbin/in.ftpd\n    tftp    dgram   udp wait    root    /usr/sbin/in.tftpd\n\nRemember to activate your changes to inetd by reloading the service or\nsending `SIGHUP` to it.  Another inetd server may use different syntax.\nLike the inetd that comes built-in to [Finit][], in `/etc/finit.conf`:\n\n    inetd ftp/tcp   nowait /usr/sbin/in.ftpd  -- The uftpd FTP server\n    inetd tftp/udp    wait /usr/sbin/in.tfptd -- The uftpd TFTP server\n\n\nCaveat\n------\n\nuftpd is primarily not targeted at secure installations, it is targeted\nat users in need of a *simple* FTP/TFTP server.\n\nuftpd allows symlinks outside the FTP root, as well as a group writable\nFTP home directory &mdash; user-friendly features that potentially can\ncause security breaches, but also very useful for people who just want\ntheir FTP server to work.  A lot of care has been taken, however, to\nlock down and secure uftpd by default.\n\n\nBuild & Install\n---------------\n\n### Debian/Ubuntu\n\n    curl -sS https://deb.troglobit.com/pubkey.gpg | sudo apt-key add -\n    echo \"deb [arch=amd64] https://deb.troglobit.com/debian stable main\" | sudo tee /etc/apt/sources.list.d/troglobit.list\n    sudo apt-get update && sudo apt-get install uftpd\n\n### Building from Source\n\n`uftpd` depends on two other projects to build from source, [libuEv][]\nand [lite][].  See their respective README for details, there should be\nno real surprises, both use the familiar configure, make, make install.\n\nTo find the two libraries uftpd depends on `pkg-config`.  The package\nname for your Linux distribution varies, on Debian/Ubuntu systems:\n\n```shell\nuser@example:~/> sudo apt install pkg-config\n```\n\nuftpd, as well as its dependencies, can be built as `.deb` packages on\nDebian or Ubuntu based distributions.  Download and install each of the\ndependencies, and then run\n\n    ./autogen.sh      <--- Only needed if using GIT sources\n    ./configure\n    make package\n\nThe `.deb` package takes care of setting up `/etc/inetd.conf`, create an\n`ftp` user and an `/srv/ftp` home directory with write permissions for\nall members of the `users` group.\n\nIf you are using a different Linux or UNIX distribution, check the\noutput from `./configure --help`, followed by `make all install`.\nFor instance, building on [Alpine Linux](https://alpinelinux.org/):\n\n    PKG_CONFIG_LIBDIR=/usr/local/lib/pkgconfig ./configure \\\n\t    --prefix=/usr --localstatedir=/var --sysconfdir=/etc\n\nProvided the library dependencies were installed in `/usr/local/`.  This\n`PKG_CONFIG_LIBDIR` trick may be needed on other GNU/Linux, or UNIX,\ndistributions as well.\n\n\nOrigin & References\n-------------------\n\nuftpd was originally based on [FtpServer][] by [Xu Wang][], but is now a\ncomplete rewrite with TFTP support by [Joachim Wiberg][], maintained at\n[GitHub][home].\n\n\n[Joachim Wiberg]: http://troglobit.com\n[the FTP]:         http://ftp.troglobit.com/uftpd/\n[Xu Wang]:         https://github.com/xu-wang11/\n[FtpServer]:       https://github.com/xu-wang11/FtpServer\n[home]:            https://github.com/troglobit/uftpd\n[Finit]:           https://github.com/troglobit/finit\n[lite]:            https://github.com/troglobit/libite\n[libuEv]:          https://github.com/troglobit/libuev\n[License]:         https://en.wikipedia.org/wiki/ISC_license\n[License Badge]:   https://img.shields.io/badge/License-ISC-blue.svg\n[GitHub]:          https://github.com/troglobit/uftpd/actions/workflows/build.yml/\n[GitHub Status]:   https://github.com/troglobit/uftpd/actions/workflows/build.yml/badge.svg\n[Coverity Scan]:   https://scan.coverity.com/projects/2947\n[Coverity Status]: https://scan.coverity.com/projects/2947/badge.svg\n"
  },
  {
    "path": "autogen.sh",
    "content": "#!/bin/sh\n\nautoreconf -W portability -visfm\n"
  },
  {
    "path": "configure.ac",
    "content": "AC_INIT([uftpd], [2.15], [https://github.com/troglobit/uftpd/issues], [],\n\t[https://troglobit.com/projects/uftpd/])\nAC_CONFIG_AUX_DIR(aux)\nAM_INIT_AUTOMAKE([1.11 foreign dist-xz])\nAM_SILENT_RULES([yes])\n\nAC_CONFIG_SRCDIR([src/uftpd.c])\nAC_CONFIG_HEADERS([config.h])\nAC_CONFIG_FILES([Makefile src/Makefile man/Makefile test/Makefile])\n\nAC_PROG_CC\nAC_PROG_LN_S\nAC_PROG_INSTALL\n\n# Configuration.\nAC_CHECK_HEADERS(sys/time.h)\nAC_CHECK_FUNCS(strstr getopt getsubopt gettimeofday)\n\n# Check for uint[8,16,32]_t\nAC_TYPE_UINT8_T\nAC_TYPE_UINT16_T\nAC_TYPE_UINT32_T\n\n# Check for pkg-config first, warn if it's not installed\nPKG_PROG_PKG_CONFIG\n\n# Check for required libraries\nPKG_CHECK_MODULES([uev],  [libuev >= 2.2.0])\nPKG_CHECK_MODULES([lite], [libite >= 1.5.0])\n\nAC_OUTPUT\n"
  },
  {
    "path": "debian/.gitignore",
    "content": "autoreconf.*\ndebhelper-build-stamp\nfiles\nuftpd.debhelper.log\nuftpd.post*\nuftpd.pre*\nuftpd.substvars\nuftpd/\n"
  },
  {
    "path": "debian/README.Debian",
    "content": "uftpd for Debian/Ubuntu\n-----------------------\n\nuftpd is a true UNIX TFTP/FTP daemon, it serves files, and nothing more.\nIt runs from inetd on ports specified in /etc/services, serving files\nfrom the ftp user's $HOME, /src/ftp -- it just works.\n\n -- Joachim Wiberg <troglobit@gmail.com>, Sat, 11 Dec 2021 08:46:52 +0100\n"
  },
  {
    "path": "debian/changelog",
    "content": "uftpd (2.15) stable; urgency=medium\n\n  * Silence some developer debug messages\n  * Always skip `.` and `..` in FTP listings\n  * Fix mdoc warning, found by lintian\n  * Fix regression introduced in v2.14, server directory name shown in FTP\n    listings instead of `.`, e.g. `MLST .` showed the directory name \n  * Fix #36: for real this time, now also with a test case to verify\n  * Fix #38: duplicate entries in FTP listings, regression in v2.14.\n    Caused by (initially unintentional) removal of sorted listings, where\n    directories prior to v2.14 were listed first.  This change, albeit an\n    accident, actually helped clean up the code base and speed up replies\n\n -- Joachim Wiberg <troglobit@gmail.com>  Mon, 20 Dec 2021 06:15:08 +0100\n\nuftpd (2.14) stable; urgency=medium\n\n  * Add support for `-o pasv_addr=ADDR` command line argument to override\n  * Add support for `-p PIDFILE` command line argument\n    the address passed to the client in passive mode, useful for some\n    types of NAT setup\n  * Fix issue #36: MLST command without any argument blocks\n  * Fix memory leak in MLST/MLSD, only affects no-MMU systems where the\n    kernel cannot free memory of processes on exit\n\n -- Joachim Wiberg <troglobit@gmail.com>  Sat, 11 Dec 2021 11:27:57 +0100\n\nuftpd (2.13) unstable; urgency=medium\n\n  * Fix issue #31: Socket leak in daemon accept(), causing denial of\n    service in standalone daemon setups.  Does not affect .deb install.\n  * Fix minor memory leak, only affects non-MMU systems.\n\n -- Joachim Nilsson <troglobit@gmail.com>  Tue, 30 Jun 2020 23:36:35 +0200\n\nuftpd (2.12) stable; urgency=medium\n\n  * Fix issue #30: uftpd crashes when an invalid CWD is entered\n  * Use common log message format and log level for all path refs.\n\n -- Joachim Nilsson <troglobit@gmail.com>  Mon, 25 May 2020 18:08:32 +0200\n\nuftpd (2.11) unstable; urgency=medium\n\n  * Increased logging at default log level.  Now all relevant interaction\n    is logged.  See the man page for how to adjust.\n  * Fix buffer overflow in FTP PORT parser\n  * Fix TFTP/FTP directory traversal regression\n  * Fix potential DOS through non-busy loop and segfault\n  * Fix potential segfault through empty FTP password\n  * Fix potential segfault through FTP PORT command\n\n -- Joachim Nilsson <troglobit@gmail.com>  Sun, 05 Jan 2020 08:49:56 +0100\n\nuftpd (2.10) unstable; urgency=medium\n\n  * Add support for TFTP WRQ, i.e. for clients sending files to server\n  * Fix invalid TFTP error codes, now uses custom error string to code 0\n  * Slightly improved debug messages\n\n -- Joachim Nilsson <troglobit@gmail.com>  Thu, 15 Aug 2019 08:59:35 +0200\n\nuftpd (2.9) unstable; urgency=medium\n\n  * Check FTP root security after dropping privileges, issue #22\n  * Revert insecure default: \"writable FTP root\", introduced in v2.8\n  * Revert part of issue #18 to fix issue #23; \"CWD /\" doesn't work\n  * Update debian packaging to policy 4.3.0\n  * Fix failing postrm script, causing dpkg -P uftpd to fail hard\n  * Fix spelling errors found by Lintian\n  * Fix package description, more formal and less personal, thanks Lintian\n\n -- Joachim Nilsson <troglobit@gmail.com>  Mon, 29 Jul 2019 10:52:49 +0200\n\nuftpd (2.8) unstable; urgency=medium\n\n  * Fix off-by-one regression introduced in v2.5\n  * Convert all commands from user to uppercase for processing\n  * Skip any and *all* FTP LIST options\n  * Enable users group writable FTP root in /etc/inetd.conf\n\n -- Joachim Nilsson <troglobit@gmail.com>  Tue, 28 May 2019 06:22:18 +0200\n\nuftpd (2.7) unstable; urgency=medium\n\n  * Bug fix release\n  * Fix running uftpd as unprivileged user using a relative FTP root\n\n -- Joachim Nilsson <troglobit@gmail.com>  Sun, 03 Mar 2019 11:39:03 +0100\n\nuftpd (2.6) unstable; urgency=medium\n\n  * Bug fix release\n  * Really fix 100% CPU issue, take two.  Some clients managed to trigger\n    a bug caused by calling `uev_exit()` twice on client session exit\n\n -- Joachim Nilsson <troglobit@gmail.com>  Tue, 03 Jul 2018 17:14:00 +0200\n\nuftpd (2.5) unstable; urgency=critical\n\n  * Really fix 100% CPU issue\n  * Fix line endings for NLST and LIST FTP commands\n  * Add support for EPSV, MLSD, ABOR, and REST FTP commands, required\n    for VLC Android app\n  * Add support for CDUP, DELE, MKD, RMD, MLST, and OPTS MLST\n  * Refactor LIST, RETR, STOR and PASV FTP commands for speed\n\n -- Joachim Nilsson <troglobit@gmail.com>  Sat, 19 May 2018 13:35:01 +0200\n\nuftpd (2.4-1) unstable; urgency=medium\n\n  * New upstream release, fixes 100% CPU issue reported by some users.\n\n -- Joachim Nilsson <troglobit@gmail.com>  Sun, 03 Sep 2017 12:38:38 +0200\n\nuftpd (2.3-1) stable; urgency=low\n\n  * New upstream release, fixes issue with lingering inetd FTP processes\n    using up 100% CPU.\n\n -- Joachim Nilsson <troglobit@gmail.com>  Wed, 22 Mar 2017 07:56:00 +0100\n\nuftpd (2.2-4) stable; urgency=low\n\n  * Fix dpkg-reconfigure support.  When disabling either TFTP/FTP\n    the disabled service was not properly disabled in /etc/inetd.conf\n\n -- Joachim Nilsson <troglobit@gmail.com>  Sun, 14 Mar 2017 22:05:00 +0100\n\nuftpd (2.2-3) stable; urgency=low\n\n  * Add debconf support\n  * Change default group and permissions of /srv/ftp to prevent any\n    future \"security\" breaches by uploads to the FTP root directory.\n\n    A user must now be member of the users group to share files over\n    TFTP/FTP.  Simply add a user to 'users' and they can upload their\n    files to /srv/ftp.  The TFTP/FTP server itself has no rights to\n    write there.  Add an uploads/ sub-directory with write perms for\n    the 'ftp' user if you want to enable anonymous uploads via FTP.\n\n -- Joachim Nilsson <troglobit@gmail.com>  Sun, 13 Mar 2017 19:56:00 +0100\n\nuftpd (2.2-1) stable; urgency=low\n\n  * New upstream release, v2.2\n\n -- Joachim Nilsson <troglobit@gmail.com>  Sun, 12 Mar 2017 17:36:00 +0100\n\nuftpd (2.1-1) stable; urgency=low\n\n  * New upstream release, v2.1\n    - Removed built-in GIT submodules for libite and libuev\n    - Updated README\n\n -- Joachim Nilsson <troglobit@gmail.com>  Sun,  5 Jun 2016 00:19:30 +0100\n\nuftpd (2.0-1) stable; urgency=low\n\n  * New upstream release, v2.0\n    - Completely changed command line syntax\n    - New binaries (symlinks) for inetd usage, in.tftpd and in.ftpd\n    - Updated man page\n\n -- Joachim Nilsson <troglobit@gmail.com>  Sun, 22 Jan 2016 13:51:56 +0100\n\nuftpd (2.0-rc1-1) unstable; urgency=low\n\n  * New upstream release, v2.0-rc1\n    - Completely changed command line syntax\n    - New binaries (symlinks) for inetd usage\n\n -- Joachim Nilsson <troglobit@gmail.com>  Sun, 17 Jan 2016 23:16:58 +0100\n\nuftpd (1.9.1-1) unstable; urgency=medium\n\n  * Minor fix release\n  * Added support for building with external libite and libuEv\n  * Rebuild for Ubuntu 15.10\n\n -- Joachim Nilsson <troglobit@gmail.com>  Sun, 27 Sep 2015 10:14:31 +0200\n\nuftpd (1.9-1) unstable; urgency=low\n\n  * New upstream release:\n    - Add support for NLST FTP command.\n    - Fix problem with 2 seconds before exiting on SIGTERM/SIGINT.\n    - Fix zombie problem when backgrounding.\n    - Add support for automatically creating a PID file.\n    - Fix problem with LIST (and NLST) ignoring path argument.\n    - Fix problem with FTP client simply closing connection (no QUIT)\n\n -- Joachim Nilsson <troglobit@gmail.com>  Mon, 13 Jul 2015 01:25:26 +0200\n\nuftpd (1.8-1) unstable; urgency=low\n\n  * New upstream release:\n    - Fix insecure chroot(), reported by Coverity Scan, CID #54523\n    - Minor updates to README and a new CHANGELOG file\n\n -- Joachim Nilsson <troglobit@gmail.com>  Sun,  2 Feb 2015 06:45:06 +0100\n\nuftpd (1.7-1) unstable; urgency=low\n\n  * New upstream release:\n    - Support for TFTP blocksize negotiation.\n    - Support for custom FTP server directory.\n    - Log to stderr when in foreground AND debug mode.\n\n -- Joachim Nilsson <troglobit@gmail.com>  Sun, 21 Dec 2014 19:35:29 +0100\n\nuftpd (1.6-1) unstable; urgency=low\n\n  * Repack of 1.5 due to missing content in new libuev subdirectory in\n    distributed tarball.\n\n -- Joachim Nilsson <troglobit@gmail.com>  Fri, 12 Sep 2014 15:45:08 +0200\n\nuftpd (1.5-1) unstable; urgency=low\n\n  * New upstream release, minor bug fixes only, found by Coverity Scan.\n  * Updates to README\n\n -- Joachim Nilsson <troglobit@gmail.com>  Fri, 12 Sep 2014 01:30:01 +0200\n\nuftpd (1.4-1) unstable; urgency=low\n\n  * New upstream release, bug fix TFTP start in /etc/inetd.conf\n  * Updates to man page.\n\n -- Joachim Nilsson <troglobit@gmail.com>  Thu, 04 Sep 2014 22:16:22 +0200\n\nuftpd (1.3-1) unstable; urgency=low\n\n  * New upstream release, with TFTP support.\n  * Incompatible change in command line options.\n\n -- Joachim Nilsson <troglobit@gmail.com>  Tue, 19 Aug 2014 23:27:28 +0200\n\nuftpd (1.2-1) unstable; urgency=low\n\n  * New upstream release.  Fixes problem with listing \"big\" directories.\n\n -- Joachim Nilsson <troglobit@gmail.com>  Mon, 19 May 2014 22:02:17 +0200\n\nuftpd (1.1-1) unstable; urgency=low\n\n  * New release.  Fixes problem with lingering zombie processes and an\n    ordering bug preventing drop privs from working properly.\n\n -- Joachim Nilsson <troglobit@gmail.com>  Sun, 04 May 2014 23:37:24 +0200\n\nuftpd (1.0-2) unstable; urgency=low\n\n  * Add support for creating and removing the standard ftp user on\n    installation and removal.  Use /srv/ftp as $HOME and make sure\n    to not remove $HOME when removing uftpd.  This also means that\n    the uftpd privsep mode gets its first testing.\n\n -- Joachim Nilsson <troglobit@gmail.com>  Sun, 04 May 2014 19:28:16 +0200\n\nuftpd (1.0-1) unstable; urgency=low\n\n  * Initial release.\n\n -- Joachim Nilsson <troglobit@gmail.com>  Sun,  4 May 2014 03:03:32 +1000\n\n"
  },
  {
    "path": "debian/compat",
    "content": "10\n"
  },
  {
    "path": "debian/config",
    "content": "#!/bin/sh\n\nset -e\n. /usr/share/debconf/confmodule\n\ndb_title uftpd\n\ndb_input critical uftpd/ftp || true\ndb_go\n\ndb_input critical uftpd/tftp || true\ndb_go\n\n"
  },
  {
    "path": "debian/control",
    "content": "Source: uftpd\nSection: net\nPriority: optional\nMaintainer: Joachim Wiberg <troglobit@gmail.com>\nBuild-Depends: debhelper (>= 10)\nStandards-Version: 4.3.0\nHomepage: https://troglobit.com/projects/uftpd/\n\nPackage: uftpd\nArchitecture: any\nPre-Depends: adduser\nDepends: openbsd-inetd | inet-superserver, debconf (>= 0.2.17), ${shlibs:Depends}, ${misc:Depends}\nProvides: ftp-server\nConflicts: ftp-server, tftpd, tftpd-hpa\nDescription: No nonsense TFTP/FTP server\n uftpd is a small and simple TFTP and FTP server intended for LANs.  Its\n author runs it on the Internet, although this is not recommended.\n .\n uftpd is set up in a read-only configuration by default.  It has no\n users, except for anonymous, no configuration file, and is started\n on-demand by the UNIX inetd super server, neatly tcpwrapped for your\n safety.\n .\n Hardcore Internet users and anyone concerned about security should\n probably consider a separate TFTP server and for FTP look at one of:\n vsftpd, proftpd, or pure-ftpd.\n"
  },
  {
    "path": "debian/copyright",
    "content": "\nCopyright: (c) 2014-2021  Joachim Wiberg <troglobit@gmail.com>\n\nLicense: ISC\n Permission to use, copy, modify, and/or distribute this software for any\n purpose with or without fee is hereby granted, provided that the above\n copyright notice and this permission notice appear in all copies.\n \n THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n"
  },
  {
    "path": "debian/dirs",
    "content": "usr/share/man/man8\nusr/sbin\n"
  },
  {
    "path": "debian/docs",
    "content": "README.md\n"
  },
  {
    "path": "debian/postinst",
    "content": "#!/bin/sh\n\nset -e\n\n[ \"$1\" = \"configure\" ] || exit 0\n\n# Source debconf library.\n. /usr/share/debconf/confmodule\n\nFTPENTRY=\"ftp\t\tstream\ttcp\tnowait\troot\t/usr/sbin/tcpd\tin.ftpd\"\nTFTPENTRY=\"tftp\t\tdgram\tudp\twait\troot\t/usr/sbin/tcpd\tin.tftpd\"\n\nif [ ! -f /etc/inetd.conf -a -d /etc/xinetd.d -a -x /usr/sbin/xinetd ]; then\n\tcat <<-TEXT\n\t\t-------------------------------------------------\n\t\tThere is no configuration support for using uftpd\n\t\tunder the control of xinetd.\n\t\t-------------------------------------------------\n\tTEXT\nfi\n\nif grep -q '[[:blank:]]/usr/sbin/uftpd.*' /etc/inetd.conf 2>/dev/null; then\n\tupdate-inetd --pattern '/usr/sbin/uftpd' --remove \".*ftp\"\nfi\n\nupdate-inetd --group STANDARD --add \"$FTPENTRY\"\nupdate-inetd --group STANDARD --add \"$TFTPENTRY\"\n\ndb_get uftpd/ftp\nif [ \"$RET\" = \"true\" ]; then\n    update-inetd --enable ftp\nelse\n    update-inetd --disable ftp\nfi\n\ndb_get uftpd/tftp\nif [ \"$RET\" = \"true\" ]; then\n    update-inetd --enable tftp\nelse\n    update-inetd --disable tftp\nfi\n\n# Redirect errors from adduser since 1) user may exist already,\n# 2) directory may exist and not be writable by user.  Ignore.\nif ! grep -q \"^ftp:\" /etc/passwd; then\n\taddgroup --quiet --system ftp\n\tadduser --quiet --system --disabled-login --home /srv/ftp \\\n\t\t--ingroup ftp ftp 2>/dev/null || true\nfi\n\n# Adjust for any previous server, users wanting to share files using\n# TFTP/FTP should be in the users group\nchown --changes ftp:users /srv/ftp\nchmod --changes 0575      /srv/ftp\n"
  },
  {
    "path": "debian/postrm",
    "content": "#!/bin/sh\nset -e\n\nif [ \"$1\" = \"purge\" ]; then\n\tif command -v update-inetd >/dev/null 2>&1; then\n\t\tupdate-inetd --pattern 'uftpd' --remove \".*ftp\"\n\t\tupdate-inetd --pattern 'in.ftpd' --remove ftp\n\t\tupdate-inetd --pattern 'in.tftpd' --remove tftp\n\tfi\n\n\t# Remove uftpd entries from db\n\tif [ -f /usr/share/debconf/confmodule ]; then\n\t\t. /usr/share/debconf/confmodule\n\t\tdb_purge\n\tfi\nfi\n\ndeluser --quiet --system ftp\n\nexit 0\n"
  },
  {
    "path": "debian/prerm",
    "content": "#!/bin/sh\n\nset -e\n\nupdate-inetd --pattern 'in.ftpd' --multi --disable ftp\nupdate-inetd --pattern 'in.tftpd' --multi --disable tftp\n"
  },
  {
    "path": "debian/rules",
    "content": "#!/usr/bin/make -f\n# export DH_VERBOSE=1\nexport DEB_BUILD_MAINT_OPTIONS = hardening=+all\nexport DEB_BUILD_OPTIONS='parallel=1'\n\n%:\n\tdh $@ --with autoreconf,systemd\n\noverride_dh_installchangelogs:\n\tdh_installchangelogs ChangeLog.md\n\n# Remove LICENSE and ChangeLog.md per Debian Policy\noverride_dh_auto_install:\n\tdh_auto_install\n\trm -v debian/uftpd/usr/share/doc/uftpd/LICENSE\n\trm -v debian/uftpd/usr/share/doc/uftpd/ChangeLog.md\n"
  },
  {
    "path": "debian/source/format",
    "content": "3.0 (native)\n"
  },
  {
    "path": "debian/templates",
    "content": "Template: uftpd/ftp\nType: boolean\nDefault: true\nDescription: Enable FTP service?\n\nTemplate: uftpd/tftp\nType: boolean\nDefault: true\nDescription: Enable TFTP service?\n\n"
  },
  {
    "path": "doc/TODO.md",
    "content": "TODO\n====\n\n* Setup signed .deb repository on deb.troglobit.com\n* Port to *BSD (Free/Net/Open) -- requires kqueue support in libuEv\n* Add TFTP retransmit support and inactivity timer, see\n  http://tools.ietf.org/html/rfc2349\n* Add support for IPv6\n* Update Coverity Scan model to skip intended constructs\n* Add uftp client, with .netrc support\n  - See netrc(5) for details of format.\n  - Build small CLI library using editline.\n\n"
  },
  {
    "path": "man/Makefile.am",
    "content": "dist_man8_MANS     = uftpd.8\nSYMLINK            = in.ftpd in.tftpd\n\n# Hook in install to add uftpd.8 --> in.ftpd.8, in.tftpd.8 symlinks\ninstall-data-hook:\n\t@for file in $(SYMLINK); do \\\n\t\tlink=$(DESTDIR)$(man8dir)/$$file.8; \\\n\t\ttest -e $$link && continue; \\\n\t\t$(LN_S) $(dist_man8_MANS) $$link; \\\n\tdone\n\nuninstall-hook:\n\t@for file in $(SYMLINK); do \\\n\t\t$(RM) $(DESTDIR)$(man8dir)/$$file.8; \\\n\tdone\n\n"
  },
  {
    "path": "man/uftpd.8",
    "content": ".\\\"\n.\\\" Copyright (c) 2014-2021  Joachim Wiberg <troglobit@gmail.com>\n.\\\"\n.\\\" Permission to use, copy, modify, and/or distribute this software for any\n.\\\" purpose with or without fee is hereby granted, provided that the above\n.\\\" copyright notice and this permission notice appear in all copies.\n.\\\"\n.\\\" THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n.\\\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n.\\\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n.\\\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n.\\\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n.\\\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n.\\\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n.\\\"\n.Dd December 6, 2021\n.Dt UFTPD 8\n.Os \"uftpd (2.14)\"\n.Sh NAME\n.Nm uftpd\n.Nd\nNo nonsense TFTP/FTP Server\n.Sh SYNOPSIS\n.Nm\n.Op Fl hnsv\n.Op Fl l Ar LOG\n.Op Fl o Ar ftp=PORT,tftp=PORT,writable\n.Op Fl p Ar FILE\n.Op Ar PATH\n.Sh DESCRIPTION\n.Nm\nis a very simple TFTP and anonymous FTP server with inetd support.  It\nlistens on standard Internet ports for each protocol, as defined in the\nsystem service specification,\n.Pa /etc/services ,\nunless other ports are given on the command line.  For details, see\n.Xr services 5 .\n.Pp\nWithout any command line arguments\n.Nm\nserves both FTP and TFTP and automatically backgrounds itself.  Usually\n.Nm\nthis means listen to port 21 (FTP) and port 69 (TFTP), serve files from\n.Pa /srv/ftp ,\nand log to syslog.  Messages are written to the syslog using the\n.Nm LOG_FTP\nfacility.\n.Pp\nAvailable command line options:\n.Bl -tag -width Ds\n.It Fl h\nShow built-in help text\n.It Fl l Ar LOG\nSet log level: none, err,\n.Ar notice ,\ninfo, debug.  By default the log level is\n.Ar notice ,\nwhich is less verbose than\n.Ar info ,\nbut still logs all relevant events: users logging in, uploading,\ndownloading, creating and removing directories, etc.  To reduce\nthe log level, start\n.Nm\nwith\n.Fl l Ar error .\n.It Fl n\nRun in foreground, do not detach from controlling terminal\n.It Fl o\nSet\n.Nm\noption, separate multiple options with comma:\n.Bl -tag\n.It Ar ftp=PORT\n.It Ar tftp=PORT\n.It Ar writable\n.It Ar pasv_addr=ADDR\n.El\n.Pp\nOverride Internet ports otherwise derived from\n.Xr services 5 .\nSet the\n.Ar PORT\nto zero (0) to disable a service.\n.Pp\nThe\n.Ar writable\noption enables writable FTP root, which is not recommended.  Some people\nwant this, but it is recommended to instead rely on a writable\nsub-directory, like\n.Ar upload/ ,\nor similar.\n.Pp\nAn address passed to the client in passive mode can be overridden with\nthe\n.Ar pasv_addr\noption (real data socket address remains unchanged). This may be useful\nfor passing through some types of NAT.\n.It Fl p Ar FILE\nFile to store process ID for signaling\n.Nm .\nThe default depends on how\n.Nm\nwas configured at build time, and also the UNIX system it runs on,\nbut often it is found in\n.Pa /var/run/uftpd.pid .\n.It Fl s\nUse syslog, even if running in foreground, default when running in the\nbackground\n.It Fl v\nShow program version\n.It Ar PATH\nRoot directory. The default is to serve files from the FTP user's $HOME.\nWhen started as root\n.Nm\nwill chroot to this directory as a security measure.\n.El\n.Pp\n.Sh Inetd\n.Nm\ncan also be used with an Internet superserver, like the traditional\ninetd or modern init replacements like finit.  In inetd mode the server\ntakes client connections from stdin.  To enable inetd mode\n.Nm\nmust be called as either\n.Nm in.tftpd\nor\n.Nm in.ftpd .\nIn inetd mode\n.Nm\nalways runs in the foreground with syslog for messages.\n.Pp\n.Sh FTP\nThe file\n.Pa /etc/nologin\ncan be used to disable FTP access.  If the file exists,\n.Nm\ndisplays it and exits.  If the file\n.Pa /etc/ftpwelcome\nexists,\n.Nm\nprints it before issuing the\n.Dq ready\nmessage.\nIf the file\n.Pa /etc/motd\nexists,\n.Nm\nprints it after a successful login.  If the file\n.Pa .message\nexists in a directory,\n.Nm\nprints it when that directory is entered.\n.Pp\nThe FTP server currently supports the following requests.\nThe case of the requests is ignored.\n.Bl -column \"Request\" -offset indent\n.It Sy Request Ta Sy \"Description\"\n.It ABOR Ta \"abort current transfer\"\n.It CDUP Ta \"shorthand for CD .. command\"\n.It CWD Ta \"change working directory\"\n.It CLNT Ta \"accepted and ignored by server\"\n.It DELE Ta \"delete a file\"\n.It EPRT Ta \"RFC 2428, extended PORT command\"\n.It EPSV Ta \"extended PASV command, used by VLC for Android\"\n.It FEAT Ta \"list supported features\"\n.It HELP Ta \"show help text\"\n.It LIST Ta \"give list files in a directory\" Pq Dq Li \"ls -lgA\"\n.It MDTM Ta \"RFC 3659, return the last-modified time of a file\"\n.It MLST Ta \"RFC 3659 extension to LIST\"\n.It MLSD Ta \"RFC 3659 extension to LIST\"\n.It MKD Ta \"make a directory\"\n.It NLST Ta \"like LIST, but much less verbose\"\n.It NOOP Ta \"do nothing, used for keep-alive\"\n.It PASS Ta \"specify password\"\n.It PASV Ta \"prepare for server-to-server transfer\"\n.It PORT Ta \"specify data connection port\"\n.It PWD Ta \"print the current working directory\"\n.It QUIT Ta \"terminate session\"\n.It REST Ta \"restore RETR or STOR command at file offset\"\n.It RETR Ta \"retrieve a file\"\n.It RMD Ta \"remove a directory\"\n.It RNFR Ta \"specify rename-from file name\"\n.It RNTO Ta \"specify rename-to file name\"\n.It SIZE Ta \"return size of file\"\n.It STOR Ta \"store a file\"\n.It SYST Ta \"show operating system type of server system\"\n.It TYPE Ta \"specify data transfer\" Em type\n.It USER Ta \"specify user name\"\n.El\n.Pp\nRemaining FTP requests, as specified in Internet RFC959, are not\nrecognized at the moment.  Patches are welcome!\n.Pp\n.Sh TFTP\n.Nm\nalso supports TFTP, the Trivial File Transfer Protocol, which is\noften used for net booting diskless devices, e.g., BOOTP and PXEBOOT.\n.Pp\nThe TFTP server currently supports the following requests.\n.Bl -column \"Request\" -offset indent\n.It Sy Request Ta Sy Description\n.It RRQ     Ta Read Request for file, may have options\n.It WRQ     Ta Write Request for file, may have options\n.It DATA    Ta File data, preceded by block n:o\n.It ERROR   Ta Error, end of session\n.It ACK     Ta ACKnowledge DATA or WRQ without options\n.It OACK    Ta Option acknowledged, sent as response to RRQ/WRQ\n.El\n.Pp\n.Nm\nsupports TFTP blocksize negotiation, according to RFC2348, so full sized\nEthernet frames can be used, which greatly speeds up transfers.\n.Pp\n.Sh FILES\n.Bl -tag -width /etc/ftpwelcome -compact\n.It Pa /etc/ftpwelcome\nFTP Welcome notice.\n.It Pa /etc/motd\nMessage of the day, presented after successful FTP login.\n.It Pa /etc/nologin\nDisplayed to user attempting to connect.  Access is refused if this\nfile exists.\n.It Pa /var/run/uftpd.pid\nProgram default PID file, created only when\n.Nm\nis ready with its internal setup and able to service signals.  Note,\n.Nm\nexits on most signals.  So no special processing is done atm.\n.El\n.Sh SEE ALSO\n.Xr ftp 1 ,\n.Xr tftp 1 ,\n.Xr syslogd 8\n.Sh AUTHORS\n.Nm\nwas written by Joachim Wiberg\n.Aq mailto:troglobit@gmail.com\nand is maintained at\n.Aq https://github.com/troglobit/uftpd\n.Sh BUGS\nHere be dragons.\n"
  },
  {
    "path": "src/.gitignore",
    "content": "uftpd"
  },
  {
    "path": "src/Makefile.am",
    "content": "sbin_PROGRAMS      = uftpd\nuftpd_SOURCES      = uftpd.c uftpd.h common.c ftpcmd.c tftpcmd.c log.c\nuftpd_CPPFLAGS     = -D_GNU_SOURCE -D_BSD_SOURCE -D_DEFAULT_SOURCE\nuftpd_CFLAGS       = -W -Wall -Wextra -Wno-unused-parameter -std=gnu99\nuftpd_CFLAGS      += $(uev_CFLAGS) $(lite_CFLAGS)\nuftpd_LDADD        = $(uev_LIBS)   $(lite_LIBS)\nSYMLINK            = in.ftpd in.tftpd\n\n# Hook in install to add uftpd --> in.ftpd, in.tftpd symlinks\ninstall-exec-hook:\n\t@for file in $(SYMLINK); do \\\n\t\tlink=$(DESTDIR)$(sbindir)/$$file; \\\n\t\ttest -e $$link && continue; \\\n\t\t$(LN_S) $(sbin_PROGRAMS) $$link; \\\n\tdone\n\nuninstall-hook:\n\t@for file in $(SYMLINK); do \\\n\t\t$(RM) $(DESTDIR)$(sbindir)/$$file; \\\n\tdone\n"
  },
  {
    "path": "src/common.c",
    "content": "/* Common methods shared between FTP and TFTP engines\n *\n * Copyright (c) 2014-2021  Joachim Wiberg <troglobit@gmail.com>\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include \"uftpd.h\"\n\nint chrooted = 0;\n\n/* Protect against common directory traversal attacks, for details see\n * https://en.wikipedia.org/wiki/Directory_traversal_attack\n *\n * Example:            /srv/ftp/ ../../etc/passwd => /etc/passwd\n *                    .~~~~~~~~ .~~~~~~~~~\n *                   /         /\n * Server dir ------'         /\n * User input ---------------'\n *\n * Forced dir ------> /srv/ftp/etc\n */\nchar *compose_path(ctrl_t *ctrl, char *path)\n{\n\tstatic char rpath[PATH_MAX];\n\tchar dir[PATH_MAX] = { 0 };\n\tchar *name, *ptr;\n\tstruct stat st;\n\n\tstrlcpy(dir, ctrl->cwd, sizeof(dir));\n\tDBG(\"Compose path from cwd: %s, arg: %s\", ctrl->cwd, path ?: \"\");\n\tif (!path || !strlen(path))\n\t\tgoto check;\n\n\tif (path[0] != '/') {\n\t\tif (dir[strlen(dir) - 1] != '/')\n\t\t\tstrlcat(dir, \"/\", sizeof(dir));\n\t}\n\tstrlcat(dir, path, sizeof(dir));\n\ncheck:\n\twhile ((ptr = strstr(dir, \"//\")))\n\t\tmemmove(ptr, &ptr[1], strlen(&ptr[1]) + 1);\n\n\tif (!chrooted) {\n\t\tsize_t len = strlen(home);\n\n//\t\tDBG(\"Server path from CWD: %s\", dir);\n\t\tif (len > 0 && home[len - 1] == '/')\n\t\t\tlen--;\n\t\tmemmove(dir + len, dir, strlen(dir) + 1);\n\t\tmemcpy(dir, home, len);\n//\t\tDBG(\"Resulting non-chroot path: %s\", dir);\n\t}\n\n\t/*\n\t * Handle directories slightly differently, since dirname() on a\n\t * directory returns the parent directory.  So, just squash ..\n\t */\n\tif (!stat(dir, &st) && S_ISDIR(st.st_mode)) {\n\t\tif (!realpath(dir, rpath))\n\t\t\treturn NULL;\n\t} else {\n\t\t/*\n\t\t * Check realpath() of directory containing the file, a\n\t\t * STOR may want to save a new file.  Then append the\n\t\t * file and return it.\n\t\t */\n\t\tname = basename(path);\n\t\tptr = dirname(dir);\n\n\t\tmemset(rpath, 0, sizeof(rpath));\n\t\tif (!realpath(ptr, rpath)) {\n\t\t\tINFO(\"Failed realpath(%s): %m\", ptr);\n\t\t\treturn NULL;\n\t\t}\n\n//\t\tDBG(\"realpath(%s) => %s\", ptr, rpath);\n\n\t\tif (rpath[1] != 0)\n\t\t\tstrlcat(rpath, \"/\", sizeof(rpath));\n\t\tstrlcat(rpath, name, sizeof(rpath));\n\t}\n\n\tif (!chrooted && strncmp(rpath, home, strlen(home))) {\n\t\tDBG(\"Failed non-chroot dir:%s vs home:%s\", dir, home);\n\t\treturn NULL;\n\t}\n\n\tDBG(\"Final path to file: %s\", rpath);\n\n\treturn rpath;\n}\n\nchar *compose_abspath(ctrl_t *ctrl, char *path)\n{\n\tchar *ptr;\n\tchar cwd[sizeof(ctrl->cwd)];\n\n\tif (path && path[0] == '/') {\n\t\tstrlcpy(cwd, ctrl->cwd, sizeof(cwd));\n\t\tmemset(ctrl->cwd, 0, sizeof(ctrl->cwd));\n\t}\n\n\tptr = compose_path(ctrl, path);\n\n\tif (path && path[0] == '/')\n\t\tstrlcpy(ctrl->cwd, cwd, sizeof(ctrl->cwd));\n\n\treturn ptr;\n}\n\nint set_nonblock(int fd)\n{\n\tint flags;\n\n\tflags = fcntl(fd, F_GETFL, 0);\n\tif (!flags)\n\t\t(void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);\n\n\treturn fd;\n}\n\nint open_socket(int port, int type, char *desc)\n{\n\tint sd, err, val = 1;\n\tsocklen_t len = sizeof(struct sockaddr);\n\tstruct sockaddr_in server;\n\n\tsd = socket(AF_INET, type | SOCK_NONBLOCK, 0);\n\tif (sd < 0) {\n\t\tWARN(errno, \"Failed creating %s server socket\", desc);\n\t\treturn -1;\n\t}\n\n\terr = setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val));\n\tif (err != 0)\n\t\tWARN(errno, \"Failed setting SO_REUSEADDR on %s socket\", type == SOCK_DGRAM ? \"TFTP\" : \"FTP\");\n\n\tmemset(&server, 0, sizeof(server));\n\tserver.sin_family      = AF_INET;\n\tserver.sin_addr.s_addr = INADDR_ANY;\n\tserver.sin_port        = htons(port);\n\tif (bind(sd, (struct sockaddr *)&server, len) < 0) {\n\t\tif (EACCES != errno) {\n\t\t\tWARN(errno, \"Failed binding to port %d, maybe another %s server is already running\", port, desc);\n\t\t}\n\t\tclose(sd);\n\n\t\treturn -1;\n\t}\n\n\tif (port && type != SOCK_DGRAM) {\n\t\tif (-1 == listen(sd, 20))\n\t\t\tWARN(errno, \"Failed starting %s server\", desc);\n\t}\n\n\tDBG(\"Opened socket for port %d\", port);\n\n\treturn sd;\n}\n\nvoid convert_address(struct sockaddr_storage *ss, char *buf, size_t len)\n{\n\tswitch (ss->ss_family) {\n\tcase AF_INET:\n\t\tinet_ntop(ss->ss_family,\n\t\t\t  &((struct sockaddr_in *)ss)->sin_addr, buf, len);\n\t\tbreak;\n\n\tcase AF_INET6:\n\t\tinet_ntop(ss->ss_family,\n\t\t\t  &((struct sockaddr_in6 *)ss)->sin6_addr, buf, len);\n\t\tbreak;\n\t}\n}\n\n/* Inactivity timer, bye bye */\nstatic void inactivity_cb(uev_t *w, void *arg, int events)\n{\n\tuev_ctx_t *ctx = (uev_ctx_t *)arg;\n\n\tINFO(\"Inactivity timer, exiting ...\");\n\tuev_exit(ctx);\n}\n\nctrl_t *new_session(uev_ctx_t *ctx, int sd, int *rc)\n{\n\tctrl_t *ctrl = NULL;\n\tstatic int privs_dropped = 0;\n\n\tif (!inetd) {\n\t\tpid_t pid = fork();\n\n\t\tif (pid) {\n\t\t\tDBG(\"Created new client session as PID %d\", pid);\n\t\t\t*rc = pid;\n\t\t\treturn NULL;\n\t\t}\n\n\t\t/*\n\t\t * Set process group to parent, so uftpd can call\n\t\t * killpg() on all of us when it exits.\n\t\t */\n\t\tsetpgid(0, getppid());\n\t\t/* Create new uEv context for the child. */\n\t\tctx = calloc(1, sizeof(uev_ctx_t));\n\t\tif (!ctx) {\n\t\t\tERR(errno, \"Failed allocating session event context\");\n\t\t\texit(1);\n\t\t}\n\n\t\tuev_init(ctx);\n\t}\n\n\tctrl = calloc(1, sizeof(ctrl_t));\n\tif (!ctrl) {\n\t\tERR(errno, \"Failed allocating session context\");\n\t\tgoto fail;\n\t}\n\n\tctrl->sd = set_nonblock(sd);\n\tctrl->ctx = ctx;\n\tstrlcpy(ctrl->cwd, \"/\", sizeof(ctrl->cwd));\n\n\t/* Chroot to FTP root */\n\tif (!chrooted && geteuid() == 0) {\n\t\tif (chroot(home) || chdir(\"/\")) {\n\t\t\tERR(errno, \"Failed chrooting to FTP root, %s, aborting\", home);\n\t\t\tgoto fail;\n\t\t}\n\t\tchrooted = 1;\n\t} else if (!chrooted) {\n\t\tif (chdir(home)) {\n\t\t\tWARN(errno, \"Failed changing to FTP root, %s, aborting\", home);\n\t\t\tgoto fail;\n\t\t}\n\t}\n\n\t/* If ftp user exists and we're running as root we can drop privs */\n\tif (!privs_dropped && pw && geteuid() == 0) {\n\t\tint fail1, fail2;\n\n\t\tinitgroups(pw->pw_name, pw->pw_gid);\n\t\tif ((fail1 = setegid(pw->pw_gid)))\n\t\t\tWARN(errno, \"Failed dropping group privileges to gid %d\", pw->pw_gid);\n\t\tif ((fail2 = seteuid(pw->pw_uid)))\n\t\t\tWARN(errno, \"Failed dropping user privileges to uid %d\", pw->pw_uid);\n\n\t\tsetenv(\"HOME\", pw->pw_dir, 1);\n\n\t\tif (!fail1 && !fail2)\n\t\t\tINFO(\"Successfully dropped privilges to %d:%d (uid:gid)\", pw->pw_uid, pw->pw_gid);\n\n\t\t/*\n\t\t * Check we don't have write access to the FTP root,\n\t\t * unless explicitly allowed\n\t\t */\n\t\tif (!do_insecure && !access(home, W_OK)) {\n\t\t\tERR(0, \"FTP root %s writable, possible security violation, aborting session!\", home);\n\t\t\tgoto fail;\n\t\t}\n\n\t\t/* On failure, we tried at least.  Only warn once. */\n\t\tprivs_dropped = 1;\n\t}\n\n\t/* Session timeout handler */\n\tuev_timer_init(ctrl->ctx, &ctrl->timeout_watcher, inactivity_cb, ctrl->ctx, INACTIVITY_TIMER, 0);\n\n\treturn ctrl;\nfail:\n\tif (ctrl)\n\t\tfree(ctrl);\n\tif (!inetd)\n\t\tfree(ctx);\n\t*rc = -1;\n\n\treturn NULL;\n}\n\nint del_session(ctrl_t *ctrl, int isftp)\n{\n\tDBG(\"%sFTP Client session ended.\", isftp ? \"\": \"T\" );\n\n\tif (!ctrl)\n\t\treturn -1;\n\n\tif (isftp && ctrl->sd > 0) {\n\t\tshutdown(ctrl->sd, SHUT_RDWR);\n\t\tclose(ctrl->sd);\n\t}\n\n\tif (ctrl->data_listen_sd > 0) {\n\t\tshutdown(ctrl->data_listen_sd, SHUT_RDWR);\n\t\tclose(ctrl->data_listen_sd);\n\t}\n\n\tif (ctrl->data_sd > 0) {\n\t\tshutdown(ctrl->data_sd, SHUT_RDWR);\n\t\tclose(ctrl->data_sd);\n\t}\n\n\tif (ctrl->buf)\n\t\tfree(ctrl->buf);\n\n\tif (!inetd && ctrl->ctx)\n\t\tfree(ctrl->ctx);\n\tfree(ctrl);\n\n\treturn 0;\n}\n\n/**\n * Local Variables:\n *  indent-tabs-mode: t\n *  c-file-style: \"linux\"\n * End:\n */\n"
  },
  {
    "path": "src/ftpcmd.c",
    "content": "/* FTP engine\n *\n * Copyright (c) 2014-2021  Joachim Wiberg <troglobit@gmail.com>\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include \"uftpd.h\"\n#include <ctype.h>\n#include <arpa/ftp.h>\n#ifdef HAVE_SYS_TIME_H\n# include <sys/time.h>\n#endif\n\n#define LISTMODE_LIST 0\n#define LISTMODE_NLST 1\n#define LISTMODE_MLST 2\n#define LISTMODE_MLSD 3\n\ntypedef struct {\n\tchar *command;\n\tvoid (*cb)(ctrl_t *ctr, char *arg);\n} ftp_cmd_t;\n\nstatic ftp_cmd_t supported[];\n\nstatic void do_PORT(ctrl_t *ctrl, pend_t pending);\nstatic void do_LIST(uev_t *w, void *arg, int events);\nstatic void do_RETR(uev_t *w, void *arg, int events);\nstatic void do_STOR(uev_t *w, void *arg, int events);\n\nstatic int is_cont(char *msg)\n{\n\tchar *ptr;\n\n\tptr = strchr(msg, '\\r');\n\tif (ptr) {\n\t\tptr++;\n\t\tif (strchr(ptr, '\\r'))\n\t\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\nstatic int send_msg(int sd, char *msg)\n{\n\tint n = 0;\n\tint l;\n\n\tif (!msg) {\n\terr:\n\t\tERR(EINVAL, \"Missing argument to send_msg()\");\n\t\treturn 1;\n\t}\n\n\tl = strlen(msg);\n\tif (l <= 0)\n\t\tgoto err;\n\n\twhile (n < l) {\n\t\tint result = send(sd, msg + n, l, 0);\n\n\t\tif (result < 0) {\n\t\t\tERR(errno, \"Failed sending message to client\");\n\t\t\treturn 1;\n\t\t}\n\n\t\tn += result;\n\t}\n\n\tDBG(\"Sent: %s%s\", is_cont(msg) ? \"\\n\" : \"\", msg);\n\n\treturn 0;\n}\n\n/*\n * Receive message from client, split into command and argument\n */\nstatic int recv_msg(int sd, char *msg, size_t len, char **cmd, char **argument)\n{\n\tchar *ptr;\n\tssize_t bytes;\n\tuint8_t *raw = (uint8_t *)msg;\n\n\t/* Clear for every new command. */\n\tmemset(msg, 0, len);\n\n\t/* Save one byte (-1) for NUL termination */\n\tbytes = recv(sd, msg, len - 1, 0);\n\tif (bytes < 0) {\n\t\tif (EINTR == errno)\n\t\t\treturn 1;\n\n\t\tif (ECONNRESET == errno)\n\t\t\tDBG(\"Connection reset by client.\");\n\t\telse\n\t\t\tERR(errno, \"Failed reading client command\");\n\t\treturn 1;\n\t}\n\n\tif (!bytes) {\n\t\tINFO(\"Client disconnected.\");\n\t\treturn 1;\n\t}\n\n\tif (raw[0] == 0xff) {\n\t\tchar tmp[4];\n\t\tchar buf[20] = { 0 };\n\t\tint i;\n\n\t\ti = recv(sd, &msg[bytes], len - bytes - 1, MSG_OOB | MSG_DONTWAIT);\n\t\tif (i > 0)\n\t\t\tbytes += i;\n\n\t\tfor (i = 0; i < bytes; i++) {\n\t\t\tsnprintf(tmp, sizeof(tmp), \"%2X%s\", raw[i], i + 1 < bytes ? \" \" : \"\");\n\t\t\tstrlcat(buf, tmp, sizeof(buf));\n\t\t}\n\n\t\tstrlcpy(msg, buf, len);\n\t\t*cmd      = msg;\n\t\t*argument = NULL;\n\n\t\tDBG(\"Recv: [%s], %zd bytes\", msg, bytes);\n\n\t\treturn 0;\n\t}\n\n\t/* NUL terminate for strpbrk() */\n\tmsg[bytes] = 0;\n\n\t*cmd = msg;\n\tptr  = strpbrk(msg, \" \");\n\tif (ptr) {\n\t\t*ptr = 0;\n\t\tptr++;\n\t\t*argument = ptr;\n\t} else {\n\t\t*argument = NULL;\n\t\tptr = msg;\n\t}\n\n\tptr = strpbrk(ptr, \"\\r\\n\");\n\tif (ptr)\n\t\t*ptr = 0;\n\n\t/* Convert command to std ftp upper case, issue #18 */\n\tfor (ptr = msg; *ptr; ++ptr) *ptr = toupper(*ptr);\n\n\tDBG(\"Recv: %s %s\", *cmd, *argument ?: \"\");\n\n\treturn 0;\n}\n\nstatic int open_data_connection(ctrl_t *ctrl)\n{\n\tsocklen_t len = sizeof(struct sockaddr);\n\tstruct sockaddr_in sin = { 0 };\n\n\t/* Previous PORT command from client */\n\tif (ctrl->data_address[0]) {\n\t\tint rc;\n\n\t\tctrl->data_sd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);\n\t\tif (-1 == ctrl->data_sd) {\n\t\t\tERR(errno, \"Failed creating data socket\");\n\t\t\treturn -1;\n\t\t}\n\n\t\tmemset(&sin, 0, sizeof(sin));\n\t\tsin.sin_family = AF_INET;\n\t\tsin.sin_port = htons(ctrl->data_port);\n\t\tinet_aton(ctrl->data_address, &(sin.sin_addr));\n\n\t\trc = connect(ctrl->data_sd, (struct sockaddr *)&sin, len);\n\t\tif (rc == -1 && EINPROGRESS != errno) {\n\t\t\tERR(errno, \"Failed connecting data socket to client\");\n\t\t\tclose(ctrl->data_sd);\n\t\t\tctrl->data_sd = -1;\n\n\t\t\treturn -1;\n\t\t}\n\n\t\tDBG(\"Connected successfully to client's previously requested address:PORT %s:%d\",\n\t\t    ctrl->data_address, ctrl->data_port);\n\t\treturn 0;\n\t}\n\n\t/* Previous PASV command, accept connect from client */\n\tif (ctrl->data_listen_sd > 0) {\n\t\tconst int const_int_1 = 1;\n\t\tchar client_ip[100];\n\t\tint retries = 3;\n\n\tretry:\n\t\tctrl->data_sd = accept(ctrl->data_listen_sd, (struct sockaddr *)&sin, &len);\n\t\tif (-1 == ctrl->data_sd) {\n\t\t\tif (EAGAIN == errno && --retries) {\n\t\t\t\tsleep(1);\n\t\t\t\tgoto retry;\n\t\t\t}\n\n\t\t\tERR(errno, \"Failed accepting connection from client\");\n\t\t\treturn -1;\n\t\t}\n\n\t\tsetsockopt(ctrl->data_sd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));\n\t\tset_nonblock(ctrl->data_sd);\n\n\t\tinet_ntop(AF_INET, &(sin.sin_addr), client_ip, INET_ADDRSTRLEN);\n\t\tDBG(\"Client PASV data connection from %s:%d\", client_ip, ntohs(sin.sin_port));\n\n\t\tclose(ctrl->data_listen_sd);\n\t\tctrl->data_listen_sd = -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int close_data_connection(ctrl_t *ctrl)\n{\n\tint ret = 0;\n\n\tDBG(\"Closing data connection ...\");\n\n\t/* PASV server listening socket */\n\tif (ctrl->data_listen_sd > 0) {\n\t\tshutdown(ctrl->data_listen_sd, SHUT_RDWR);\n\t\tclose(ctrl->data_listen_sd);\n\t\tctrl->data_listen_sd = -1;\n\t\tret++;\n\t}\n\n\t/* PASV client socket */\n\tif (ctrl->data_sd > 0) {\n\t\tshutdown(ctrl->data_sd, SHUT_RDWR);\n\t\tclose(ctrl->data_sd);\n\t\tctrl->data_sd = -1;\n\t\tret++;\n\t}\n\n\t/* PORT */\n\tif (ctrl->data_address[0]) {\n\t\tctrl->data_address[0] = 0;\n\t\tctrl->data_port = 0;\n\t}\n\n\treturn ret;\n}\n\nstatic int check_user_pass(ctrl_t *ctrl)\n{\n\tif (!ctrl->name[0])\n\t\treturn -1;\n\n\tif (!strcmp(\"anonymous\", ctrl->name))\n\t\treturn 1;\n\n\treturn 0;\n}\n\nstatic int do_abort(ctrl_t *ctrl)\n{\n\tif (ctrl->d || ctrl->d_num) {\n\t\tuev_io_stop(&ctrl->data_watcher);\n\t\tif (ctrl->d_num > 0) {\n\t\t\tint i;\n\n\t\t\tfor (i = 0; i < ctrl->d_num; i++)\n\t\t\t\tfree(ctrl->d[i]);\n\t\t\tfree(ctrl->d);\n\t\t}\n\t\tctrl->d_num = 0;\n\t\tctrl->d = NULL;\n\t\tctrl->i = 0;\n\n\t\tif (ctrl->file)\n\t\t\tfree(ctrl->file);\n\t\tctrl->file = NULL;\n\t}\n\n\tif (ctrl->file) {\n\t\tuev_io_stop(&ctrl->data_watcher);\n\t\tfree(ctrl->file);\n\t\tctrl->file = NULL;\n\t}\n\n\tif (ctrl->fp) {\n\t\tfclose(ctrl->fp);\n\t\tctrl->fp = NULL;\n\t}\n\n\tctrl->pending = PENDING_NONE;\n\tctrl->offset = 0;\n\n\treturn close_data_connection(ctrl);\n}\n\nstatic void handle_ABOR(ctrl_t *ctrl, char *arg)\n{\n\tDBG(\"Aborting any current transfer ...\");\n\tif (do_abort(ctrl))\n\t\tsend_msg(ctrl->sd, \"426 Connection closed; transfer aborted.\\r\\n\");\n\n\tsend_msg(ctrl->sd, \"226 Closing data connection.\\r\\n\");\n}\n\nstatic void handle_USER(ctrl_t *ctrl, char *name)\n{\n\tif (ctrl->name[0]) {\n\t\tctrl->name[0] = 0;\n\t\tctrl->pass[0] = 0;\n\t}\n\n\tif (name) {\n\t\tstrlcpy(ctrl->name, name, sizeof(ctrl->name));\n\t\tif (check_user_pass(ctrl) == 1) {\n\t\t\tINFO(\"Guest logged in from %s\", ctrl->clientaddr);\n\t\t\tsend_msg(ctrl->sd, \"230 Guest login OK, access restrictions apply.\\r\\n\");\n\t\t} else {\n\t\t\tsend_msg(ctrl->sd, \"331 Login OK, please enter password.\\r\\n\");\n\t\t}\n\t} else {\n\t\tsend_msg(ctrl->sd, \"530 You must input your name.\\r\\n\");\n\t}\n}\n\nstatic void handle_PASS(ctrl_t *ctrl, char *pass)\n{\n\tif (!ctrl->name[0]) {\n\t\tsend_msg(ctrl->sd, \"503 No username given.\\r\\n\");\n\t\treturn;\n\t}\n\n        if (!pass) {\n                send_msg(ctrl->sd, \"503 No password given.\\r\\n\");\n                return;\n        }\n\n\tstrlcpy(ctrl->pass, pass, sizeof(ctrl->pass));\n\tif (check_user_pass(ctrl) < 0) {\n\t\tLOG(\"User %s from %s, invalid password!\", ctrl->name, ctrl->clientaddr);\n\t\tsend_msg(ctrl->sd, \"530 username or password is unacceptable\\r\\n\");\n\t\treturn;\n\t}\n\n\tINFO(\"User %s login from %s\", ctrl->name, ctrl->clientaddr);\n\tsend_msg(ctrl->sd, \"230 Guest login OK, access restrictions apply.\\r\\n\");\n}\n\nstatic void handle_SYST(ctrl_t *ctrl, char *arg)\n{\n\tchar system[] = \"215 UNIX Type: L8\\r\\n\";\n\n\tsend_msg(ctrl->sd, system);\n}\n\nstatic void handle_TYPE(ctrl_t *ctrl, char *argument)\n{\n\tchar type[24]  = \"200 Type set to I.\\r\\n\";\n\tchar unknown[] = \"501 Invalid argument to TYPE.\\r\\n\";\n\n\tif (!argument)\n\t\targument = \"Z\";\n\n\tswitch (argument[0]) {\n\tcase 'A':\n\t\tctrl->type = TYPE_A; /* ASCII */\n\t\tbreak;\n\n\tcase 'I':\n\t\tctrl->type = TYPE_I; /* IMAGE/BINARY */\n\t\tbreak;\n\n\tdefault:\n\t\tsend_msg(ctrl->sd, unknown);\n\t\treturn;\n\t}\n\n\ttype[16] = argument[0];\n\tsend_msg(ctrl->sd, type);\n}\n\nstatic void handle_PWD(ctrl_t *ctrl, char *arg)\n{\n\tchar buf[sizeof(ctrl->cwd) + 10];\n\n\tsnprintf(buf, sizeof(buf), \"257 \\\"%s\\\"\\r\\n\", ctrl->cwd);\n\tsend_msg(ctrl->sd, buf);\n}\n\nstatic void handle_CWD(ctrl_t *ctrl, char *path)\n{\n\tstruct stat st;\n\tchar *dir;\n\n\tif (!path)\n\t\tgoto done;\n\n\t/*\n\t * Some FTP clients, most notably Chrome, use CWD to check if an\n\t * entry is a file or directory.\n\t */\n\tdir = compose_abspath(ctrl, path);\n\tif (!dir || stat(dir, &st) || !S_ISDIR(st.st_mode)) {\n\t\tINFO(\"%s: CWD: invalid path to %s: %m\", ctrl->clientaddr, path);\n\t\tsend_msg(ctrl->sd, \"550 No such directory.\\r\\n\");\n\t\treturn;\n\t}\n\n\tif (!chrooted)\n\t\tdir += strlen(home);\n\n\tsnprintf(ctrl->cwd, sizeof(ctrl->cwd), \"%s\", dir);\n\tif (ctrl->cwd[0] == 0)\n\t\tsnprintf(ctrl->cwd, sizeof(ctrl->cwd), \"/\");\n\ndone:\n\tDBG(\"New CWD: '%s'\", ctrl->cwd);\n\tsend_msg(ctrl->sd, \"250 OK\\r\\n\");\n}\n\nstatic void handle_CDUP(ctrl_t *ctrl, char *path)\n{\n\thandle_CWD(ctrl, \"..\");\n}\n\nstatic void handle_PORT(ctrl_t *ctrl, char *str)\n{\n\tint a, b, c, d, e, f;\n\tchar addr[INET_ADDRSTRLEN];\n\tstruct sockaddr_in sin;\n\n\tif (ctrl->data_sd > 0) {\n\t\tuev_io_stop(&ctrl->data_watcher);\n\t\tclose(ctrl->data_sd);\n\t\tctrl->data_sd = -1;\n\t}\n\n        if (!str) {\n                send_msg(ctrl->sd, \"500 No PORT specified.\\r\\n\");\n                return;\n        }\n\n\t/* Convert PORT command's argument to IP address + port */\n\tsscanf(str, \"%d,%d,%d,%d,%d,%d\", &a, &b, &c, &d, &e, &f);\n\tsnprintf(addr, sizeof(addr), \"%d.%d.%d.%d\", a, b, c, d);\n\n\t/* Check IPv4 address using inet_aton(), throw away converted result */\n\tif (!inet_aton(addr, &(sin.sin_addr))) {\n\t\tERR(0, \"Invalid address '%s' given to PORT command\", addr);\n\t\tsend_msg(ctrl->sd, \"500 Illegal PORT command.\\r\\n\");\n\t\treturn;\n\t}\n\n\tstrlcpy(ctrl->data_address, addr, sizeof(ctrl->data_address));\n\tctrl->data_port = e * 256 + f;\n\n\tDBG(\"Client PORT command accepted for %s:%d\", ctrl->data_address, ctrl->data_port);\n\tsend_msg(ctrl->sd, \"200 PORT command successful.\\r\\n\");\n}\n\nstatic void handle_EPRT(ctrl_t *ctrl, char *str)\n{\n\tsend_msg(ctrl->sd, \"502 Command not implemented.\\r\\n\");\n}\n\nstatic char *mode_to_str(mode_t m)\n{\n\tstatic char str[11];\n\n\tsnprintf(str, sizeof(str), \"%c%c%c%c%c%c%c%c%c%c\",\n\t\t S_ISDIR(m)    ? 'd' : '-',\n\t\t (m & S_IRUSR) ? 'r' : '-',\n\t\t (m & S_IWUSR) ? 'w' : '-',\n\t\t (m & S_IXUSR) ? 'x' : '-',\n\t\t (m & S_IRGRP) ? 'r' : '-',\n\t\t (m & S_IWGRP) ? 'w' : '-',\n\t\t (m & S_IXGRP) ? 'x' : '-',\n\t\t (m & S_IROTH) ? 'r' : '-',\n\t\t (m & S_IWOTH) ? 'w' : '-',\n\t\t (m & S_IXOTH) ? 'x' : '-');\n\n\treturn str;\n}\n\nstatic char *time_to_str(time_t mtime)\n{\n\tstruct tm *t = localtime(&mtime);\n\tstatic char str[20];\n\n\tsetlocale(LC_TIME, \"C\");\n\tstrftime(str, sizeof(str), \"%b %e %H:%M\", t);\n\n\treturn str;\n}\n\nstatic char *mlsd_time(time_t mtime)\n{\n\tstruct tm *t = localtime(&mtime);\n\tstatic char str[20];\n\n\tstrftime(str, sizeof(str), \"%Y%m%d%H%M%S\", t);\n\n\treturn str;\n}\n\nstatic const char *mlsd_type(char *name, int mode)\n{\n\tif (!strcmp(name, \".\"))\n\t\treturn \"cdir\";\n\tif (!strcmp(name, \"..\"))\n\t\treturn \"pdir\";\n\n\treturn S_ISDIR(mode) ? \"dir\" : \"file\";\n}\n\nvoid mlsd_fact(char fact, char *buf, size_t len, char *name, char *perms, struct stat *st)\n{\n\tchar size[20];\n\n\tswitch (fact) {\n\tcase 'm':\n\t\tstrlcat(buf, \"modify=\", len);\n\t\tstrlcat(buf, mlsd_time(st->st_mtime), len);\n\t\tbreak;\n\n\tcase 'p':\n\t\tstrlcat(buf, \"perm=\", len);\n\t\tstrlcat(buf, perms, len);\n\t\tbreak;\n\n\tcase 't':\n\t\tstrlcat(buf, \"type=\", len);\n\t\tstrlcat(buf, mlsd_type(name, st->st_mode), len);\n\t\tbreak;\n\n\n\tcase 's':\n\t\tif (S_ISDIR(st->st_mode))\n\t\t\treturn;\n\t\tsnprintf(size, sizeof(size), \"size=%\" PRIu64, st->st_size);\n\t\tstrlcat(buf, size, len);\n\t\tbreak;\n\n\tdefault:\n\t\treturn;\n\t}\n\n\tstrlcat(buf, \";\", len);\n}\n\nstatic void mlsd_printf(ctrl_t *ctrl, char *buf, size_t len, char *path, char *name, struct stat *st)\n{\n\tchar perms[10] = \"\";\n\tint ro = !access(path, R_OK);\n\tint rw = !access(path, W_OK);\n\n\tif (S_ISDIR(st->st_mode)) {\n\t\t/* XXX: Verify 'e' by checking that we can CD to the 'name' */\n\t\tif (ro)\n\t\t\tstrlcat(perms, \"le\", sizeof(perms));\n\t\tif (rw)\n\t\t\tstrlcat(perms, \"pc\", sizeof(perms)); /* 'd' RMD, 'm' MKD */\n\t} else {\n\t\tif (ro)\n\t\t\tstrlcat(perms, \"r\", sizeof(perms));\n\t\tif (rw)\n\t\t\tstrlcat(perms, \"w\", sizeof(perms)); /* 'f' RNFR, 'd' DELE */\n\t}\n\n\tmemset(buf, 0, len);\n\tif (ctrl->d_num == -1 && ctrl->list_mode == LISTMODE_MLST)\n\t\tstrlcat(buf, \" \", len);\n\n\tfor (int i = 0; ctrl->facts[i]; i++)\n\t\tmlsd_fact(ctrl->facts[i], buf, len, name, perms, st);\n\n\tstrlcat(buf, \" \", len);\n\tstrlcat(buf, name, len);\n\tstrlcat(buf, \"\\r\\n\", len);\n}\n\nstatic int list_printf(ctrl_t *ctrl, char *buf, size_t len, char *path, char *name)\n{\n\tstruct stat st;\n\n\tif (stat(path, &st))\n\t\treturn -1;\n\n\tswitch (ctrl->list_mode) {\n\tcase LISTMODE_MLSD:\n\t\t/* fallthrough */\n\tcase LISTMODE_MLST:\n\t\tmlsd_printf(ctrl, buf, len, path, name, &st);\n\t\tbreak;\n\n\tcase LISTMODE_NLST:\n\t\tsnprintf(buf, len, \"%s\\r\\n\", name);\n\t\tbreak;\n\n\tcase LISTMODE_LIST:\n\t\tsnprintf(buf, len, \"%s 1 %5d %5d %12\" PRIu64 \" %s %s\\r\\n\",\n\t\t\t mode_to_str(st.st_mode),\n\t\t\t 0, 0, (uint64_t)st.st_size,\n\t\t\t time_to_str(st.st_mtime), name);\n\t\tbreak;\n\t}\n\n\treturn 0;\n}\n\nstatic void do_MLST(ctrl_t *ctrl)\n{\n\tchar buf[512] = { 0 };\n\tchar cwd[PATH_MAX];\n\tint sd = ctrl->sd;\n\tchar *path;\n\tint len;\n\n\tif (ctrl->data_sd != -1)\n\t\tsd = ctrl->data_sd;\n\n\tlen = snprintf(buf, sizeof(buf), \"250- Listing %s\\r\\n\", ctrl->file);\n\tif (len < 0 || len > (int)sizeof(buf))\n\t\tgoto abort;\n\n\tstrlcpy(cwd, ctrl->file, sizeof(cwd));\n\tpath = compose_path(ctrl, cwd);\n\tif (!path)\n\t\tgoto abort;\n\n\tif (list_printf(ctrl, &buf[len], sizeof(buf) -  len, path, basename(ctrl->file))) {\n\tabort:\n\t\tdo_abort(ctrl);\n\t\tsend_msg(ctrl->sd, \"550 No such file or directory.\\r\\n\");\n\t\treturn;\n\t}\n\n\tstrlcat(buf, \"250 End.\\r\\n\", sizeof(buf));\n\tsend_msg(sd, buf);\n\tdo_abort(ctrl);\n}\n\nstatic void do_MLSD(ctrl_t *ctrl)\n{\n\tchar buf[512] = { 0 };\n\tchar cwd[PATH_MAX];\n\tchar *path;\n\n\tstrlcpy(cwd, ctrl->file, sizeof(cwd));\n\tpath = compose_path(ctrl, cwd);\n\tif (!path)\n\t\tgoto abort;\n\n\tif (list_printf(ctrl, buf, sizeof(buf), path, basename(path))) {\n\tabort:\n\t\tdo_abort(ctrl);\n\t\tsend_msg(ctrl->sd, \"550 No such file or directory.\\r\\n\");\n\t\treturn;\n\t}\n\n\tsend_msg(ctrl->data_sd, buf);\n\tdo_abort(ctrl);\n\tsend_msg(ctrl->sd, \"226 Transfer complete.\\r\\n\");\n}\n\nstatic void do_LIST(uev_t *w, void *arg, int events)\n{\n\tctrl_t *ctrl = (ctrl_t *)arg;\n\tstruct timeval tv;\n\tssize_t bytes;\n\tchar buf[BUFFER_SIZE] = { 0 };\n\n\tif (UEV_ERROR == events || UEV_HUP == events) {\n\t\tuev_io_start(w);\n\t\treturn;\n\t}\n\n\t/* Reset inactivity timer. */\n\tuev_timer_set(&ctrl->timeout_watcher, INACTIVITY_TIMER, 0);\n\n\tif (ctrl->d_num == -1) {\n\t\tif (ctrl->list_mode == LISTMODE_MLST)\n\t\t\tdo_MLST(ctrl);\n\t\telse\n\t\t\tdo_MLSD(ctrl);\n\t\treturn;\n\t}\n\n\tgettimeofday(&tv, NULL);\n\tif (tv.tv_sec - ctrl->tv.tv_sec > 3) {\n\t\tDBG(\"Sending LIST entry %d of %d to %s ...\", ctrl->i, ctrl->d_num, ctrl->clientaddr);\n\t\tctrl->tv.tv_sec = tv.tv_sec;\n\t}\n\n\twhile (ctrl->i < ctrl->d_num) {\n\t\tstruct dirent *entry;\n\t\tchar cwd[PATH_MAX];\n\t\tchar *name, *path;\n\t\tsize_t len;\n\n\t\tentry = ctrl->d[ctrl->i++];\n\t\tname  = entry->d_name;\n\n\t\tDBG(\"Found directory entry %s\", name);\n\t\tif (!strcmp(name, \".\") || !strcmp(name, \"..\"))\n\t\t\tcontinue;\n\n\t\tlen = strlen(ctrl->file);\n\t\tsnprintf(cwd, sizeof(cwd), \"%s%s%s\", ctrl->file,\n\t\t\t ctrl->file[len > 0 ? len - 1 : len] == '/' ? \"\" : \"/\", name);\n\n\t\tpath = compose_path(ctrl, cwd);\n\t\tif (!path) {\n\t\tfail:\n\t\t\tINFO(\"%s: LIST: Failed reading status for %s: %m\", ctrl->clientaddr, path ? path : name);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (list_printf(ctrl, buf, sizeof(buf), path, name))\n\t\t\tgoto fail;\n\n\t\tDBG(\"LIST %s\", buf);\n\n\t\tbytes = send(ctrl->data_sd, buf, strlen(buf), 0);\n\t\tif (-1 == bytes) {\n\t\t\tif (ECONNRESET == errno)\n\t\t\t\tDBG(\"Connection reset by client.\");\n\t\t\telse\n\t\t\t\tERR(errno, \"Failed sending file %s to client\", ctrl->file);\n\n\t\t\tdo_abort(ctrl);\n\t\t\tsend_msg(ctrl->sd, \"426 TCP connection was established but then broken!\\r\\n\");\n\t\t}\n\n\t\treturn;\n\t}\n\n\tdo_abort(ctrl);\n\tsend_msg(ctrl->sd, \"226 Transfer complete.\\r\\n\");\n}\n\nstatic const char *mode2op(int mode)\n{\n\tswitch (mode) {\n\tcase LISTMODE_LIST: return \"LIST\";\n\tcase LISTMODE_NLST: return \"NLST\";\n\tcase LISTMODE_MLST: return \"MLST\";\n\tcase LISTMODE_MLSD: return \"MLSD\";\n\t}\n\n\treturn \"LST?\";\n}\n\nstatic void list(ctrl_t *ctrl, char *arg, int mode)\n{\n\tchar *path;\n\n\tif (string_valid(arg)) {\n\t\tchar *ptr, *quot;\n\n\t\t/* Check if client sends ls arguments ... */\n\t\tptr = arg;\n\t\twhile (*ptr) {\n\t\t\tif (isspace(*ptr))\n\t\t\t\tptr++;\n\n\t\t\tif (*ptr == '-') {\n\t\t\t\twhile (*ptr && !isspace(*ptr))\n\t\t\t\t\tptr++;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\n\t\t/* Strip any \"\" from \"<arg>\" */\n\t\twhile ((quot = strchr(ptr, '\"'))) {\n\t\t\tchar *ptr2;\n\n\t\t\tptr2 = strchr(&quot[1], '\"');\n\t\t\tif (!ptr2)\n\t\t\t\tbreak;\n\n\t\t\tmemmove(ptr2, &ptr2[1], strlen(ptr2));\n\t\t\tmemmove(quot, &quot[1], strlen(quot));\n\t\t}\n\t\targ = ptr;\n\t}\n\n\tif (mode >= LISTMODE_MLST)\n\t\tpath = compose_abspath(ctrl, arg);\n\telse\n\t\tpath = compose_path(ctrl, arg);\n\tif (!path) {\n\t\tINFO(\"%s: %s: invalid path to %s: %m\", ctrl->clientaddr, mode2op(mode), arg);\n\t\tsend_msg(ctrl->sd, \"550 No such file or directory.\\r\\n\");\n\t\treturn;\n\t}\n\n\tctrl->list_mode = mode;\n\tctrl->file = strdup(arg ? arg : \"\");\n\tctrl->i = 0;\n\tctrl->d_num = scandir(path, &ctrl->d, NULL, alphasort);\n\tif (ctrl->d_num == -1) {\n\t\tif (access(path, R_OK)) {\n\t\t\tsend_msg(ctrl->sd, \"550 No such file or directory.\\r\\n\");\n\t\t\tDBG(\"Failed reading directory '%s': %s\", path, strerror(errno));\n\t\t\treturn;\n\t\t}\n\t}\n\n\tDBG(\"Reading directory %s ... %d number of entries\", path, ctrl->d_num);\n\tif (ctrl->data_sd > -1) {\n\t\tsend_msg(ctrl->sd, \"125 Data connection already open; transfer starting.\\r\\n\");\n\t\tuev_io_init(ctrl->ctx, &ctrl->data_watcher, do_LIST, ctrl, ctrl->data_sd, UEV_WRITE);\n\t\treturn;\n\t}\n\n\tdo_PORT(ctrl, PENDING_LIST);\n}\n\nstatic void handle_LIST(ctrl_t *ctrl, char *arg)\n{\n\tlist(ctrl, arg, LISTMODE_LIST);\n}\n\nstatic void handle_NLST(ctrl_t *ctrl, char *arg)\n{\n\tlist(ctrl, arg, LISTMODE_NLST);\n}\n\nstatic void handle_MLST(ctrl_t *ctrl, char *arg)\n{\n\tlist(ctrl, arg, LISTMODE_MLST);\n}\n\nstatic void handle_MLSD(ctrl_t *ctrl, char *arg)\n{\n\tlist(ctrl, arg, LISTMODE_MLSD);\n}\n\nstatic void do_pasv_connection(uev_t *w, void *arg, int events)\n{\n\tctrl_t *ctrl = (ctrl_t *)arg;\n\tint rc = 0;\n\n\tif (UEV_ERROR == events || UEV_HUP == events) {\n\t\tDBG(\"error on data_listen_sd ...\");\n\t\tuev_io_start(w);\n\t\treturn;\n\t}\n\tDBG(\"Event on data_listen_sd ...\");\n\tuev_io_stop(&ctrl->data_watcher);\n\tif (open_data_connection(ctrl))\n\t\treturn;\n\n\tswitch (ctrl->pending) {\n\tcase PENDING_STOR:\n\t\t/* fallthrough */\n\tcase PENDING_RETR:\n\t\tif (ctrl->offset)\n\t\t\trc = fseek(ctrl->fp, ctrl->offset, SEEK_SET);\n\t\tif (rc) {\n\t\t\tdo_abort(ctrl);\n\t\t\tsend_msg(ctrl->sd, \"551 Failed seeking to that position in file.\\r\\n\");\n\t\t\treturn;\n\t\t}\n\t\t/* fallthrough */\n\tcase PENDING_LIST:\n\t\tbreak;\n\n\tcase PENDING_NONE:\n\t\tDBG(\"No pending command, waiting ...\");\n\t\treturn;\n\t}\n\n\tswitch (ctrl->pending) {\n\tcase PENDING_STOR:\n\t\tDBG(\"Pending STOR, starting ...\");\n\t\tuev_io_init(ctrl->ctx, &ctrl->data_watcher, do_STOR, ctrl, ctrl->data_sd, UEV_READ);\n\t\tbreak;\n\n\tcase PENDING_RETR:\n\t\tDBG(\"Pending RETR, starting ...\");\n\t\tuev_io_init(ctrl->ctx, &ctrl->data_watcher, do_RETR, ctrl, ctrl->data_sd, UEV_WRITE);\n\t\tbreak;\n\n\tcase PENDING_LIST:\n\t\tDBG(\"Pending LIST, starting ...\");\n\t\tuev_io_init(ctrl->ctx, &ctrl->data_watcher, do_LIST, ctrl, ctrl->data_sd, UEV_WRITE);\n\t\tbreak;\n\n\tcase PENDING_NONE:\n\t\t/* cannot get here */\n\t\treturn;\n\t}\n\n\tif (ctrl->pending == PENDING_LIST && ctrl->list_mode == LISTMODE_MLST)\n\t\tsend_msg(ctrl->sd, \"150 Opening ASCII mode data connection for MLSD.\\r\\n\");\n\telse\n\t\tsend_msg(ctrl->sd, \"150 Data connection accepted; transfer starting.\\r\\n\");\n\tctrl->pending = PENDING_NONE;\n}\n\nstatic int do_PASV(ctrl_t *ctrl, char *arg, struct sockaddr *data, socklen_t *len)\n{\n\tstruct sockaddr_in server;\n\n\tif (ctrl->data_sd > 0) {\n\t\tclose(ctrl->data_sd);\n\t\tctrl->data_sd = -1;\n\t}\n\n\tif (ctrl->data_listen_sd > 0)\n\t\tclose(ctrl->data_listen_sd);\n\n\tctrl->data_listen_sd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);\n\tif (ctrl->data_listen_sd < 0) {\n\t\tERR(errno, \"Failed opening data server socket\");\n\t\tsend_msg(ctrl->sd, \"426 Internal server error.\\r\\n\");\n\t\treturn 1;\n\t}\n\n\tmemset(&server, 0, sizeof(server));\n\tserver.sin_family      = AF_INET;\n\tserver.sin_addr.s_addr = inet_addr(ctrl->serveraddr);\n\tserver.sin_port        = htons(0);\n\tif (bind(ctrl->data_listen_sd, (struct sockaddr *)&server, sizeof(server)) < 0) {\n\t\tERR(errno, \"Failed binding to client socket\");\n\t\tsend_msg(ctrl->sd, \"426 Internal server error.\\r\\n\");\n\t\tclose(ctrl->data_listen_sd);\n\t\tctrl->data_listen_sd = -1;\n\t\treturn 1;\n\t}\n\n\tINFO(\"Data server port established.  Waiting for client to connect ...\");\n\tif (listen(ctrl->data_listen_sd, 1) < 0) {\n\t\tERR(errno, \"Client data connection failure\");\n\t\tsend_msg(ctrl->sd, \"426 Internal server error.\\r\\n\");\n\t\tclose(ctrl->data_listen_sd);\n\t\tctrl->data_listen_sd = -1;\n\t\treturn 1;\n\t}\n\n\tmemset(data, 0, sizeof(*data));\n\tif (-1 == getsockname(ctrl->data_listen_sd, data, len)) {\n\t\tERR(errno, \"Cannot determine our address, need it if client should connect to us\");\n\t\tclose(ctrl->data_listen_sd);\n\t\tctrl->data_listen_sd = -1;\n\t\treturn 1;\n\t}\n\n\tuev_io_init(ctrl->ctx, &ctrl->data_watcher, do_pasv_connection, ctrl, ctrl->data_listen_sd, UEV_READ);\n\n\treturn 0;\n}\n\nstatic void handle_PASV(ctrl_t *ctrl, char *arg)\n{\n\tstruct sockaddr_in data;\n\tsocklen_t len = sizeof(data);\n\tchar *msg, *p, buf[200];\n\tint port;\n\n\tif (do_PASV(ctrl, arg, (struct sockaddr *)&data, &len))\n\t\treturn;\n\n\t/* Convert server IP address and port to comma separated list */\n\tif (pasv_addr)\n\t\tmsg = strdup(pasv_addr);\n\telse\n\t\tmsg = strdup(ctrl->serveraddr);\n\tif (!msg) {\n\t\tsend_msg(ctrl->sd, \"426 Internal server error.\\r\\n\");\n\t\texit(1);\n\t}\n\tp = msg;\n\twhile ((p = strchr(p, '.')))\n\t\t*p++ = ',';\n\n\tport = ntohs(data.sin_port);\n\tsnprintf(buf, sizeof(buf), \"227 Entering Passive Mode (%s,%d,%d)\\r\\n\",\n\t\t msg, port / 256, port % 256);\n\tsend_msg(ctrl->sd, buf);\n\n\tfree(msg);\n}\n\nstatic void handle_EPSV(ctrl_t *ctrl, char *arg)\n{\n\tstruct sockaddr_in data;\n\tsocklen_t len = sizeof(data);\n\tchar buf[200];\n\n\tif (string_valid(arg) && string_case_compare(arg, \"ALL\")) {\n\t\tsend_msg(ctrl->sd, \"200 Command OK\\r\\n\");\n\t\treturn;\n\t}\n\n\tif (do_PASV(ctrl, arg, (struct sockaddr *)&data, &len))\n\t\treturn;\n\n\tsnprintf(buf, sizeof(buf), \"229 Entering Extended Passive Mode (|||%d|)\\r\\n\", ntohs(data.sin_port));\n\tsend_msg(ctrl->sd, buf);\n}\n\nstatic void do_RETR(uev_t *w, void *arg, int events)\n{\n\tctrl_t *ctrl = (ctrl_t *)arg;\n\tstruct timeval tv;\n\tssize_t bytes;\n\tsize_t num;\n\tchar buf[BUFFER_SIZE];\n\n\tif (UEV_ERROR == events || UEV_HUP == events) {\n\t\tDBG(\"error on data_sd ...\");\n\t\tuev_io_start(w);\n\t\treturn;\n\t}\n\n\tif (!ctrl->fp) {\n\t\tDBG(\"no fp for RETR, bailing.\");\n\t\treturn;\n\t}\n\n\tnum = fread(buf, sizeof(char), sizeof(buf), ctrl->fp);\n\tif (!num) {\n\t\tif (feof(ctrl->fp))\n\t\t\tLOG(\"User %s from %s downloaded '%s'\", ctrl->name, ctrl->clientaddr, ctrl->file);\n\t\telse if (ferror(ctrl->fp))\n\t\t\tERR(0, \"Error while reading %s\", ctrl->file);\n\t\tdo_abort(ctrl);\n\t\tsend_msg(ctrl->sd, \"226 Transfer complete.\\r\\n\");\n\t\treturn;\n\t}\n\n\t/* Reset inactivity timer. */\n\tuev_timer_set(&ctrl->timeout_watcher, INACTIVITY_TIMER, 0);\n\n\tgettimeofday(&tv, NULL);\n\tif (tv.tv_sec - ctrl->tv.tv_sec > 3) {\n\t\tDBG(\"Sending %zd bytes of %s to %s ...\", num, ctrl->file, ctrl->clientaddr);\n\t\tctrl->tv.tv_sec = tv.tv_sec;\n\t}\n\n\tbytes = send(ctrl->data_sd, buf, num, 0);\n\tif (-1 == bytes) {\n\t\tif (ECONNRESET == errno)\n\t\t\tDBG(\"Connection reset by client.\");\n\t\telse\n\t\t\tERR(errno, \"Failed sending file %s to client\", ctrl->file);\n\n\t\tdo_abort(ctrl);\n\t\tsend_msg(ctrl->sd, \"426 TCP connection was established but then broken!\\r\\n\");\n\t}\n}\n\n/*\n * Check if previous command was PORT, then connect to client and\n * transfer file/listing similar to what's done for PASV conns.\n */\nstatic void do_PORT(ctrl_t *ctrl, pend_t pending)\n{\n\tif (!ctrl->data_address[0]) {\n\t\t/* Check if previous command was PASV */\n\t\tif (ctrl->data_sd == -1 && ctrl->data_listen_sd == -1) {\n\t\t\tif (pending == 1)\n\t\t\t\tdo_MLST(ctrl);\n\t\t\treturn;\n\t\t}\n\n\t\tctrl->pending = pending;\n\t\treturn;\n\t}\n\n\tif (open_data_connection(ctrl)) {\n\t\tdo_abort(ctrl);\n\t\tsend_msg(ctrl->sd, \"425 TCP connection cannot be established.\\r\\n\");\n\t\treturn;\n\t}\n\n\tif (pending != PENDING_LIST || ctrl->list_mode != LISTMODE_MLST)\n\t\tsend_msg(ctrl->sd, \"150 Data connection opened; transfer starting.\\r\\n\");\n\n\tswitch (pending) {\n\tcase PENDING_STOR:\n\t\tuev_io_init(ctrl->ctx, &ctrl->data_watcher, do_STOR, ctrl, ctrl->data_sd, UEV_READ);\n\t\tbreak;\n\n\tcase PENDING_RETR:\n\t\tuev_io_init(ctrl->ctx, &ctrl->data_watcher, do_RETR, ctrl, ctrl->data_sd, UEV_WRITE);\n\t\tbreak;\n\n\tcase PENDING_LIST:\n\t\tuev_io_init(ctrl->ctx, &ctrl->data_watcher, do_LIST, ctrl, ctrl->data_sd, UEV_WRITE);\n\t\tbreak;\n\n\tdefault:\n\t\tERR(0, \"Unhandled pending command (%d) in %s()!\", pending, __func__);\n\t\tbreak;\n\t}\n\n\tctrl->pending = PENDING_NONE;\n}\n\nstatic void handle_RETR(ctrl_t *ctrl, char *file)\n{\n\tFILE *fp;\n\tchar *path;\n\tstruct stat st;\n\n\tpath = compose_abspath(ctrl, file);\n\tif (!path || stat(path, &st)) {\n\t\tINFO(\"%s: RETR: invalid path to %s: %m\", ctrl->clientaddr, file);\n\t\tsend_msg(ctrl->sd, \"550 No such file or directory.\\r\\n\");\n\t\treturn;\n\t}\n\tif (!S_ISREG(st.st_mode)) {\n\t\tLOG(\"%s: Failed opening '%s'. Not a regular file\", ctrl->clientaddr, path);\n\t\tsend_msg(ctrl->sd, \"550 Not a regular file.\\r\\n\");\n\t\treturn;\n\t}\n\n\tfp = fopen(path, \"rb\");\n\tif (!fp) {\n\t\tif (errno != ENOENT)\n\t\t\tERR(errno, \"Failed RETR %s for %s\", path, ctrl->clientaddr);\n\t\tsend_msg(ctrl->sd, \"451 Trouble to RETR file.\\r\\n\");\n\t\treturn;\n\t}\n\n\tctrl->fp = fp;\n\tctrl->file = strdup(file);\n\n\tif (ctrl->data_sd > -1) {\n\t\tif (ctrl->offset) {\n\t\t\tDBG(\"Previous REST %ld of file size %ld\", ctrl->offset, st.st_size);\n\t\t\tif (fseek(fp, ctrl->offset, SEEK_SET)) {\n\t\t\t\tdo_abort(ctrl);\n\t\t\t\tsend_msg(ctrl->sd, \"551 Failed seeking to that position in file.\\r\\n\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tsend_msg(ctrl->sd, \"125 Data connection already open; transfer starting.\\r\\n\");\n\t\tuev_io_init(ctrl->ctx, &ctrl->data_watcher, do_RETR, ctrl, ctrl->data_sd, UEV_WRITE);\n\t\treturn;\n\t}\n\n\tdo_PORT(ctrl, PENDING_RETR);\n}\n\n/* Request to set mtime, ncftp does this */\nstatic void handle_MDTM(ctrl_t *ctrl, char *file)\n{\n\tstruct stat st;\n\tstruct tm *tm;\n\tchar *path, *ptr;\n\tchar *mtime = NULL;\n\tchar buf[80];\n\n        if (!file)\n\t\tgoto missing;\n\n\tptr = strchr(file, ' ');\n\tif (ptr) {\n\t\t*ptr++ = 0;\n\t\tmtime = file;\n\t\tfile  = ptr;\n        }\n\n\tpath = compose_abspath(ctrl, file);\n\tif (!path || stat(path, &st) || !S_ISREG(st.st_mode)) {\n\tmissing:\n\t\tINFO(\"MDTM: invalid path to %s: %m\", file);\n\t\tsend_msg(ctrl->sd, \"550 Not a regular file.\\r\\n\");\n\t\treturn;\n\t}\n\n\tif (mtime) {\n\t\tstruct timespec times[2] = {\n\t\t\t{ 0, UTIME_OMIT },\n\t\t\t{ 0, 0 }\n\t\t};\n\t\tstruct tm tm;\n\t\tint rc;\n\n\t\tif (!strptime(mtime, \"%Y%m%d%H%M%S\", &tm)) {\n\t\tfail:\n\t\t\tsend_msg(ctrl->sd, \"550 Invalid time format\\r\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\ttimes[1].tv_sec = mktime(&tm);\n\t\trc = utimensat(0, path, times, 0);\n\t\tif (rc) {\n\t\t\tERR(errno, \"Failed setting MTIME %s of %s\", mtime, file);\n\t\t\tgoto fail;\n\t\t}\n\n\t\tLOG(\"User %s from %s changed mtime of %s\", ctrl->name, ctrl->clientaddr, file);\n\t\t(void)stat(path, &st);\n\t}\n\n\ttm = gmtime(&st.st_mtime);\n\tstrftime(buf, sizeof(buf), \"213 %Y%m%d%H%M%S\\r\\n\", tm);\n\n\tsend_msg(ctrl->sd, buf);\n}\n\nstatic void do_STOR(uev_t *w, void *arg, int events)\n{\n\tctrl_t *ctrl = (ctrl_t *)arg;\n\tstruct timeval tv;\n\tssize_t bytes;\n\tsize_t num;\n\tchar buf[BUFFER_SIZE];\n\n\tif (UEV_ERROR == events || UEV_HUP == events) {\n\t\tDBG(\"error on data_sd ...\");\n\t\tuev_io_start(w);\n\t\treturn;\n\t}\n\n\tif (!ctrl->fp) {\n\t\tDBG(\"no fp for STOR, bailing.\");\n\t\treturn;\n\t}\n\n\t/* Reset inactivity timer. */\n\tuev_timer_set(&ctrl->timeout_watcher, INACTIVITY_TIMER, 0);\n\n\tbytes = recv(ctrl->data_sd, buf, sizeof(buf), 0);\n\tif (bytes < 0) {\n\t\tif (ECONNRESET == errno)\n\t\t\tDBG(\"Connection reset by client.\");\n\t\telse\n\t\t\tERR(errno, \"Failed receiving file %s from client\", ctrl->file);\n\t\tdo_abort(ctrl);\n\t\tsend_msg(ctrl->sd, \"426 TCP connection was established but then broken!\\r\\n\");\n\t\treturn;\n\t}\n\tif (bytes == 0) {\n\t\tLOG(\"User %s from %s uploaded file %s\", ctrl->name, ctrl->clientaddr, ctrl->file);\n\t\tdo_abort(ctrl);\n\t\tsend_msg(ctrl->sd, \"226 Transfer complete.\\r\\n\");\n\t\treturn;\n\t}\n\n\tgettimeofday(&tv, NULL);\n\tif (tv.tv_sec - ctrl->tv.tv_sec > 3) {\n\t\tDBG(\"Receiving %zd bytes of %s from %s ...\", bytes, ctrl->file, ctrl->clientaddr);\n\t\tctrl->tv.tv_sec = tv.tv_sec;\n\t}\n\n\tnum = fwrite(buf, 1, bytes, ctrl->fp);\n\tif ((size_t)bytes != num)\n\t\tERR(errno, \"552 Disk full.\");\n}\n\nstatic void handle_STOR(ctrl_t *ctrl, char *file)\n{\n\tFILE *fp = NULL;\n\tchar *path;\n\tint rc = 0;\n\n\tpath = compose_abspath(ctrl, file);\n\tif (!path) {\n\t\tINFO(\"STOR: invalid path to %s: %m\", file);\n\t\tgoto fail;\n\t}\n\n\tDBG(\"Trying to write to %s ...\", path);\n\tfp = fopen(path, \"wb\");\n\tif (!fp) {\n\t\t/* If EACCESS client is trying to do something disallowed */\n\t\tERR(errno, \"Failed writing %s\", path);\n\tfail:\n\t\tsend_msg(ctrl->sd, \"451 Trouble storing file.\\r\\n\");\n\t\tdo_abort(ctrl);\n\t\treturn;\n\t}\n\n\tctrl->fp = fp;\n\tctrl->file = strdup(file);\n\n\tif (ctrl->data_sd > -1) {\n\t\tif (ctrl->offset)\n\t\t\trc = fseek(fp, ctrl->offset, SEEK_SET);\n\t\tif (rc) {\n\t\t\tdo_abort(ctrl);\n\t\t\tsend_msg(ctrl->sd, \"551 Failed seeking to that position in file.\\r\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tsend_msg(ctrl->sd, \"125 Data connection already open; transfer starting.\\r\\n\");\n\t\tuev_io_init(ctrl->ctx, &ctrl->data_watcher, do_STOR, ctrl, ctrl->data_sd, UEV_READ);\n\t\treturn;\n\t}\n\n\tdo_PORT(ctrl, PENDING_STOR);\n}\n\nstatic void handle_DELE(ctrl_t *ctrl, char *file)\n{\n\tchar *path;\n\n\tpath = compose_abspath(ctrl, file);\n\tif (!path) {\n\t\tINFO(\"DELE: invalid path to %s: %m\", file);\n\t\tgoto fail;\n\t}\n\n\tif (remove(path)) {\n\t\tif (ENOENT == errno)\n\t\tfail:\tsend_msg(ctrl->sd, \"550 No such file or directory.\\r\\n\");\n\t\telse if (EPERM == errno)\n\t\t\tsend_msg(ctrl->sd, \"550 Not allowed to remove file or directory.\\r\\n\");\n\t\telse if (ENOTEMPTY == errno)\n\t\t\tsend_msg(ctrl->sd, \"550 Not allowed to remove directory, not empty.\\r\\n\");\n\t\telse\n\t\t\tsend_msg(ctrl->sd, \"550 Unknown error.\\r\\n\");\n\t\treturn;\n\t}\n\n\tLOG(\"User %s from %s deleted %s\", ctrl->name, ctrl->clientaddr, file);\n\tsend_msg(ctrl->sd, \"200 Command OK\\r\\n\");\n}\n\nstatic void handle_MKD(ctrl_t *ctrl, char *arg)\n{\n\tchar *path;\n\n\tpath = compose_abspath(ctrl, arg);\n\tif (!path) {\n\t\tINFO(\"MKD: invalid path to %s: %m\", arg);\n\t\tgoto fail;\n\t}\n\n\tif (mkdir(path, 0755)) {\n\t\tif (EPERM == errno)\n\t\tfail:\tsend_msg(ctrl->sd, \"550 Not allowed to create directory.\\r\\n\");\n\t\telse\n\t\t\tsend_msg(ctrl->sd, \"550 Unknown error.\\r\\n\");\n\t\treturn;\n\t}\n\n\tLOG(\"User %s from %s created directory %s\", ctrl->name, ctrl->clientaddr, arg);\n\tsend_msg(ctrl->sd, \"200 Command OK\\r\\n\");\n}\n\nstatic void handle_RMD(ctrl_t *ctrl, char *arg)\n{\n\thandle_DELE(ctrl, arg);\n}\n\nstatic void handle_REST(ctrl_t *ctrl, char *arg)\n{\n\tconst char *errstr;\n\tchar buf[80];\n\n\tif (!string_valid(arg)) {\n\t\tsend_msg(ctrl->sd, \"550 Invalid argument.\\r\\n\");\n\t\treturn;\n\t}\n\n\tctrl->offset = strtonum(arg, 0, INT64_MAX, &errstr);\n\tsnprintf(buf, sizeof(buf), \"350 Restarting at %ld.  Send STOR or RETR to continue transfer.\\r\\n\", ctrl->offset);\n\tsend_msg(ctrl->sd, buf);\n}\n\nstatic size_t num_nl(char *file)\n{\n\tFILE *fp;\n\tchar buf[80];\n\tsize_t len, num = 0;\n\n\tfp = fopen(file, \"r\");\n\tif (!fp)\n\t\treturn 0;\n\n\tdo {\n\t\tchar *ptr = buf;\n\n\t\tlen = fread(buf, sizeof(char), sizeof(buf) - 1, fp);\n\t\tif (len > 0) {\n\t\t\tbuf[len] = 0;\n\t\t\twhile ((ptr = strchr(ptr, '\\n'))) {\n\t\t\t\tptr++;\n\t\t\t\tnum++;\n\t\t\t}\n\t\t}\n\t} while (len > 0);\n\tfclose(fp);\n\n\treturn num;\n}\n\nstatic void handle_SIZE(ctrl_t *ctrl, char *file)\n{\n\tchar *path;\n\tchar buf[80];\n\tsize_t extralen = 0;\n\tstruct stat st;\n\n\tpath = compose_abspath(ctrl, file);\n\tif (!path || stat(path, &st) || S_ISDIR(st.st_mode)) {\n\t\tsend_msg(ctrl->sd, \"550 No such file, or argument is a directory.\\r\\n\");\n\t\treturn;\n\t}\n\n\tDBG(\"SIZE %s\", path);\n\n\tif (ctrl->type == TYPE_A)\n\t\textralen = num_nl(path);\n\n\tsnprintf(buf, sizeof(buf), \"213 %\"  PRIu64 \"\\r\\n\", (uint64_t)(st.st_size + extralen));\n\tsend_msg(ctrl->sd, buf);\n}\n\n/* No operation - used as session keepalive by clients. */\nstatic void handle_NOOP(ctrl_t *ctrl, char *arg)\n{\n\tsend_msg(ctrl->sd, \"200 NOOP OK.\\r\\n\");\n}\n\n#if 0\nstatic void handle_RNFR(ctrl_t *ctrl, char *arg)\n{\n}\n\nstatic void handle_RNTO(ctrl_t *ctrl, char *arg)\n{\n}\n#endif\n\nstatic void handle_QUIT(ctrl_t *ctrl, char *arg)\n{\n\tsend_msg(ctrl->sd, \"221 Goodbye.\\r\\n\");\n\tuev_exit(ctrl->ctx);\n}\n\nstatic void handle_CLNT(ctrl_t *ctrl, char *arg)\n{\n\tsend_msg(ctrl->sd, \"200 CLNT\\r\\n\");\n}\n\nstatic void handle_OPTS(ctrl_t *ctrl, char *arg)\n{\n\t/* OPTS MLST type;size;modify;perm; */\n\tif (arg && strstr(arg, \"MLST\")) {\n\t\tsize_t i = 0;\n\t\tchar *ptr;\n\t\tchar buf[42] = \"200 MLST OPTS \";\n\t\tchar facts[10] = { 0 };\n\n\t\tptr = strtok(arg + 4, \" \\t;\");\n\t\twhile (ptr && i < sizeof(facts) - 1) {\n\t\t\tif (!strcmp(ptr, \"modify\") ||\n\t\t\t    !strcmp(ptr, \"perm\")   ||\n\t\t\t    !strcmp(ptr, \"size\")   ||\n\t\t\t    !strcmp(ptr, \"type\")) {\n\t\t\t\tfacts[i++] = ptr[0];\n\t\t\t\tstrlcat(buf, ptr, sizeof(buf));\n\t\t\t\tstrlcat(buf, \";\", sizeof(buf));\n\t\t\t}\n\n\t\t\tptr = strtok(NULL, \";\");\n\t\t}\n\t\tstrlcat(buf, \"\\r\\n\", sizeof(buf));\n\n\t\tDBG(\"New MLSD facts: %s\", facts);\n\t\tstrlcpy(ctrl->facts, facts, sizeof(ctrl->facts));\n\t\tsend_msg(ctrl->sd, buf);\n\t} else\n\t\tsend_msg(ctrl->sd, \"200 UTF8 OPTS ON\\r\\n\");\n}\n\nstatic void handle_HELP(ctrl_t *ctrl, char *arg)\n{\n\tftp_cmd_t *cmd;\n\tchar buf[80];\n\tint i = 0;\n\n\tif (string_valid(arg) && !string_compare(arg, \"SITE\")) {\n\t\tsend_msg(ctrl->sd, \"500 command HELP does not take any arguments on this server.\\r\\n\");\n\t\treturn;\n\t}\n\n\tsnprintf(ctrl->buf, ctrl->bufsz, \"214-The following commands are recognized.\");\n\tfor (cmd = &supported[0]; cmd->command; cmd++, i++) {\n\t\tif (i % 14 == 0)\n\t\t\tstrlcat(ctrl->buf, \"\\r\\n\", ctrl->bufsz);\n\t\tsnprintf(buf, sizeof(buf), \" %s\", cmd->command);\n\t\tstrlcat(ctrl->buf, buf, ctrl->bufsz);\n\t}\n\tsnprintf(buf, sizeof(buf), \"\\r\\n214 Help OK.\\r\\n\");\n\tstrlcat(ctrl->buf, buf, ctrl->bufsz);\n\n\tsend_msg(ctrl->sd, ctrl->buf);\n}\n\nstatic void handle_FEAT(ctrl_t *ctrl, char *arg)\n{\n\tsnprintf(ctrl->buf, ctrl->bufsz, \"211-Features:\\r\\n\"\n\t\t \" EPSV\\r\\n\"\n\t\t \" PASV\\r\\n\"\n\t\t \" SIZE\\r\\n\"\n\t\t \" UTF8\\r\\n\"\n\t\t \" REST STREAM\\r\\n\"\n\t\t \" MLST modify*;perm*;size*;type*;\\r\\n\"\n\t\t \"211 End\\r\\n\");\n\tsend_msg(ctrl->sd, ctrl->buf);\n}\n\nstatic void handle_UNKNOWN(ctrl_t *ctrl, char *command)\n{\n\tchar buf[128];\n\n\tsnprintf(buf, sizeof(buf), \"500 command '%s' not recognized by server.\\r\\n\", command);\n\tsend_msg(ctrl->sd, buf);\n}\n\n#define COMMAND(NAME) { #NAME, handle_ ## NAME }\n\nstatic ftp_cmd_t supported[] = {\n\tCOMMAND(ABOR),\n\tCOMMAND(DELE),\n\tCOMMAND(USER),\n\tCOMMAND(PASS),\n\tCOMMAND(SYST),\n\tCOMMAND(TYPE),\n\tCOMMAND(PORT),\n\tCOMMAND(EPRT),\n\tCOMMAND(RETR),\n\tCOMMAND(MKD),\n\tCOMMAND(RMD),\n\tCOMMAND(REST),\n\tCOMMAND(MDTM),\n\tCOMMAND(PASV),\n\tCOMMAND(EPSV),\n\tCOMMAND(QUIT),\n\tCOMMAND(LIST),\n\tCOMMAND(NLST),\n\tCOMMAND(MLST),\n\tCOMMAND(MLSD),\n\tCOMMAND(CLNT),\n\tCOMMAND(OPTS),\n\tCOMMAND(PWD),\n\tCOMMAND(STOR),\n\tCOMMAND(CWD),\n\tCOMMAND(CDUP),\n\tCOMMAND(SIZE),\n\tCOMMAND(NOOP),\n\tCOMMAND(HELP),\n\tCOMMAND(FEAT),\n\t{ NULL, NULL }\n};\n\nstatic void child_exit(uev_t *w, void *arg, int events)\n{\n\tDBG(\"Child exiting ...\");\n\tuev_exit(w->ctx);\n}\n\nstatic void read_client_command(uev_t *w, void *arg, int events)\n{\n\tchar *command, *argument;\n\tctrl_t *ctrl = (ctrl_t *)arg;\n\tftp_cmd_t *cmd;\n\n\tif (UEV_ERROR == events || UEV_HUP == events) {\n\t\tuev_io_start(w);\n\t\treturn;\n\t}\n\n\t/* Reset inactivity timer. */\n\tuev_timer_set(&ctrl->timeout_watcher, INACTIVITY_TIMER, 0);\n\n\tif (recv_msg(w->fd, ctrl->buf, ctrl->bufsz, &command, &argument)) {\n\t\tDBG(\"Short read, exiting.\");\n\t\tuev_exit(ctrl->ctx);\n\t\treturn;\n\t}\n\n\tif (!string_valid(command))\n\t\treturn;\n\n\tif (string_match(command, \"FF F4\")) {\n\t\tDBG(\"Ignoring IAC command, client should send ABOR as well.\");\n\t\treturn;\n\t}\n\n\tfor (cmd = &supported[0]; cmd->command; cmd++) {\n\t\tif (string_compare(command, cmd->command)) {\n\t\t\tcmd->cb(ctrl, argument);\n\t\t\treturn;\n\t\t}\n\t}\n\n\thandle_UNKNOWN(ctrl, command);\n}\n\nstatic void ftp_command(ctrl_t *ctrl)\n{\n\tuev_t sigterm_watcher;\n\n\tctrl->bufsz = BUFFER_SIZE * sizeof(char);\n\tctrl->buf   = malloc(ctrl->bufsz);\n\tif (!ctrl->buf) {\n                WARN(errno, \"FTP session failed allocating buffer\");\n                exit(1);\n\t}\n\n\tsnprintf(ctrl->buf, ctrl->bufsz, \"220 %s (%s) ready.\\r\\n\", prognm, VERSION);\n\tsend_msg(ctrl->sd, ctrl->buf);\n\n\tuev_signal_init(ctrl->ctx, &sigterm_watcher, child_exit, NULL, SIGTERM);\n\tuev_io_init(ctrl->ctx, &ctrl->io_watcher, read_client_command, ctrl, ctrl->sd, UEV_READ);\n\tuev_run(ctrl->ctx, 0);\n}\n\nint ftp_session(uev_ctx_t *ctx, int sd)\n{\n\tint pid = 0;\n\tctrl_t *ctrl;\n\tsocklen_t len;\n\n\tctrl = new_session(ctx, sd, &pid);\n\tif (!ctrl) {\n\t\tif (pid < 0)\n\t\t\tshutdown(sd, SHUT_RDWR);\n\t\tclose(sd);\n\n\t\treturn pid;\n\t}\n\n\tlen = sizeof(ctrl->server_sa);\n\tif (-1 == getsockname(sd, (struct sockaddr *)&ctrl->server_sa, &len)) {\n\t\tERR(errno, \"Cannot determine our address\");\n\t\tgoto fail;\n\t}\n\tconvert_address(&ctrl->server_sa, ctrl->serveraddr, sizeof(ctrl->serveraddr));\n\n\tlen = sizeof(ctrl->client_sa);\n\tif (-1 == getpeername(sd, (struct sockaddr *)&ctrl->client_sa, &len)) {\n\t\tERR(errno, \"Cannot determine client address\");\n\t\tgoto fail;\n\t}\n\tconvert_address(&ctrl->client_sa, ctrl->clientaddr, sizeof(ctrl->clientaddr));\n\n\tctrl->type = TYPE_A;\n\tctrl->data_listen_sd = -1;\n\tctrl->data_sd = -1;\n\tctrl->name[0] = 0;\n\tctrl->pass[0] = 0;\n\tctrl->data_address[0] = 0;\n\tstrlcpy(ctrl->facts, \"mpst\", sizeof(ctrl->facts));\n\n\tINFO(\"Client connection from %s\", ctrl->clientaddr);\n\tftp_command(ctrl);\n\n\tDBG(\"Client exiting, bye\");\n\texit(del_session(ctrl, 1));\nfail:\n\tfree(ctrl);\n\tshutdown(sd, SHUT_RDWR);\n\tclose(sd);\n\n\treturn -1;\n}\n\n/**\n * Local Variables:\n *  indent-tabs-mode: t\n *  c-file-style: \"linux\"\n * End:\n */\n"
  },
  {
    "path": "src/log.c",
    "content": "/* uftpd -- the no nonsense (T)FTP server\n *\n * Copyright (c) 2014-2021  Joachim Wiberg <troglobit@gmail.com>\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\n#define SYSLOG_NAMES\n#include \"uftpd.h\"\n\nint loglevel = LOG_NOTICE;\n\n\nint loglvl(char *level)\n{\n\tfor (int i = 0; prioritynames[i].c_name; i++) {\n\t\tif (string_match(prioritynames[i].c_name, level))\n\t\t\treturn prioritynames[i].c_val;\n\t}\n\n\treturn atoi(level);\n}\n\nvoid logit(int severity, const char *fmt, ...)\n{\n\tFILE *file;\n        va_list args;\n\n\tif (loglevel == INTERNAL_NOPRI)\n\t\treturn;\n\n\tif (severity > LOG_WARNING)\n\t\tfile = stdout;\n\telse\n\t\tfile = stderr;\n\n        va_start(args, fmt);\n\tif (do_syslog)\n\t\tvsyslog(severity, fmt, args);\n\telse if (severity <= loglevel) {\n\t\tif (loglevel == LOG_DEBUG)\n\t\t\tfprintf(file, \"%d> \", getpid());\n\t\tvfprintf(file, fmt, args);\n\t\tfflush(file);\n\t}\n        va_end(args);\n}\n\n/**\n * Local Variables:\n *  indent-tabs-mode: t\n *  c-file-style: \"linux\"\n * End:\n */\n"
  },
  {
    "path": "src/tftpcmd.c",
    "content": "/* TFTP Engine\n *\n * Copyright (c) 2014-2021  Joachim Wiberg <troglobit@gmail.com>\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include \"uftpd.h\"\n#include <poll.h>\n#include <arpa/tftp.h>\n\n/*\n * Theory of operation, from RFC1350:\n *\n * Client gets file, RRQ:\n *\n *      client                     server\n *        |----------- RRQ ---------->|\n *        |<--------- DATA -----------|\n *        |----------- ACK ---------->|\n *        |                           |\n *\n * Client puts file, WRQ:\n *\n *      client                     server\n *        |----------- WRQ ---------->|\n *        |<---------- ACK -----------|\n *        |---------- DATA ---------->|\n *        |<---------- ACK -----------|\n *        |                           |\n *\n */\n\n/* Send @len bytes data in @ctrl->buf */\nstatic int do_send(ctrl_t *ctrl, size_t len)\n{\n\tint     result;\n\tsize_t  hdrsz = ctrl->th->th_msg - ctrl->buf;\n\tsize_t  salen = sizeof(struct sockaddr_in);\n\n\tif (ctrl->client_sa.ss_family == AF_INET6)\n\t\tsalen = sizeof(struct sockaddr_in6);\n\n\tif (ctrl->th->th_opcode == OACK)\n\t\thdrsz = ctrl->th->th_stuff - ctrl->buf;\n\n\tDBG(\"SND %c: header size: %zd, data len: %zd ...\", ctrl->th->th_code, hdrsz, len);\n\tresult = sendto(ctrl->sd, ctrl->buf, hdrsz + len, 0, (struct sockaddr *)&ctrl->client_sa, salen);\n\tif (-1 == result)\n\t\treturn 1;\n\n\treturn 0;\n}\n\n/* If @block is non-zero, resend that block */\nstatic int send_DATA(ctrl_t *ctrl, int block)\n{\n\tsize_t  len;\n\n\tmemset(ctrl->buf, 0, ctrl->bufsz);\n\n\t/* Create message */\n\tctrl->th->th_opcode = htons(DATA);\n\tif (block) {\n\t\tint pos = (block - 1) * ctrl->segsize;\n\n\t\tctrl->th->th_block = htons(block);\n\t\tif (-1 == fseek(ctrl->fp, pos, SEEK_SET)) {\n\t\t\tERR(errno, \"Failed resending block\");\n\t\t\treturn 1;\n\t\t}\n\t} else {\n\t\tctrl->th->th_block = htons((ftell(ctrl->fp) / ctrl->segsize) + 1);\n\t}\n\n\tDBG(\"tftp block %d reading %zd bytes ...\", ctrl->th->th_block, ctrl->segsize);\n\tlen = fread(ctrl->th->th_data, sizeof(char), ctrl->segsize, ctrl->fp);\n\n\treturn do_send(ctrl, len);\n}\n\nstatic int send_ACK(ctrl_t *ctrl, int block)\n{\n\tmemset(ctrl->buf, 0, ctrl->bufsz);\n\n\tctrl->th->th_opcode = htons(ACK);\n\tctrl->th->th_block  = htons(block);\n\tDBG(\"ACK block %d\", block);\n\n\treturn do_send(ctrl, 4);\n}\n\n/* Acknowledge options sent by client */\nstatic int send_OACK(ctrl_t *ctrl)\n{\n\tchar *ptr;\n\n\tmemset(ctrl->buf, 0, ctrl->bufsz);\n\n\t/* Create message */\n\tctrl->th->th_opcode = htons(OACK);\n\n\tptr = &ctrl->th->th_stuff[0];\n\tif (isset(&ctrl->tftp_options, 1)) {\n\t\tptr += sprintf(ptr, \"blksize\");\n\t\tptr ++;\n\n\t\tptr += sprintf(ptr, \"%zd\", ctrl->segsize);\n\t\tptr ++;\n\t}\n\n\treturn do_send(ctrl, ptr - ctrl->buf);\n}\n\nstatic int send_ERROR(ctrl_t *ctrl, int code, char *str)\n{\n\tsize_t len;\n\n\tif (!str)\n\t\tstr = strerror(code);\n\tlen = strlen(str);\n\n\tmemset(ctrl->buf, 0, ctrl->segsize);\n\n\t/* Create error message */\n\tctrl->th->th_opcode = htons(ERROR);\n\tctrl->th->th_code   = htons(code);\n\tstrlcpy(ctrl->th->th_msg, str, len);\n\tDBG(\"ERR %d: %s\", code, str);\n\n\t/* Error is ASCIIZ string, hence +1 */\n\treturn do_send(ctrl, len + 1);\n}\n\nstatic int alloc_buf(ctrl_t *ctrl, size_t segsize)\n{\n\tif (!ctrl) {\n\t\terrno = EINVAL;\n\t\treturn 1;\n\t}\n\n\tctrl->segsize = segsize;\n\tctrl->bufsz   = sizeof(tftp_t) + ctrl->segsize;\n\n\tif (ctrl->buf)\n\t\tctrl->buf = realloc(ctrl->buf, ctrl->bufsz);\n\telse\n\t\tctrl->buf = malloc(ctrl->bufsz);\n\n\tif (!ctrl->buf)\n\t\treturn 1;\n\n\tctrl->th = (tftp_t *)ctrl->buf;\n\n\treturn 0;\n}\n\n/* Parse TFTP payload in WRQ/RRQ for filename and optional blksize+timeout */\nstatic int parse_RWRQ(ctrl_t *ctrl, char *buf, size_t len)\n{\n\tsize_t opt_len = strlen(buf) + 1;\n\n\t/* First opt is always filename */\n\tctrl->file = strdup(buf);\n\tif (!ctrl->file)\n\t\treturn send_ERROR(ctrl, EUNDEF, NULL);\n\n\tdo {\n\t\t/* Prepare to read options */\n\t\tbuf += opt_len;\n\t\tlen -= opt_len;\n\t\topt_len = strlen(buf) + 1;\n\n\t\tif (!strncasecmp(buf, \"blksize\", 7)) {\n\t\t\tsize_t sz = 0;\n\n\t\t\tbuf += opt_len;\n\t\t\tlen -= opt_len;\n\t\t\topt_len = strlen(buf) + 1;\n\n\t\t\tsscanf(buf, \"%zd\", &sz);\n\t\t\tif (sz < MIN_SEGSIZE)\n\t\t\t\tcontinue; /* Ignore if too small for us. */\n\n\t\t\tif (alloc_buf(ctrl, sz)) {\n\t\t\t\tERR(errno, \"Failed reallocating TFTP buffer memory\");\n\t\t\t\treturn send_ERROR(ctrl, EUNDEF, NULL);\n\t\t\t}\n\n\t\t\tDBG(\"Negotiated blksize %zd\", sz);\n\t\t\tsetbit(&ctrl->tftp_options, 1);\n\t\t}\n\t} while (len);\n\n\tif (!ctrl->tftp_options)\n\t\treturn 0;\n\n\treturn send_OACK(ctrl);\n}\n\nstatic int handle_RRQ(ctrl_t *ctrl)\n{\n\tchar *path;\n\n\tpath = compose_path(ctrl, ctrl->file);\n\tif (!path) {\n\t\tERR(errno, \"%s: Invalid path to file %s\", ctrl->clientaddr, ctrl->file);\n\t\treturn send_ERROR(ctrl, ENOTFOUND, NULL);\n\t}\n\n\tctrl->fp = fopen(path, \"r\");\n\tif (!ctrl->fp) {\n\t\tERR(errno, \"%s: Failed opening '%s'\", ctrl->clientaddr, path);\n\t\treturn send_ERROR(ctrl, ENOTFOUND, NULL);\n\t}\n\n\treturn !send_DATA(ctrl, 0);\n}\n\nstatic int handle_WRQ(ctrl_t *ctrl)\n{\n\tchar *path;\n\n\tpath = compose_path(ctrl, ctrl->file);\n\tif (!path) {\n\t\tERR(errno, \"%s: Invalid path to file %s\", ctrl->clientaddr, ctrl->file);\n\t\treturn send_ERROR(ctrl, ENOTFOUND, NULL);\n\t}\n\n\tctrl->offset = 1;\t/* First expected block */\n\tctrl->fp = fopen(path, \"w\");\n\tif (!ctrl->fp) {\n\t\tERR(errno, \"%s: Failed opening '%s'\", ctrl->clientaddr, path);\n\t\treturn send_ERROR(ctrl, ENOTFOUND, NULL);\n\t}\n\n\tif (ctrl->tftp_options)\n\t\treturn 0;\n\n\treturn send_ACK(ctrl, 0);\n}\n\nstatic int handle_DATA(ctrl_t *ctrl, size_t len)\n{\n\tchar errmsg[80];\n\tint block;\n\n\tblock = ntohs(ctrl->th->th_block);\n\tif (block != ctrl->offset) {\n\t\tsnprintf(errmsg, sizeof(errmsg), \"Expected block %ld, \"\n\t\t\t \"got DATA for block %d\", ctrl->offset, block);\n\t\treturn !send_ERROR(ctrl, EUNDEF, errmsg);\n\t}\n\n\tDBG(\"tftp block %d writing %zd bytes ...\", ctrl->th->th_block, len);\n\tif (len != fwrite(ctrl->th->th_data, sizeof(char), len, ctrl->fp)) {\n\t\tsnprintf(errmsg, sizeof(errmsg), \"Failed writing file: %s\",\n\t\t\t strerror(errno));\n\t\treturn !send_ERROR(ctrl, ENOSPACE, errmsg);\n\t}\n\n\tctrl->offset++;\n\tif (send_ACK(ctrl, block) || len < ctrl->segsize)\n\t\treturn 0;\n\n\treturn 1;\n}\n\n/* TODO: Add support for ACK timeout and resend */\nstatic int handle_ACK(ctrl_t *ctrl, int block)\n{\n\tif (ctrl->fp) {\n\t\tif (feof(ctrl->fp)) {\n\t\t\tfclose(ctrl->fp);\n\t\t\tctrl->fp = NULL;\n\t\t\treturn 0;\n\t\t}\n\n\t\tDBG(\"ACK block %d, file still open ... \", block);\n\t\treturn !send_DATA(ctrl, 0);\n\t}\n\n\treturn 0;\n}\n\nstatic void read_client_command(uev_t *w, void *arg, int events)\n{\n\tint              active = 1;\n\tctrl_t          *ctrl = (ctrl_t *)arg;\n\tssize_t          len;\n\tuint16_t         port, op, block;\n\tstruct sockaddr *addr = (struct sockaddr *)&ctrl->client_sa;\n\tsocklen_t        addr_len = sizeof(ctrl->client_sa);\n\n\t/* Reset inactivity timer. */\n\tuev_timer_set(&ctrl->timeout_watcher, INACTIVITY_TIMER, 0);\n\n\tmemset(ctrl->buf, 0, ctrl->bufsz);\n\tlen = recvfrom(ctrl->sd, ctrl->buf, ctrl->bufsz, 0, addr, &addr_len);\n\tif (-1 == len) {\n\t\tif (errno != EINTR)\n\t\t\tERR(errno, \"Failed reading command/status from client\");\n\n\t\tuev_exit(w->ctx);\n\t\treturn;\n\t}\n\n\tconvert_address(&ctrl->client_sa, ctrl->clientaddr, sizeof(ctrl->clientaddr));\n\tport   = ntohs(((struct sockaddr_in *)addr)->sin_port);\n\top     = ntohs(ctrl->th->th_opcode);\n\tblock  = ntohs(ctrl->th->th_block);\n\n\tswitch (op) {\n\tcase RRQ:\n\t\tlen -= ctrl->th->th_stuff - ctrl->buf;\n\t\tif (parse_RWRQ(ctrl, ctrl->th->th_stuff, len)) {\n\t\t\tERR(errno, \"Failed parsing TFTP RRQ\");\n\t\t\tactive = 0;\n\t\t\tbreak;\n\t\t}\n\t\tLOG(\"tftp RRQ '%s' from %s:%d\", ctrl->file, ctrl->clientaddr, port);\n\t\tactive = handle_RRQ(ctrl);\n\t\tfree(ctrl->file);\n\t\tbreak;\n\n\tcase WRQ:\n\t\tlen -= ctrl->th->th_stuff - ctrl->buf;\n\t\tif (parse_RWRQ(ctrl, ctrl->th->th_stuff, len)) {\n\t\t\tERR(errno, \"Failed parsing TFTP WRQ\");\n\t\t\tactive = 0;\n\t\t\tbreak;\n\t\t}\n\t\tLOG(\"tftp WRQ '%s' from %s:%d\", ctrl->file, ctrl->clientaddr, port);\n\t\thandle_WRQ(ctrl);\n\t\tfree(ctrl->file);\n\t\tbreak;\n\n\tcase DATA:\t\t/* Received data after WRQ */\n\t\tINFO(\"tftp DATA '%s' from %s:%d\", ctrl->file, ctrl->clientaddr, port);\n\t\tlen -= ctrl->th->th_data - ctrl->buf;\n\t\tactive = handle_DATA(ctrl, len);\n\t\tbreak;\n\n\tcase ERROR:\n\t\tDBG(\"tftp ERROR: %hd\", ntohs(ctrl->th->th_code));\n\t\tactive = 0;\n\t\tbreak;\n\n\tcase ACK:\t\t/* Sent for each DATA we send in a RRQ */\n\t\tDBG(\"tftp ACK, block # %hu\", block);\n\t\tactive = handle_ACK(ctrl, block);\n\t\tbreak;\n\n\tdefault:\n\t\tDBG(\"tftp opcode: %hd\", op);\n\t\tDBG(\"tftp block#: %hu\", block);\n\t\tbreak;\n\t}\n\n\tif (!active)\n\t\tuev_exit(w->ctx);\n}\n\nstatic void tftp_command(ctrl_t *ctrl)\n{\n\t/* Default buffer and segment size */\n\tif (alloc_buf(ctrl, SEGSIZE)) {\n\t\tERR(errno, \"Failed allocating TFTP buffer memory\");\n\t\treturn;\n\t}\n\n\tuev_io_init(ctrl->ctx, &ctrl->io_watcher, read_client_command, ctrl, ctrl->sd, UEV_READ);\n\tuev_run(ctrl->ctx, 0);\n}\n\nint tftp_session(uev_ctx_t *ctx, int sd)\n{\n\tint pid = 0;\n\tctrl_t *ctrl;\n\n\tctrl = new_session(ctx, sd, &pid);\n\tif (!ctrl)\n\t\treturn pid;\n\n\ttftp_command(ctrl);\n\n\texit(del_session(ctrl, 0));\n}\n\n/**\n * Local Variables:\n *  indent-tabs-mode: t\n *  c-file-style: \"linux\"\n * End:\n */\n\n"
  },
  {
    "path": "src/uftpd.c",
    "content": "/* uftpd -- the no nonsense (T)FTP server\n *\n * Copyright (c) 2014-2021  Joachim Wiberg <troglobit@gmail.com>\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\n#include \"uftpd.h\"\n\n/* Global daemon settings */\nchar *prognm      = PACKAGE_NAME;\nchar *pidfn       = NULL;\nchar *home        = NULL;\nint   inetd       = 0;\nint   background  = 1;\nint   do_syslog   = 1;\nint   do_ftp      = FTP_DEFAULT_PORT;\nint   do_tftp     = TFTP_DEFAULT_PORT;\nchar *pasv_addr   = NULL;\nint   do_insecure = 0;\npid_t tftp_pid    = 0;\nstruct passwd *pw = NULL;\n\n/* Event contexts */\nstatic uev_t ftp_watcher;\nstatic uev_t tftp_watcher;\nstatic uev_t sigchld_watcher;\nstatic uev_t sigterm_watcher;\nstatic uev_t sigint_watcher;\nstatic uev_t sighup_watcher;\nstatic uev_t sigquit_watcher;\n\n\nstatic int version(void)\n{\n\tprintf(\"%s\\n\", PACKAGE_VERSION);\n\tprintf(\"\\nBug report address: %s\\n\", PACKAGE_BUGREPORT);\n#ifdef PACKAGE_URL\n\tprintf(\"Project homepage: %s\\n\", PACKAGE_URL);\n#endif\n\treturn 0;\n}\n\nstatic int usage(int code)\n{\n\tint is_inetd = string_match(prognm, \"in.\");\n\n\tif (is_inetd)\n\t\tprintf(\"\\nUsage: %s [-hv] [-l LEVEL] [PATH]\\n\\n\", prognm);\n\telse\n\t\tprintf(\"\\nUsage: %s [-hnsv] [-l LEVEL] [-o OPTS] [-p FILE] [PATH]\\n\\n\", prognm);\n\n\tprintf(\"  -h         Show this help text\\n\"\n\t       \"  -l LEVEL   Set log level: none, err, notice (default), info, debug\\n\");\n\tif (!is_inetd)\n\t\tprintf(\"  -n         Run in foreground, do not detach from controlling terminal\\n\"\n\t\t       \"  -o OPT     Options:\\n\"\n\t\t       \"                      ftp=PORT\\n\"\n\t\t       \"                      tftp=PORT\\n\"\n\t\t       \"                      pasv_addr=ADDR\\n\"\n\t\t       \"                      writable\\n\"\n\t\t       \"  -p FILE    File to store process ID for signaling %s\\n\"\n\t\t       \"  -s         Use syslog, even if running in foreground, default w/o -n\\n\",\n\t\t       prognm);\n\n\tprintf(\"  -v         Show program version\\n\\n\");\n\tprintf(\"The optional 'PATH' defaults to the $HOME of the /etc/passwd user 'ftp'\\n\");\n\n\treturn code;\n}\n\n/*\n * SIGCHLD: one of our children has died\n */\nstatic void sigchld_cb(uev_t *w, void *arg, int events)\n{\n\twhile (1) {\n\t\tpid_t pid;\n\n\t\tpid = waitpid(0, NULL, WNOHANG);\n\t\tif (pid <= 0)\n\t\t\tbreak;\n\n\t\t/* TFTP client disconnected, we can now serve TFTP again! */\n\t\tif (pid == tftp_pid) {\n\t\t\tDBG(\"Previous TFTP session ended, restarting TFTP watcher ...\");\n\t\t\ttftp_pid = 0;\n\t\t\tuev_io_start(&tftp_watcher);\n\t\t}\n\t}\n}\n\n/*\n * SIGQUIT: request termination\n */\nstatic void sigquit_cb(uev_t *w, void *arg, int events)\n{\n\tINFO(\"Received signal %d, exiting ...\", w->signo);\n\n\t/* Forward signal to any children in this process group. */\n\tif (killpg(getpgrp(), SIGTERM))\n\t\tWARN(errno, \"Failed signalling children\");\n\n\t/* Give them time to exit gracefully. */\n\twhile (wait(NULL) != -1)\n\t\t;\n\n\tif (home)\n\t\tfree(home);\n\n\t/* Leave main loop. */\n\tuev_exit(w->ctx);\n}\n\nstatic void sig_init(uev_ctx_t *ctx)\n{\n\tuev_signal_init(ctx, &sigchld_watcher, sigchld_cb, NULL, SIGCHLD);\n\tuev_signal_init(ctx, &sigterm_watcher, sigquit_cb, NULL, SIGTERM);\n\tuev_signal_init(ctx, &sigint_watcher,  sigquit_cb, NULL, SIGINT);\n\tuev_signal_init(ctx, &sighup_watcher,  sigquit_cb, NULL, SIGHUP);\n\tuev_signal_init(ctx, &sigquit_watcher, sigquit_cb, NULL, SIGQUIT);\n}\n\nstatic int find_port(char *service, char *proto, int fallback)\n{\n\tint port = fallback;\n\tstruct servent *sv;\n\n\tsv = getservbyname(service, proto);\n\tif (!sv)\n\t\tWARN(errno, \"Cannot find service %s/%s, defaulting to %d.\", service, proto, port);\n\telse\n\t\tport = ntohs(sv->s_port);\n\n\tDBG(\"Found port %d for service %s, proto %s (fallback port %d)\", port, service, proto, fallback);\n\n\treturn port;\n}\n\nstatic int init(uev_ctx_t *ctx)\n{\n\t/* Figure out FTP/TFTP ports */\n\tif (do_ftp == 1)\n\t\tdo_ftp  = find_port(FTP_SERVICE_NAME, FTP_PROTO_NAME, FTP_DEFAULT_PORT);\n\tif (do_tftp == 1)\n\t\tdo_tftp = find_port(TFTP_SERVICE_NAME, TFTP_PROTO_NAME, TFTP_DEFAULT_PORT); \n\n\t/* Figure out FTP home directory */\n\tif (!home) {\n\t\tpw = getpwnam(FTP_DEFAULT_USER);\n\t\tif (!pw) {\n\t\t\tWARN(errno, \"Cannot find user %s, falling back to %s as FTP root.\",\n\t\t\t     FTP_DEFAULT_USER, FTP_DEFAULT_HOME);\n\t\t\thome = strdup(FTP_DEFAULT_HOME);\n\t\t} else {\n\t\t\thome = strdup(pw->pw_dir);\n\t\t}\n\t}\n\n\tif (!home || access(home, F_OK)) {\n\t\tERR(errno, \"Cannot access FTP root %s\", home ? home : \"NIL\");\n\t\treturn 1;\n\t}\n\n\treturn uev_init(ctx);\n}\n\nstatic void ftp_cb(uev_t *w, void *arg, int events)\n{\n        int client;\n\n\tif (UEV_ERROR == events || UEV_HUP == events) {\n\t\tuev_io_stop(w);\n\t\tclose(w->fd);\n\t\treturn;\n\t}\n\n        client = accept(w->fd, NULL, NULL);\n        if (client < 0) {\n                WARN(errno, \"Failed accepting FTP client connection\");\n                return;\n        }\n\n        ftp_session(arg, client);\n}\n\nstatic void tftp_cb(uev_t *w, void *arg, int events)\n{\n\tuev_io_stop(w);\n\n\tif (UEV_ERROR == events || UEV_HUP == events) {\n\t\tclose(w->fd);\n\t\treturn;\n\t}\n\n        tftp_pid = tftp_session(arg, w->fd);\n\tif (tftp_pid < 0) {\n\t\ttftp_pid = 0;\n\t\tuev_io_start(w);\n\t}\n}\n\nstatic int start_service(uev_ctx_t *ctx, uev_t *w, uev_cb_t *cb, int port, int type, char *desc)\n{\n\tint sd;\n\n\tif (!port)\n\t\t/* Disabled */\n\t\treturn 1;\n\n\tsd = open_socket(port, type, desc);\n\tif (sd < 0) {\n\t\tif (EACCES == errno)\n\t\t\tWARN(0, \"Not allowed to start %s service.%s\",\n\t\t\t     desc, port < 1024 ? \"  Privileged port.\" : \"\");\n\t\treturn 1;\n\t}\n\n\tINFO(\"Starting %s server on port %d ...\", desc, port);\n\tuev_io_init(ctx, w, cb, ctx, sd, UEV_READ);\n\n\treturn 0;\n}\n\nstatic int serve_files(uev_ctx_t *ctx)\n{\n\tint ftp, tftp;\n\n\tDBG(\"Starting services ...\");\n\tftp  = start_service(ctx, &ftp_watcher,   ftp_cb, do_ftp, SOCK_STREAM, \"FTP\");\n\ttftp = start_service(ctx, &tftp_watcher, tftp_cb, do_tftp, SOCK_DGRAM, \"TFTP\");\n\n\t/* Check if failed to start any service ... */\n\tif (ftp && tftp)\n\t\treturn 1;\n\n\t/* Setup signal callbacks */\n\tsig_init(ctx);\n\n\t/* We're now up and running, save pid file. */\n\tpidfile(pidfn);\n\n\tINFO(\"Serving files from %s ...\", home);\n\n\treturn uev_run(ctx, 0);\n}\n\nstatic char *progname(char *arg0)\n{\n       char *nm;\n\n       nm = strrchr(arg0, '/');\n       if (nm)\n\t       nm++;\n       else\n\t       nm = arg0;\n\n       return nm;\n}\n\nint main(int argc, char **argv)\n{\n\tint c;\n\tenum {\n\t\tFTP_OPT = 0,\n\t\tTFTP_OPT,\n\t\tSEC_OPT,\n\t\tPASV_OPT\n\t};\n\tchar *subopts;\n\tchar *const token[] = {\n\t\t[FTP_OPT]  = \"ftp\",\n\t\t[TFTP_OPT] = \"tftp\",\n\t\t[SEC_OPT]  = \"writable\",\n\t\t[PASV_OPT] = \"pasv_addr\",\n\t\tNULL\n\t};\n\tuev_ctx_t ctx;\n\tstruct in_addr in_pasv_addr;\n\n\tpidfn = prognm = progname(argv[0]);\n\twhile ((c = getopt(argc, argv, \"hl:no:p:sv\")) != EOF) {\n\t\tswitch (c) {\n\t\tcase 'h':\n\t\t\treturn usage(0);\n\n\t\tcase 'l':\n\t\t\tloglevel = loglvl(optarg);\n\t\t\tif (-1 == loglevel)\n\t\t\t\treturn usage(1);\n\t\t\tbreak;\n\n\t\tcase 'n':\n\t\t\tbackground = 0;\n\t\t\tdo_syslog--;\n\t\t\tbreak;\n\n\t\tcase 'o':\n\t\t\tsubopts = optarg;\n\t\t\twhile (*subopts != '\\0') {\n\t\t\t\tchar *value;\n\n\t\t\t\tswitch (getsubopt(&subopts, token, &value)) {\n\t\t\t\tcase FTP_OPT:\n\t\t\t\t\tif (!value) {\n\t\t\t\t\t\tfprintf(stderr, \"Missing port argument to -o ftp=PORT\\n\");\n\t\t\t\t\t\treturn usage(1);\n\t\t\t\t\t}\n\t\t\t\t\tdo_ftp = atoi(value);\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase TFTP_OPT:\n\t\t\t\t\tif (!value) {\n\t\t\t\t\t\tfprintf(stderr, \"Missing port argument to -o tftp=PORT\\n\");\n\t\t\t\t\t\treturn usage(1);\n\t\t\t\t\t}\n\t\t\t\t\tdo_tftp = atoi(value);\n\t\t\t\t\tbreak;\n\t\t\t\tcase PASV_OPT:\n\t\t\t\t\tif (!value) {\n\t\t\t\t\t\tfprintf(stderr, \"Missing PASV address argument to -o pasv_addr=ADDR\");\n\t\t\t\t\t\treturn usage(1);\n\t\t\t\t\t}\n\t\t\t\t\tif (!inet_aton(value,&in_pasv_addr)) {\n\t\t\t\t\t\tfprintf(stderr, \"Value specified to pasv_addr is not a valid IPv4 address\");\n\t\t\t\t\t\treturn usage(1);\n\t\t\t\t\t}\n\t\t\t\t\tpasv_addr = strdup(value);\n\t\t\t\t\tbreak;\n\t\t\t\tcase SEC_OPT:\n\t\t\t\t\tdo_insecure = 1;\n\t\t\t\t\tbreak;\n\n\t\t\t\tdefault:\n\t\t\t\t\tfprintf(stderr, \"Unrecognized option '%s'\\n\", value);\n\t\t\t\t\treturn usage(1);\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 'p':\n\t\t\tpidfn = optarg;\n\t\t\tbreak;\n\n\t\tcase 's':\n\t\t\tdo_syslog++;\n\t\t\tbreak;\n\n\t\tcase 'v':\n\t\t\treturn version();\n\n\t\tdefault:\n\t\t\treturn usage(1);\n\t\t}\n\t}\n\n\tif (optind < argc) {\n\t\thome = realpath(argv[optind], NULL);\n\t\tif (!home) {\n\t\t\tERR(errno, \"Invalid FTP root %s\", argv[optind]);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\t/* Inetd mode enforces foreground and syslog */\n\tif (string_compare(prognm, \"in.tftpd\")) {\n\t\tinetd      = 1;\n\t\tdo_ftp     = 0;\n\t\tdo_tftp    = 1;\n\t\tbackground = 0;\n\t\tdo_syslog  = 1;\n\t} else if (string_compare(prognm, \"in.ftpd\")) {\n\t\tinetd      = 1;\n\t\tdo_ftp     = 1;\n\t\tdo_tftp    = 0;\n\t\tbackground = 0;\n\t\tdo_syslog  = 1;\n\t}\n\n\tif (do_syslog) {\n\t\topenlog(prognm, LOG_PID | LOG_NDELAY, LOG_FTP);\n\t\tsetlogmask(LOG_UPTO(loglevel));\n\t}\n\n\tDBG(\"Initializing ...\");\n\tif (init(&ctx)) {\n\t\tERR(0, \"Failed initializing, exiting.\");\n\t\treturn 1;\n\t}\n\n\tif (inetd) {\n\t\tint sd;\n\t\tpid_t pid;\n\n\t\tINFO(\"Started from inetd, serving files from %s ...\", home);\n\n\t\t/* Ensure socket is non-blocking */\n\t\tsd = STDIN_FILENO;\n\t\t(void)fcntl(sd, F_SETFL, fcntl(sd, F_GETFL, 0) | O_NONBLOCK);\n\n\t\tif (do_tftp)\n\t\t\tpid = tftp_session(&ctx, sd);\n\t\telse\n\t\t\tpid = ftp_session(&ctx, sd);\n\n\t\tif (-1 == pid)\n\t\t\treturn 1;\n\t\treturn 0;\n\t}\n\n\tif (background) {\n\t\tDBG(\"Daemonizing ...\");\n\t\tif (-1 == daemon(0, 0)) {\n\t\t\tERR(errno, \"Failed daemonizing\");\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tDBG(\"Serving files as PID %d ...\", getpid());\n\treturn serve_files(&ctx);\n}\n\n/**\n * Local Variables:\n *  indent-tabs-mode: t\n *  c-file-style: \"linux\"\n * End:\n */\n"
  },
  {
    "path": "src/uftpd.h",
    "content": "/* uftpd -- the no nonsense (T)FTP server\n *\n * Copyright (c) 2014-2021  Joachim Wiberg <troglobit@gmail.com>\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n\n#ifndef UFTPD_H_\n#define UFTPD_H_\n\n#include \"config.h\"\n\n#include <arpa/inet.h>\n#include <dirent.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <grp.h>\n#include <libgen.h>\n#include <limits.h>\n#include <locale.h>\n#include <netdb.h>\n#include <netinet/in.h>\n#include <pwd.h>\n#include <sched.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <stdint.h>\n#include <inttypes.h>\t\t/*  PRIu64/PRI64, etc. for stdint.h types */\n#include <stdlib.h>\n#include <string.h>\n#include <sys/param.h>\t\t/* isset(), setbit(), etc. */\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <syslog.h>\n#include <time.h>\n#include <unistd.h>\n\n#include <uev/uev.h>\n#ifdef _LIBITE_LITE\n# include <libite/lite.h>\n#else\n# include <lite/lite.h>\n#endif\n\n#define FTP_DEFAULT_PORT  21\n#define FTP_SERVICE_NAME  \"ftp\"\n#define FTP_PROTO_NAME    \"tcp\"\n\n#define TFTP_DEFAULT_PORT 69\n#define TFTP_SERVICE_NAME \"tftp\"\n#define TFTP_PROTO_NAME   \"udp\"\n\n#define FTP_DEFAULT_USER  \"ftp\"\n#define FTP_DEFAULT_HOME  \"/srv/ftp\"\n\n#define BUFFER_SIZE       BUFSIZ\n\n/* This is a stupid server, it doesn't expect >3 min inactivity */\n#define INACTIVITY_TIMER  180 * 1000\n\n/* TFTP Packet Types (New) */\n#define OACK              06\t/* option acknowledgement */\n\n/* TFTP Minimum segment size, specific to uftpd */\n#define MIN_SEGSIZE       32\n\n#define LOGIT(severity, code, fmt, args...)\t\t\t\t\\\n\tdo {\t\t\t\t\t\t\t\t\\\n\t\tif (code)\t\t\t\t\t\t\\\n\t\t\tlogit(severity, fmt \". Error %d: %s%s\",\t\t\\\n\t\t\t      ##args, code, strerror(code),\t\t\\\n\t\t\t      do_syslog ? \"\" : \"\\n\");\t\t\t\\\n\t\telse\t\t\t\t\t\t\t\\\n\t\t\tlogit(severity, fmt \"%s\", ##args,\t\t\\\n\t\t\t      do_syslog ? \"\" : \"\\n\");\t\t\t\\\n\t} while (0)\n#define ERR(code, fmt, args...)  LOGIT(LOG_ERR, code, fmt, ##args)\n#define WARN(code, fmt, args...) LOGIT(LOG_WARNING, code, fmt, ##args)\n#define LOG(fmt, args...)        LOGIT(LOG_NOTICE, 0, fmt, ##args)\n#define INFO(fmt, args...)       LOGIT(LOG_INFO, 0, fmt, ##args)\n#define DBG(fmt, args...)        LOGIT(LOG_DEBUG, 0, fmt, ##args)\n\nextern char *prognm;\nextern char *home;\t\t/* Server root/home directory       */\nextern int   inetd;             /* Bool: conflicts with daemonize   */\nextern int   background;\t/* Bool: conflicts with inetd       */\nextern int   chrooted;\t\t/* Bool: are we chrooted?           */\nextern int   loglevel;\nextern int   do_syslog;         /* Bool: False at daemon start      */\nextern int   do_ftp;            /* Port: FTP port, or disabled      */\nextern int   do_tftp;           /* Port: TFTP port, or disabled     */\nextern char *pasv_addr;\t/* Address passed to client in pasv mode */\nextern int   do_insecure;\t/* Bool: Allow writable root or not */\nextern struct passwd *pw;       /* FTP user's passwd entry          */\n\ntypedef struct tftphdr tftp_t;\n\ntypedef enum {\n\tPENDING_NONE=0,\n\tPENDING_LIST,\n\tPENDING_RETR,\n\tPENDING_STOR\n} pend_t;\n\ntypedef struct {\n\tint sd;\n\tint type;\n\n\tchar cwd[PATH_MAX];\n\n\tstruct sockaddr_storage server_sa;\n\tstruct sockaddr_storage client_sa;\n\n\tchar serveraddr[INET_ADDRSTRLEN];\n\tchar clientaddr[INET_ADDRSTRLEN];\n\n\t/* Event loop context and session watchers */\n\tuev_t      io_watcher, data_watcher, timeout_watcher;\n\tuev_ctx_t *ctx;\n\n\t/* Session buffer */\n\tchar    *buf;\t\t/* Pointer to segment buffer */\n\tsize_t   bufsz;\t\t/* Size of buf */\n\n\tchar     facts[10];\n\tpend_t   pending; \t/* Pending op: LIST, RETR, STOR */\n\tchar     list_mode;\t/* Current LIST mode */\n\tchar    *file;\t        /* Current file name to fetch */\n\toff_t    offset;\t/* Offset/block in current file, for REST/WRQ */\n\tFILE    *fp;\t\t/* Current file in operation */\n\tint      i;\t\t/* i of d_num in 'd' */\n\tint      d_num;\t\t/* Number of entries in 'd' */\n\tstruct dirent **d;\t/* Current directory in LIST op */\n\tstruct timeval tv;\t/* Progress indicator */\n\n\t/* TFTP */\n\ttftp_t  *th;\t\t/* Same as buf, only as tftp_t */\n\tsize_t   segsize;\t/* SEGSIZE, or per session negotiated */\n\tint      timeout;\t/* INACTIVITY_TIMER, or per session neg. */\n\tuint32_t tftp_options;\t/* %1:blksize */\n\n\t/* User credentials */\n\tchar name[20];\n\tchar pass[20];\n\n\t/* PASV */\n\tint data_sd;\n\tint data_listen_sd;\n\n\t/* PORT */\n\tchar data_address[INET_ADDRSTRLEN];\n\tint  data_port;\n} ctrl_t;\n\nctrl_t *new_session(uev_ctx_t *ctx, int sd, int *rc);\nint     del_session(ctrl_t *ctrl, int isftp);\n\nint     ftp_session(uev_ctx_t *ctx, int client);\nint     tftp_session(uev_ctx_t *ctx, int client);\n\nchar   *compose_path(ctrl_t *ctrl, char *path);\nchar   *compose_abspath(ctrl_t *ctrl, char *path);\nint     set_nonblock(int fd);\nint     open_socket(int port, int type, char *desc);\nvoid    convert_address(struct sockaddr_storage *ss, char *buf, size_t len);\n\nint     loglvl(char *level);\nvoid    logit(int severity, const char *fmt, ...);\n\n#endif  /* UFTPD_H_ */\n\n/**\n * Local Variables:\n *  indent-tabs-mode: t\n *  c-file-style: \"linux\"\n * End:\n */\n"
  },
  {
    "path": "test/.gitignore",
    "content": "*.trs\n*.log\n"
  },
  {
    "path": "test/Makefile.am",
    "content": "EXTRA_DIST         = lib.sh ftp.sh tftp.sh mlst.sh maxfiles.sh\nCLEANFILES         = *~ *.trs *.log\n\nTEST_EXTENSIONS    = .sh\nTESTS_ENVIRONMENT  = unshare -mrun\n\nTESTS              = ftp.sh\nTESTS             += tftp.sh\nTESTS             += mlst.sh\nTESTS             += maxfiles.sh\n"
  },
  {
    "path": "test/ftp.sh",
    "content": "#!/bin/sh\n#set -x\n\nif [ x\"${srcdir}\" = x ]; then\n    srcdir=.\nfi\n. ${srcdir}/lib.sh\n\nget()\n{\n    ftp -n 127.0.0.1 <<-END\n\tverbose on\n    \tuser anonymous a@b\n\tbin\n\tget $1 \n\tbye\n\tEND\n    sleep 1\n}\n\ncheck_dep ftp\nget testfile.txt\n\nls -la\n[ -s testfile.txt ] && OK\nFAIL\n\n"
  },
  {
    "path": "test/lib.sh",
    "content": "#!/bin/sh\n\n# Test name, used everywhere as /tmp/uftpd/$NM/foo\nNM=$(basename \"$0\" .sh)\nDIR=/tmp/uftpd/$NM\nCDIR=/tmp/uftpd/${NM}-client\n\n# Print heading for test phases\nprint()\n{\n\tprintf \"\\e[7m>> %-76s\\e[0m\\n\" \"$1\"\n}\n\ndprint()\n{\n\tprintf \"\\e[2m%-76s\\e[0m\\n\" \"$1\"\n}\n\nSKIP()\n{\n\tprint \"TEST: SKIP\"\n\t[ $# -gt 0 ] && echo \"$*\"\n\texit 77\n}\n\nFAIL()\n{\n\tprint \"TEST: FAIL\"\n\t[ $# -gt 0 ] && echo \"$*\"\n\texit 99\n}\n\nOK()\n{\n\tprint \"TEST: OK\"\n\t[ $# -gt 0 ] && echo \"$*\"\n\texit 0\n}\n\n# shellcheck disable=SC2068\ncheck_dep()\n{\n    if [ -n \"$2\" ]; then\n\tif ! $@; then\n\t    SKIP \"$* is not supported on this system.\"\n\tfi\n    elif ! command -v \"$1\" >/dev/null; then\n\tSKIP \"Cannot find $1, skipping test.\"\n    fi\n}\n\n# Stop all lingering collectors and other tools\nkill_pids()\n{\n\t# shellcheck disable=SC2162\n\tif [ -f \"$DIR/PIDs\" ]; then\n\t\twhile read ln; do kill \"$ln\" 2>/dev/null; done < \"$DIR/PIDs\"\n\t\trm \"$DIR/PIDs\"\n\tfi\n}\n\nteardown()\n{\n\tkill_pids\n\tsleep 1\n\n\t[ -d \"${DIR}\"  ] && rm -rf \"${DIR}\"\n\t[ -d \"${CDIR}\" ] && rm -rf \"${CDIR}\"\n}\n\nsignal()\n{\n\techo\n\tif [ \"$1\" != \"EXIT\" ]; then\n\t\tprint \"Got signal, cleaning up\"\n\tfi\n\tteardown\n}\n\n# props to https://stackoverflow.com/a/2183063/1708249\n# shellcheck disable=SC2064\ntrapit()\n{\n\tfunc=\"$1\" ; shift\n\tfor sig ; do\n\t\ttrap \"$func $sig\" \"$sig\"\n\tdone\n}\n\nsetup()\n{\n\tbindir=$(pwd)/../src\n\n\tip link set lo up\n\tsleep 1\n\n\t# https://datatracker.ietf.org/doc/html/rfc3092\n\tmkdir -p \"${DIR}/foo/bar\"\n\tfor file in baz qux quz xyzzy; do\n\t\ttouch \"${DIR}/foo/$file\"\n\tdone\n\tfor file in fred garply grault waldo; do\n\t\ttouch \"${DIR}/foo/bar/$file\"\n\tdone\n\n\tcp /etc/passwd \"${DIR}/testfile.txt\"\n\n\t\"${bindir}/uftpd\" \"$DIR\" -p \"$DIR/pid\" >\"$DIR/log\"\n\tcd \"${CDIR}\" || exit 1\n\n\tsleep 1\n\tcat \"$DIR/pid\" >> \"$DIR/PIDs\"\n\n\treturn 0\n}\n\n# Runs once when including lib.sh\nmkdir -p \"${DIR}\"\nmkdir -p \"${CDIR}\"\ntouch \"$DIR/PIDs\"\n\n# Call signal() on signals or on exit\ntrapit signal INT TERM QUIT EXIT\n\n# Basic setup for all tests\nsetup\n"
  },
  {
    "path": "test/maxfiles.sh",
    "content": "#!/bin/sh\n#set -x\n\nif [ x\"${srcdir}\" = x ]; then\n    srcdir=.\nfi\n. ${srcdir}/lib.sh\n\n#max=`ulimit -n`\nmax=1040\n\n# check beyond max to verify uftpd doesn't leak descriptors\nmax=$((max + 20))\n\nget()\n{\n\tftp -n 127.0.0.1 <<-EOF\n\t\tuser anonymous a@b\n\t\tget testfile.txt\n\t\tbye\n\t\tEOF\n}\n\ncheck_dep ftp\n\ni=1\nwhile [ $i -lt $max ]; do\n    get\n    rm testfile.txt\n    i=$((i + 1))\ndone\n"
  },
  {
    "path": "test/mlst.sh",
    "content": "#!/bin/sh\n#set -x\n\nif [ x\"${srcdir}\" = x ]; then\n    srcdir=.\nfi\n. ${srcdir}/lib.sh\n\ncmd()\n{\n\ttnftp anonymous@127.0.0.1 <<-EOF\n\t\tcd foo\n\t\tls\n\t\t$*\n\t\tEOF\n}\n\ncheck_dep tnftp\n\ncmd mlst bar |grep -q \"perm=lepc;type=dir; bar\" || FAIL \"missing bar\"\ncmd mlst baz |grep -q \"perm=rw;size=0;type=file; baz\"  || FAIL \"missing baz\"\n\nOK\n"
  },
  {
    "path": "test/tftp.sh",
    "content": "#!/bin/sh\n#set -x\n\nif [ x\"${srcdir}\" = x ]; then\n    srcdir=.\nfi\n. ${srcdir}/lib.sh\n\nget()\n{\n\ttftp 127.0.0.1 -c get \"$1\"\n\tsleep 1\n}\n\ncheck_dep tftp\nnetstat -atnup\n\nget testfile.txt\nls -la\n[ -s testfile.txt ] && OK\nFAIL\n\n"
  }
]