├── .clang-format ├── .github └── workflows │ ├── build.yml │ ├── pages.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── CNAME ├── COPYING ├── Makefile ├── OPERATIONS.md ├── README.md ├── builds └── .keep ├── compose.yml ├── debian ├── changelog ├── compat ├── control ├── copyright ├── dirs ├── lintian-overrides ├── postinst └── rules ├── dockerfiles ├── Dockerfile.debian-11 ├── Dockerfile.debian-12 ├── Dockerfile.ubuntu-22 └── Dockerfile.ubuntu-24 ├── misc ├── architecture.png ├── github-org-team.png ├── nsswitch.conf ├── octopass-icon.afdesign ├── octopass-icon.svg ├── octopass-logo-plain-2021.afdesign ├── octopass-logo-plain-2021.svg ├── octopass-logo-plain.ai ├── octopass-logo-plain.png ├── octopass.afdesign ├── octopass.ai ├── octopass.conf ├── octopass.png ├── octopass.svg └── packagecloud.sh ├── nss_octopass-group.c ├── nss_octopass-group_cli.c ├── nss_octopass-group_test.c ├── nss_octopass-passwd.c ├── nss_octopass-passwd_cli.c ├── nss_octopass-passwd_test.c ├── nss_octopass-shadow.c ├── nss_octopass-shadow_cli.c ├── nss_octopass-shadow_test.c ├── octopass.c ├── octopass.conf.example ├── octopass.h ├── octopass_cli.c ├── octopass_test.c ├── rpm └── octopass.spec ├── selinux └── octopass.te ├── test ├── collaborators.json ├── integration.sh ├── octopass.conf └── octopass_repo.conf └── website ├── .env.local ├── .eslintrc.json ├── .gitignore ├── README.md ├── next.config.mjs ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── _document.tsx └── index.tsx ├── styles ├── Home.module.css └── globals.css └── tsconfig.json /.clang-format: -------------------------------------------------------------------------------- 1 | # requires clang-format >= 3.6 2 | BasedOnStyle: "LLVM" 3 | IndentWidth: 2 4 | ColumnLimit: 120 5 | BreakBeforeBraces: Linux 6 | AllowShortFunctionsOnASingleLine: None 7 | SortIncludes: false 8 | AlignConsecutiveAssignments: true 9 | AlignTrailingComments: true 10 | AllowShortBlocksOnASingleLine: true 11 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build by matrix 2 | on: 3 | push: 4 | pull_request: 5 | workflow_dispatch: 6 | schedule: 7 | - cron: | 8 | 0 0 * * 1 9 | jobs: 10 | ubuntu: 11 | name: Build on ${{matrix.container }} 12 | runs-on: ubuntu-latest 13 | container: ${{ matrix.container }} 14 | strategy: 15 | matrix: 16 | container: ['ubuntu:jammy', 'ubuntu:noble'] 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Install packages 20 | run: | 21 | apt update -qq 22 | apt install -qqy build-essential libcurl4-openssl-dev libjansson-dev linux-libc-dev libtool autoconf git bzip2 clang-format 23 | apt install -qqy glibc-source gcc make libcurl4-gnutls-dev unzip debhelper dh-make devscripts cdbs clang libcriterion-dev 24 | - name: Add octopass user 25 | run: | 26 | useradd -m -s /bin/bash octopass 27 | echo "LOGNAME=octopass" >> $GITHUB_ENV 28 | echo "USER=octopass" >> $GITHUB_ENV 29 | - name: Build and Install 30 | run: | 31 | make build install 32 | make deb 33 | - name: Set Distribution and Name 34 | run: | 35 | echo "DISTRIBUTION=$(echo '${{ matrix.container }}' | sed 's/:/-/g')" >> $GITHUB_ENV 36 | - name: Upload artifacts 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: octopass-${{ env.DISTRIBUTION }} 40 | path: builds/** 41 | debian: 42 | name: Build on ${{matrix.container }} 43 | runs-on: ubuntu-latest 44 | container: ${{ matrix.container }} 45 | strategy: 46 | matrix: 47 | container: ['debian:buster', 'debian:bullseye'] 48 | steps: 49 | - uses: actions/checkout@v4 50 | - name: Install packages 51 | run: | 52 | apt-get update -qq 53 | apt-get install -qqy glibc-source gcc make libcurl4-gnutls-dev libjansson-dev bzip2 unzip debhelper dh-make devscripts cdbs clang apt-utils 54 | - name: Set LOGNAME and USER to environment 55 | run: | 56 | echo "LOGNAME=root" >> $GITHUB_ENV 57 | echo "USER=root" >> $GITHUB_ENV 58 | - name: Build and Install 59 | run: | 60 | make build install 61 | make deb 62 | - name: Set Distribution and Name 63 | run: | 64 | echo "DISTRIBUTION=$(echo '${{ matrix.container }}' | sed 's/:/-/g')" >> $GITHUB_ENV 65 | - name: Upload artifacts 66 | uses: actions/upload-artifact@v4 67 | with: 68 | name: octopass-${{ env.DISTRIBUTION }} 69 | path: builds/** 70 | # centos: 71 | # name: Build on ${{matrix.container }} 72 | # runs-on: ubuntu-latest 73 | # container: ${{ matrix.container }} 74 | # strategy: 75 | # matrix: 76 | # container: ['centos:7'] 77 | # steps: 78 | # - uses: actions/checkout@v2 79 | # - name: Install packages 80 | # run: | 81 | # grep "8." /etc/centos-release && \ 82 | # sed -i -e 's/^mirrorlist/#mirrorlist/g' -e 's/^#baseurl=http:\/\/mirror/baseurl=http:\/\/vault/g' /etc/yum.repos.d/CentOS-*repo 83 | # yum install -y gcc make libcurl-devel jansson-devel bzip2 unzip rpmdevtools epel-release selinux-policy-devel 84 | # - name: Build and Install 85 | # run: | 86 | # make build install 87 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: Pages 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | schedule: 9 | - cron: 0 0 * * * 10 | 11 | concurrency: 12 | group: "pages" 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | env: 19 | NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }} 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | - name: Setup Node 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: 22 27 | cache: npm 28 | cache-dependency-path: website/package-lock.json 29 | - name: Setup pages 30 | uses: actions/configure-pages@v5 31 | with: 32 | static_site_generator: next 33 | - name: Restore next cache 34 | uses: actions/cache@v4 35 | with: 36 | path: | 37 | website/.next/cache 38 | key: | 39 | ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} 40 | restore-keys: | 41 | ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}- 42 | - name: Restore rotion cache 43 | uses: actions/cache@v4 44 | with: 45 | path: | 46 | website/.cache 47 | website/public/images 48 | key: rotion 49 | restore-keys: rotion 50 | - name: Install dependencies 51 | working-directory: website 52 | run: npm ci 53 | - name: Build and Export 54 | working-directory: website 55 | run: | 56 | npm run build 57 | - name: Upload artifact 58 | uses: actions/upload-pages-artifact@v3 59 | with: 60 | path: ./website/out 61 | 62 | deploy: 63 | environment: 64 | name: github-pages 65 | url: ${{ steps.deployment.outputs.page_url }} 66 | runs-on: ubuntu-latest 67 | needs: build 68 | permissions: 69 | contents: read 70 | pages: write 71 | id-token: write 72 | steps: 73 | - name: Deploy to github pages 74 | id: deployment 75 | uses: actions/deploy-pages@v4 76 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release by matrix 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | ubuntu: 10 | name: Build on ${{matrix.container }} 11 | runs-on: ubuntu-latest 12 | container: ${{ matrix.container }} 13 | strategy: 14 | matrix: 15 | container: ['ubuntu:jammy', 'ubuntu:noble'] 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Install packages 19 | run: | 20 | apt update -qq 21 | apt install -qqy build-essential libcurl4-openssl-dev libjansson-dev linux-libc-dev libtool autoconf git bzip2 clang-format 22 | apt install -qqy glibc-source gcc make libcurl4-gnutls-dev unzip debhelper dh-make devscripts cdbs clang libcriterion-dev jq 23 | - name: Add octopass user 24 | run: | 25 | useradd -m -s /bin/bash octopass 26 | echo "LOGNAME=octopass" >> $GITHUB_ENV 27 | echo "USER=octopass" >> $GITHUB_ENV 28 | - name: Create package 29 | run: | 30 | make deb 31 | - name: Upload package to Github Releases 32 | uses: svenstaro/upload-release-action@v2 33 | with: 34 | repo_token: ${{ secrets.GITHUB_TOKEN }} 35 | file: builds/* 36 | tag: ${{ github.ref }} 37 | overwrite: true 38 | file_glob: true 39 | - name: Upload package to PackageCloud 40 | env: 41 | PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }} 42 | run: | 43 | make dist 44 | 45 | debian: 46 | name: Build on ${{matrix.container }} 47 | runs-on: ubuntu-latest 48 | container: ${{ matrix.container }} 49 | strategy: 50 | matrix: 51 | container: ['debian:buster', 'debian:bullseye'] 52 | steps: 53 | - uses: actions/checkout@v4 54 | - name: Install packages 55 | run: | 56 | apt update -qq 57 | apt install -qqy glibc-source gcc make libcurl4-gnutls-dev libjansson-dev bzip2 unzip debhelper dh-make devscripts cdbs clang apt-utils jq 58 | - name: Set LOGNAME and USER to environment 59 | run: | 60 | echo "LOGNAME=root" >> $GITHUB_ENV 61 | echo "USER=root" >> $GITHUB_ENV 62 | - name: Create package 63 | run: | 64 | make deb 65 | - name: Upload package to Github Releases 66 | uses: svenstaro/upload-release-action@v2 67 | with: 68 | repo_token: ${{ secrets.GITHUB_TOKEN }} 69 | file: builds/* 70 | tag: ${{ github.ref }} 71 | overwrite: true 72 | file_glob: true 73 | - name: Upload package to PackageCloud 74 | env: 75 | PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }} 76 | run: | 77 | make dist 78 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test and Lint 2 | on: 3 | push: 4 | pull_request: 5 | workflow_dispatch: 6 | schedule: 7 | - cron: | 8 | 0 0 * * * 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Install packages and Set envfile 15 | env: 16 | OCTOPASS_ENDPOINT: https://api.github.com/ 17 | OCTOPASS_ORGANIZATION: ${{secrets.OCTOPASS_ORGANIZATION}} 18 | OCTOPASS_TEAM: ${{secrets.OCTOPASS_TEAM}} 19 | OCTOPASS_TOKEN: ${{secrets.OCTOPASS_TOKEN}} 20 | run: | 21 | sudo apt update -qq 22 | sudo apt install -qqy build-essential libcurl4-openssl-dev libjansson-dev linux-libc-dev libtool autoconf git bzip2 clang-format libcriterion-dev 23 | env > .env 24 | - name: Lint 25 | run: sudo make format 26 | - name: Unit Test 27 | run: sudo make test 28 | - name: Integration Test 29 | run: sudo make integration 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.test 2 | octopass 3 | octopass.conf 4 | exports 5 | builds 6 | /*console.log 7 | /selinux/*.pp 8 | /selinux/*.fc 9 | /selinux/*.if 10 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | octopass.linyo.ws -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS=-Wall -Wstrict-prototypes -Werror -fPIC -std=c99 -D_GNU_SOURCE 3 | LD_SONAME=-Wl,-soname,libnss_octopass.so.2 4 | LIBRARY=libnss_octopass.so.2.0 5 | LINKS=libnss_octopass.so.2 libnss_octopass.so 6 | 7 | PREFIX=/usr 8 | #LIBDIR=$(PREFIX)/lib64 9 | LIBDIR=$(PREFIX)/lib/x86_64-linux-gnu 10 | ifeq ($(wildcard $(LIBDIR)/.*),) 11 | LIBDIR=$(PREFIX)/lib 12 | endif 13 | BINDIR=$(PREFIX)/bin 14 | BUILD=tmp/libs 15 | CACHE=/var/cache/octopass 16 | 17 | DIST_CODENAME := $(shell grep VERSION_CODENAME /etc/os-release | awk -F '=' '{print $$2}' ORS='') 18 | DIST_ID := $(shell grep '^ID=' /etc/os-release | awk -F '=' '{print $$2}' ORS='') 19 | DIST ?= $(DIST_ID)-$(DIST_CODENAME) 20 | 21 | SOURCES_RPM=Makefile octopass.h octopass*.c nss_octopass*.c octopass.conf.example COPYING selinux/octopass.pp 22 | SOURCES=Makefile octopass.h octopass*.c nss_octopass*.c octopass.conf.example COPYING 23 | VERSION=$(shell awk -F\" '/^\#define OCTOPASS_VERSION / { print $$2; exit }' octopass.h) 24 | CRITERION_VERSION=2.4.2 25 | JANSSON_VERSION=2.4 26 | 27 | INFO_COLOR=\033[1;34m 28 | RESET=\033[0m 29 | BOLD=\033[1m 30 | 31 | ifeq ("$(shell ls .env)",".env") 32 | include .env 33 | export $(shell sed 's/=.*//' .env) 34 | endif 35 | 36 | default: build 37 | build: nss_octopass octopass_cli 38 | 39 | build_dir: ## Create directory for build 40 | test -d $(BUILD) || mkdir -p $(BUILD) 41 | 42 | cache_dir: ## Create directory for cache 43 | test -d $(CACHE) || mkdir -p $(CACHE) && chmod 777 $(CACHE) 44 | 45 | deps: ## Install dependencies 46 | @echo "$(INFO_COLOR)==> $(RESET)$(BOLD)Installing Dependencies$(RESET)" 47 | git clone --depth=1 https://github.com/vstakhov/libucl.git tmp/libucl 48 | pushd tmp/libucl; ./autogen.sh; ./configure && make && make install; popd 49 | git clone --depth=1 https://github.com/allanjude/uclcmd.git tmp/uclcmd 50 | 51 | criterion: ## Installing criterion 52 | test -f $(BUILD)/criterion.tar.bz2 || curl -sL https://github.com/Snaipe/Criterion/releases/download/v$(CRITERION_VERSION)/criterion-v$(CRITERION_VERSION)-linux-x86_64.tar.bz2 -o $(BUILD)/criterion.tar.bz2 53 | cd $(BUILD); tar xf criterion.tar.bz2; cd ../ 54 | mv $(BUILD)/criterion-v$(CRITERION_VERSION)/include/criterion $(PREFIX)/include/criterion 55 | mv $(BUILD)/criterion-v$(CRITERION_VERSION)/lib/libcriterion.* $(LIBDIR)/ 56 | 57 | nss_octopass: build_dir cache_dir ## Build nss_octopass 58 | @echo "$(INFO_COLOR)==> $(RESET)$(BOLD)Building nss_octopass$(RESET)" 59 | $(CC) $(CFLAGS) -c nss_octopass-passwd.c -o $(BUILD)/nss_octopass-passwd.o 60 | $(CC) $(CFLAGS) -c nss_octopass-group.c -o $(BUILD)/nss_octopass-group.o 61 | $(CC) $(CFLAGS) -c nss_octopass-shadow.c -o $(BUILD)/nss_octopass-shadow.o 62 | $(CC) $(CFLAGS) -c octopass.c -o $(BUILD)/octopass.o 63 | $(CC) -shared $(LD_SONAME) -o $(BUILD)/$(LIBRARY) \ 64 | $(BUILD)/octopass.o \ 65 | $(BUILD)/nss_octopass-passwd.o \ 66 | $(BUILD)/nss_octopass-group.o \ 67 | $(BUILD)/nss_octopass-shadow.o \ 68 | -lcurl -ljansson 69 | 70 | octopass_cli: build_dir cache_dir ## Build octopass cli 71 | @echo "$(INFO_COLOR)==> $(RESET)$(BOLD)Building octopass cli$(RESET)" 72 | $(CC) $(CFLAGS) -c nss_octopass-passwd_cli.c -o $(BUILD)/nss_octopass-passwd_cli.o 73 | $(CC) $(CFLAGS) -c nss_octopass-group_cli.c -o $(BUILD)/nss_octopass-group_cli.o 74 | $(CC) $(CFLAGS) -c nss_octopass-shadow_cli.c -o $(BUILD)/nss_octopass-shadow_cli.o 75 | $(CC) $(CFLAGS) -c octopass_cli.c -o $(BUILD)/octopass_cli.o 76 | $(CC) -o $(BUILD)/octopass \ 77 | $(BUILD)/octopass.o \ 78 | $(BUILD)/octopass_cli.o \ 79 | $(BUILD)/nss_octopass-passwd_cli.o \ 80 | $(BUILD)/nss_octopass-group_cli.o \ 81 | $(BUILD)/nss_octopass-shadow_cli.o \ 82 | -lcurl -ljansson 83 | 84 | test: build_dir cache_dir ## Run unit testing 85 | @echo "$(INFO_COLOR)==> $(RESET)$(BOLD)Testing$(RESET)" 86 | test -d $(PREFIX)/include/criterion || $(MAKE) criterion 87 | $(CC) octopass_test.c \ 88 | nss_octopass-passwd_test.c \ 89 | nss_octopass-group_test.c \ 90 | nss_octopass-shadow_test.c -lcurl -ljansson -lcriterion -o $(BUILD)/test && \ 91 | $(BUILD)/test --verbose 92 | 93 | integration: build install ## Run integration test 94 | @echo "$(INFO_COLOR)==> $(RESET)$(BOLD)Integration Testing$(RESET)" 95 | #test -d /usr/lib/x86_64-linux-gnu && ln -sf /usr/lib/libnss_octopass.so.2.0 /usr/lib/x86_64-linux-gnu/libnss_octopass.so.2.0 || true 96 | cp octopass.conf.example /etc/octopass.conf 97 | sed -i -e 's/^passwd:.*/passwd: files octopass/g' /etc/nsswitch.conf 98 | sed -i -e 's/^shadow:.*/shadow: files octopass/g' /etc/nsswitch.conf 99 | sed -i -e 's/^group:.*/group: files octopass/g' /etc/nsswitch.conf 100 | test/integration.sh 101 | 102 | format: ## Format with clang-format 103 | @echo "$(INFO_COLOR)==> $(RESET)$(BOLD)Formatting$(RESET)" 104 | for f in $(ls *.{c,h}); do\ 105 | clang-format -i $f;\ 106 | done 107 | test -z "$$(git status -s -uno)" 108 | 109 | install: install_lib install_cli ## Install octopass 110 | 111 | install_lib: ## Install only shared objects 112 | @echo "$(INFO_COLOR)==> $(RESET)$(BOLD)Installing as Libraries$(RESET)" 113 | [ -d $(LIBDIR) ] || install -d $(LIBDIR) 114 | install $(BUILD)/$(LIBRARY) $(LIBDIR) 115 | cd $(LIBDIR); for link in $(LINKS); do ln -sf $(LIBRARY) $$link ; done; 116 | 117 | install_cli: ## Install only cli command 118 | @echo "$(INFO_COLOR)==> $(RESET)$(BOLD)Installing as Command$(RESET)" 119 | cp $(BUILD)/octopass $(BINDIR)/octopass 120 | 121 | source_for_rpm: ## Create source for RPM 122 | @echo "$(INFO_COLOR)==> $(RESET)$(BOLD)Distributing$(RESET)" 123 | rm -rf tmp.$(DIST) octopass-$(VERSION).tar.gz 124 | mkdir -p tmp.$(DIST)/octopass-$(VERSION) 125 | $(MAKE) selinux_policy 126 | cp $(SOURCES_RPM) tmp.$(DIST)/octopass-$(VERSION) 127 | cd tmp.$(DIST) && \ 128 | tar cf octopass-$(VERSION).tar octopass-$(VERSION) && \ 129 | gzip -9 octopass-$(VERSION).tar 130 | cp tmp.$(DIST)/octopass-$(VERSION).tar.gz ./builds 131 | rm -rf tmp.$(DIST) 132 | 133 | selinux_policy: ## Build policy file for SELinux 134 | make -C selinux -f /usr/share/selinux/devel/Makefile 135 | 136 | rpm: source_for_rpm ## Packaging for RPM 137 | @echo "$(INFO_COLOR)==> $(RESET)$(BOLD)Packaging for RPM$(RESET)" 138 | cp builds/octopass-$(VERSION).tar.gz /root/rpmbuild/SOURCES 139 | spectool -g -R rpm/octopass.spec 140 | rpmbuild -ba rpm/octopass.spec 141 | cp /root/rpmbuild/RPMS/*/*.rpm /octopass/builds 142 | 143 | jansson: build_dir ## Build and Install Janson 144 | @echo "$(INFO_COLOR)==> $(RESET)$(BOLD)Building and Installing Jansson$(RESET)" 145 | mkdir -p /usr/src/redhat/SOURCES 146 | test -f $(BUILD)/jansson.spec || curl -sLk https://raw.github.com/nmilford/rpm-jansson/master/jansson.spec -o $(BUILD)/jansson.spec 147 | test -f /usr/src/redhat/SOURCES/jansson-$(JANSSON_VERSION).tar.gz || curl -sLk http://www.digip.org/jansson/releases/jansson-$(JANSSON_VERSION).tar.gz -o /usr/src/redhat/SOURCES/jansson-$(JANSSON_VERSION).tar.gz 148 | rpmbuild -bb $(BUILD)/jansson.spec 149 | rm -rf /usr/src/redhat/RPMS/*/jansson-*.rpm 150 | 151 | source_for_deb: ## Create source for DEB 152 | @echo "$(INFO_COLOR)==> $(RESET)$(BOLD)Distributing$(RESET)" 153 | rm -rf tmp.$(DIST) octopass_$(VERSION).orig.tar.xz 154 | mkdir -p tmp.$(DIST)/octopass-$(VERSION) 155 | cp $(SOURCES) tmp.$(DIST)/octopass-$(VERSION) 156 | cd tmp.$(DIST) && \ 157 | tar cf octopass_$(VERSION).tar octopass-$(VERSION) && \ 158 | xz -v octopass_$(VERSION).tar 159 | mv tmp.$(DIST)/octopass_$(VERSION).tar.xz tmp.$(DIST)/octopass_$(VERSION).orig.tar.xz 160 | 161 | deb: source_for_deb ## Packaging for DEB 162 | @echo "$(INFO_COLOR)==> $(RESET)$(BOLD)Packaging for DEB$(RESET)" 163 | cd tmp.$(DIST) && \ 164 | tar xf octopass_$(VERSION).orig.tar.xz && \ 165 | cd octopass-$(VERSION) && \ 166 | dh_make --single --createorig -y && \ 167 | rm -rf debian/*.ex debian/*.EX debian/README.Debian && \ 168 | cp -v ../../debian/* debian/ && \ 169 | sed -i -e 's/DIST/$(DIST)/g' debian/changelog && \ 170 | debuild -uc -us 171 | cd tmp.$(DIST) && \ 172 | ls -las && rm -f *-dbgsym_*.deb && \ 173 | find . -name "*.deb" ! -name "*-dbgsym_*.deb" | sed -e 's/\(\(.*octopass_.*\).deb\)/mv \1 \2.$(DIST).deb/g' | sh && \ 174 | cp *.deb ../builds 175 | rm -rf tmp.$(DIST) 176 | 177 | packagecloud: ## Upload archives to PackageCloud 178 | @echo "$(INFO_COLOR)==> $(RESET)$(BOLD)Releasing for PackageCloud$(RESET)" 179 | @HTTP_CODE=$$(curl -s -o /dev/null -w "%{http_code}" -X POST \ 180 | -F "package[distro_version_id]=$(shell misc/packagecloud.sh $(DIST_ID) $(DIST_CODENAME))" \ 181 | -F "package[package_file]=@builds/$(shell find ./builds -maxdepth 1 -name "*.deb" | head -1 | awk -F '/' '{print $$3}' ORS='')" \ 182 | -H "Authorization: Bearer $(PACKAGECLOUD_TOKEN)" \ 183 | https://packagecloud.io/api/v1/repos/linyows/octopass/packages.json); \ 184 | if [ "$$HTTP_CODE" -ne 201 ]; then \ 185 | echo "Upload failed with HTTP status: $$HTTP_CODE"; \ 186 | exit 1; \ 187 | fi 188 | @echo "Upload successful with HTTP status: $$HTTP_CODE" 189 | 190 | pkg: clean ## Create some distribution packages 191 | docker-compose up 192 | 193 | dist: ## Upload archives to GitHub and PackageCloud 194 | @test -z $(PACKAGECLOUD_TOKEN) || $(MAKE) packagecloud 195 | 196 | clean: ## Delete tmp directory 197 | @echo "$(INFO_COLOR)==> $(RESET)$(BOLD)Cleaning$(RESET)" 198 | rm -rf builds && mkdir builds 199 | 200 | help: 201 | @echo "$(BOLD)Target Name Description$(RESET)" 202 | @echo "------------------------ ------------------------" 203 | @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "$(INFO_COLOR)%-24s$(RESET) %s\n", $$1, $$2}' 204 | 205 | .PHONY: install dist deps test rpm 206 | -------------------------------------------------------------------------------- /OPERATIONS.md: -------------------------------------------------------------------------------- 1 | Operations 2 | == 3 | 4 | New Release 5 | -- 6 | 7 | Update version: 8 | 9 | - octopass.h 10 | - rpm/octopass.spec 11 | - debian/changelog 12 | 13 | ```sh 14 | $ export GITHUB_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 15 | $ export PACKAGECLOUD_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 16 | $ make dist 17 | ``` 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |




2 | 3 | OCTOPASS 4 |


5 |

6 | 7 | Octopass is user management tool for linux with Github user. 8 | The name-resolves and authentication is provided from the team or collaborator on github. 9 | Features easy handling and ease of operation. https://octopass.linyo.ws 10 | 11 |
12 |

13 | GitHub Workflow Status 14 | 15 |

16 | 17 | Author 18 | ------ 19 | 20 | [linyows](https://github.com/linyows) 21 | -------------------------------------------------------------------------------- /builds/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyows/octopass/3a12b24362b1701a1fa80c3fc26f785cd707d6db/builds/.keep -------------------------------------------------------------------------------- /compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | ubuntu24: 3 | build: 4 | context: . 5 | dockerfile: dockerfiles/Dockerfile.ubuntu-24 6 | volumes: 7 | - .:/octopass 8 | environment: 9 | DIST: noble 10 | command: make deb 11 | #ubuntu22: 12 | # dockerfile: dockerfiles/Dockerfile.ubuntu-22 13 | # build: . 14 | # volumes: 15 | # - .:/octopass 16 | # environment: 17 | # DIST: jammy 18 | # command: make deb 19 | # 20 | #debian11: 21 | # dockerfile: dockerfiles/Dockerfile.debian-11 22 | # build: . 23 | # volumes: 24 | # - .:/octopass 25 | # environment: 26 | # DIST: bullseye 27 | # DEB_BUILD_OPTIONS: noautodbgsym 28 | # command: make deb 29 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | octopass (0.9.0-1) DIST; urgency=medium 2 | 3 | * Bugfixes 4 | 5 | -- linyows Mon, 17 Feb 2025 14:00:00 +0900 6 | octopass (0.8.0-1) DIST; urgency=medium 7 | 8 | * Bugfixes 9 | 10 | -- linyows Mon, 13 Jun 2025 23:00:00 +0900 11 | octopass (0.7.1-1) DIST; urgency=medium 12 | 13 | * Bugfixes 14 | 15 | -- linyows Thu, 27 Apr 2021 15:30:00 +0900 16 | octopass (0.7.0-1) DIST; urgency=medium 17 | 18 | * Resolve a problem of cache file permission 19 | 20 | -- linyows Fri, 21 Jun 2019 17:10:00 +0900 21 | octopass (0.6.0-1) DIST; urgency=medium 22 | 23 | * Support SELinux policy 24 | 25 | -- linyows Mon, 22 Oct 2018 16:50:00 +0900 26 | octopass (0.5.1-1) DIST; urgency=medium 27 | 28 | * Fix for systemd-networkd SEGV 29 | 30 | -- linyows Wed, 10 Oct 2018 00:20:00 +0900 31 | octopass (0.5.0-1) DIST; urgency=medium 32 | 33 | * Support slug for GitHub team API 34 | 35 | -- linyows Thu, 02 Oct 2018 14:40:00 +0900 36 | octopass (0.4.1-1) DIST; urgency=medium 37 | 38 | * Page size changes to 100 from 30 on Github organization API 39 | 40 | -- linyows Mon, 02 Apr 2018 14:50:00 +0900 41 | 42 | octopass (0.4.0-1) DIST; urgency=medium 43 | 44 | * Support github repository collaborators as name resolve 45 | 46 | -- linyows Mon, 25 Sep 2017 00:30:00 +0900 47 | octopass (0.3.5-1) DIST; urgency=medium 48 | 49 | * Bugfixes 50 | 51 | -- linyows Thu, 14 Sep 2017 19:00:00 +0900 52 | octopass (0.3.3-1) DIST; urgency=medium 53 | 54 | * Fix segmentation fault 55 | 56 | -- linyows Sun, 07 May 2017 23:00:00 +0900 57 | octopass (0.3.2-1) DIST; urgency=medium 58 | 59 | * Example Typo 60 | 61 | -- linyows Tue, 28 Feb 2017 17:20:00 +0900 62 | octopass (0.3.1-1) DIST; urgency=medium 63 | 64 | * Bug fixes 65 | 66 | -- linyows Mon, 27 Feb 2017 23:00:00 +0900 67 | octopass (0.3.0-1) DIST; urgency=medium 68 | 69 | * Support shared-users option 70 | 71 | -- linyows Sun, 26 Feb 2017 23:00:00 +0900 72 | octopass (0.2.0-1) DIST; urgency=medium 73 | 74 | * Change implementation in Go to C. 75 | 76 | -- linyows Mon, 20 Feb 2017 23:00:00 +0900 77 | octopass (0.1.0-1) DIST; urgency=medium 78 | 79 | * Initial release. 80 | 81 | -- linyows Tue, 07 Feb 2017 16:00:00 +0900 82 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: octopass 2 | Section: admin 3 | Priority: optional 4 | Maintainer: linyows 5 | Build-Depends: debhelper (>= 9), libcurl4-gnutls-dev, libjansson-dev 6 | Standards-Version: 3.9.7 7 | Homepage: https://github.com/linyows/octopass 8 | Vcs-Browser: https://github.com/linyows/octopass/tree/debian 9 | Vcs-Git: https://github.com/linyows/octopass -b debian 10 | 11 | Package: octopass 12 | Architecture: amd64 13 | Depends: ${shlibs:Depends}, ${misc:Depends} 14 | Description: This is user management tool for linux by github. 15 | The name-resloves and authentication is provided the team or collaborator on 16 | github. Features easy handling and ease of operation. 17 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | This package was debianized by linyows on 2 | Tue, 7 Feb 2017 16:00:00 +0900. 3 | 4 | Copyright: 5 | 6 | Copyright 2017 linyows. 7 | 8 | License: 9 | 10 | This package is free software; you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation; either version 2 of the License, or 13 | (at your option) any later version. 14 | 15 | This package is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with this package; if not, write to the Free Software 22 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 23 | 24 | On Debian systems, the complete text of the GNU General 25 | Public License can be found in `/usr/share/common-licenses/GPL'. 26 | -------------------------------------------------------------------------------- /debian/dirs: -------------------------------------------------------------------------------- 1 | /usr/lib 2 | /usr/bin 3 | /var/cache 4 | -------------------------------------------------------------------------------- /debian/lintian-overrides: -------------------------------------------------------------------------------- 1 | # This shared library is only for NSS. 2 | libnss-cache binary: package-name-doesnt-match-sonames libnss-cache2 3 | -------------------------------------------------------------------------------- /debian/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # postinst script for octopass 3 | # 4 | # see: dh_installdeb(1) 5 | 6 | set -e 7 | 8 | # summary of how this script can be called: 9 | # * `configure' 10 | # * `abort-upgrade' 11 | # * `abort-remove' `in-favour' 12 | # 13 | # * `abort-remove' 14 | # * `abort-deconfigure' `in-favour' 15 | # `removing' 16 | # 17 | # for details, see https://www.debian.org/doc/debian-policy/ or 18 | # the debian-policy package 19 | 20 | 21 | case "$1" in 22 | configure) 23 | mkdir -p /var/cache/octopass 24 | chmod 777 /var/cache/octopass 25 | ;; 26 | 27 | abort-upgrade|abort-remove|abort-deconfigure) 28 | ;; 29 | 30 | *) 31 | echo "postinst called with unknown argument \`$1'" >&2 32 | exit 1 33 | ;; 34 | esac 35 | 36 | # dh_installdeb will replace this with shell code automatically 37 | # generated by other debhelper scripts. 38 | 39 | #DEBHELPER# 40 | 41 | exit 0 42 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # See debhelper(7) (uncomment to enable) 3 | # output every command that modifies files on the build system. 4 | #export DH_VERBOSE = 1 5 | 6 | 7 | # see FEATURE AREAS in dpkg-buildflags(1) 8 | #export DEB_BUILD_MAINT_OPTIONS = hardening=+all 9 | 10 | # see ENVIRONMENT in dpkg-buildflags(1) 11 | # package maintainers to append CFLAGS 12 | #export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic 13 | # package maintainers to append LDFLAGS 14 | #export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed 15 | 16 | include /usr/share/dpkg/default.mk 17 | 18 | DESTDIR=$(CURDIR)/debian/octopass 19 | CONFDIR=$(DESTDIR)/etc 20 | PREFIX=$(DESTDIR)/usr 21 | LIBDIR=$(PREFIX)/lib/$(DEB_HOST_MULTIARCH) 22 | BINDIR=$(PREFIX)/bin 23 | 24 | %: 25 | dh $@ 26 | 27 | # dh_make generated override targets 28 | # This is example for Cmake (See https://bugs.debian.org/641051 ) 29 | #override_dh_auto_configure: 30 | # dh_auto_configure -- # -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH) 31 | 32 | override_dh_auto_install: 33 | mkdir -p $(LIBDIR) $(BINDIR) $(CONFDIR) 34 | dh_auto_install -- LIBDIR=$(LIBDIR) PREFIX=$(PREFIX) BINDIR=$(BINDIR) 35 | install -pm 644 octopass.conf.example $(CONFDIR) 36 | find $(DESTDIR) 37 | 38 | override_dh_auto_test: 39 | -------------------------------------------------------------------------------- /dockerfiles/Dockerfile.debian-11: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye 2 | 3 | RUN apt-get -qq update && \ 4 | apt-get install -qq glibc-source gcc make libcurl4-gnutls-dev libjansson-dev \ 5 | bzip2 unzip debhelper dh-make devscripts cdbs clang apt-utils 6 | 7 | ENV USER root 8 | 9 | RUN mkdir /octopass 10 | WORKDIR /octopass 11 | -------------------------------------------------------------------------------- /dockerfiles/Dockerfile.debian-12: -------------------------------------------------------------------------------- 1 | FROM debian:Bookworm 2 | 3 | RUN apt-get -qq update && \ 4 | apt-get install -qq glibc-source gcc make libcurl4-gnutls-dev libjansson-dev \ 5 | bzip2 unzip debhelper dh-make devscripts cdbs clang apt-utils 6 | 7 | ENV USER root 8 | 9 | RUN mkdir /octopass 10 | WORKDIR /octopass 11 | -------------------------------------------------------------------------------- /dockerfiles/Dockerfile.ubuntu-22: -------------------------------------------------------------------------------- 1 | FROM ubuntu:jammy 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | RUN apt-get -qq update && \ 5 | apt-get install -qq glibc-source gcc make libcurl4-gnutls-dev libjansson-dev \ 6 | bzip2 unzip debhelper dh-make devscripts cdbs clang libcriterion-dev 7 | 8 | ENV USER root 9 | 10 | RUN mkdir /octopass 11 | WORKDIR /octopass 12 | -------------------------------------------------------------------------------- /dockerfiles/Dockerfile.ubuntu-24: -------------------------------------------------------------------------------- 1 | FROM ubuntu:noble 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | RUN apt-get -qq update && \ 5 | apt-get install -qq glibc-source gcc make libcurl4-gnutls-dev libjansson-dev \ 6 | bzip2 unzip debhelper dh-make devscripts cdbs clang libcriterion-dev 7 | 8 | ENV USER root 9 | 10 | RUN mkdir /octopass 11 | WORKDIR /octopass 12 | -------------------------------------------------------------------------------- /misc/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyows/octopass/3a12b24362b1701a1fa80c3fc26f785cd707d6db/misc/architecture.png -------------------------------------------------------------------------------- /misc/github-org-team.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyows/octopass/3a12b24362b1701a1fa80c3fc26f785cd707d6db/misc/github-org-team.png -------------------------------------------------------------------------------- /misc/nsswitch.conf: -------------------------------------------------------------------------------- 1 | # /etc/nsswitch.conf 2 | # 3 | # Example configuration of GNU Name Service Switch functionality. 4 | # If you have the `glibc-doc-reference' and `info' packages installed, try: 5 | # `info libc "Name Service Switch"' for information about this file. 6 | 7 | passwd: compat systemd octopass 8 | group: compat systemd octopass 9 | shadow: compat octopass 10 | gshadow: files 11 | 12 | hosts: files dns 13 | networks: files 14 | 15 | protocols: db files 16 | services: db files 17 | ethers: db files 18 | rpc: db files 19 | 20 | netgroup: nis 21 | -------------------------------------------------------------------------------- /misc/octopass-icon.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyows/octopass/3a12b24362b1701a1fa80c3fc26f785cd707d6db/misc/octopass-icon.afdesign -------------------------------------------------------------------------------- /misc/octopass-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /misc/octopass-logo-plain-2021.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyows/octopass/3a12b24362b1701a1fa80c3fc26f785cd707d6db/misc/octopass-logo-plain-2021.afdesign -------------------------------------------------------------------------------- /misc/octopass-logo-plain-2021.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /misc/octopass-logo-plain.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyows/octopass/3a12b24362b1701a1fa80c3fc26f785cd707d6db/misc/octopass-logo-plain.ai -------------------------------------------------------------------------------- /misc/octopass-logo-plain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyows/octopass/3a12b24362b1701a1fa80c3fc26f785cd707d6db/misc/octopass-logo-plain.png -------------------------------------------------------------------------------- /misc/octopass.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyows/octopass/3a12b24362b1701a1fa80c3fc26f785cd707d6db/misc/octopass.afdesign -------------------------------------------------------------------------------- /misc/octopass.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyows/octopass/3a12b24362b1701a1fa80c3fc26f785cd707d6db/misc/octopass.ai -------------------------------------------------------------------------------- /misc/octopass.conf: -------------------------------------------------------------------------------- 1 | # O C T O P A S S 2 | 3 | # Required 4 | Token = "GITHUB_TOKEN" 5 | 6 | ## Use team 7 | Organization = "fukuokago" 8 | Team = "admin" 9 | 10 | ## Use collaborators 11 | #Owner = "yourname" 12 | #Repository = "yourrepository" 13 | 14 | # Default 15 | #Endpoint = "https://api.github.com/" 16 | #Group = "yourgroup" 17 | #Home = "/home/foo/%s" 18 | #Shell = "/bin/zsh" 19 | #UidStarts = 2000 20 | #Gid = 2000 21 | #Cache = 300 22 | Syslog = true 23 | 24 | # Advanced 25 | #SharedUsers = [ "admin", "deploy" ] 26 | -------------------------------------------------------------------------------- /misc/octopass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linyows/octopass/3a12b24362b1701a1fa80c3fc26f785cd707d6db/misc/octopass.png -------------------------------------------------------------------------------- /misc/octopass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /misc/packagecloud.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Returns Distribution ID from PackageCloud 4 | 5 | check_jq() { 6 | if ! command -v jq &> /dev/null; then 7 | echo "jq is not installed" >&2 8 | exit 1 9 | fi 10 | } 11 | 12 | list() { 13 | if [ "$PACKAGECLOUD_TOKEN" = "" ]; then 14 | echo '$PACKAGECLOUD_TOKEN is required' 15 | exit 1 16 | fi 17 | 18 | curl -s -H "Authorization: Bearer $PACKAGECLOUD_TOKEN" \ 19 | https://packagecloud.io/api/v1/distributions.json > distributions.json 20 | } 21 | 22 | find() { 23 | os=$1 24 | if [ "$os" = "" ]; then 25 | os=ubuntu 26 | fi 27 | 28 | code=$2 29 | if [ "$code" = "" ]; then 30 | code=noble 31 | fi 32 | 33 | number=$(cat distributions.json | \ 34 | jq ".deb[] | select(.index_name == \"$os\") | .versions[] | {\"id\":.id,\"name\":.index_name} | select(.name == \"$code\") | .id") 35 | 36 | printf "$number" 37 | } 38 | 39 | #check_jq 40 | list 41 | find $1 $2 42 | rm -rf distributions.json 43 | -------------------------------------------------------------------------------- /nss_octopass-group.c: -------------------------------------------------------------------------------- 1 | /* Management linux user and authentication with the organization/team on Github. 2 | Copyright (C) 2017 Tomohisa Oda 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . */ 16 | 17 | #include "octopass.h" 18 | 19 | static pthread_mutex_t OCTOPASS_MUTEX = PTHREAD_MUTEX_INITIALIZER; 20 | static json_t *ent_json_root = NULL; 21 | static int ent_json_idx = 0; 22 | 23 | static int pack_group_struct(json_t *root, struct group *result, char *buffer, size_t buflen, struct config *con) 24 | { 25 | char *next_buf = buffer; 26 | size_t bufleft = buflen; 27 | 28 | if (!json_is_array(root)) { 29 | return -1; 30 | } 31 | 32 | memset(buffer, '\0', buflen); 33 | 34 | size_t team_count = json_array_size(root); 35 | 36 | result->gr_mem = (char **)malloc((team_count + 1) * sizeof(char *)); 37 | if (!result->gr_mem) { 38 | return -1; 39 | } 40 | 41 | result->gr_name = strdup(con->group_name); 42 | if (!result->gr_name) { 43 | free(result->gr_mem); 44 | return -1; 45 | } 46 | 47 | result->gr_passwd = "x"; 48 | result->gr_gid = con->gid; 49 | 50 | size_t gr_mem_index = 0; 51 | 52 | for (size_t i = 0; i < team_count; i++) { 53 | json_t *j_team_obj = json_array_get(root, i); 54 | if (!j_team_obj) { 55 | continue; 56 | } 57 | 58 | json_t *j_team_id = json_object_get(j_team_obj, "id"); 59 | if (!json_is_integer(j_team_id)) { 60 | continue; 61 | } 62 | 63 | json_error_t error; 64 | struct response res; 65 | int team_id = json_integer_value(j_team_id); 66 | int status = octopass_team_members_by_team_id(con, team_id, &res); 67 | if (status != 0) { 68 | free(res.data); 69 | continue; 70 | } 71 | 72 | json_t *members_root = NULL; 73 | members_root = json_loads(res.data, 0, &error); 74 | free(res.data); 75 | res.data = NULL; 76 | 77 | if (!members_root || !json_is_array(members_root)) { 78 | json_decref(members_root); 79 | continue; 80 | } 81 | 82 | for (size_t mi = 0; mi < json_array_size(members_root); mi++) { 83 | json_t *j_member = json_object_get(json_array_get(members_root, mi), "login"); 84 | if (!json_is_string(j_member)) { 85 | continue; 86 | } 87 | const char *login = json_string_value(j_member); 88 | size_t login_len = strlen(login); 89 | if (bufleft <= strlen(login)) { 90 | continue; 91 | } 92 | result->gr_mem[gr_mem_index] = strdup(login); 93 | 94 | next_buf += login_len + 1; 95 | bufleft -= login_len + 1; 96 | 97 | gr_mem_index++; 98 | } 99 | json_decref(members_root); 100 | } 101 | 102 | result->gr_mem[gr_mem_index] = NULL; 103 | 104 | return 0; 105 | } 106 | 107 | enum nss_status _nss_octopass_setgrent_locked(int stayopen) 108 | { 109 | struct config con; 110 | //struct response res; 111 | octopass_config_loading(&con, OCTOPASS_CONFIG_FILE); 112 | 113 | if (con.syslog) { 114 | syslog(LOG_INFO, "%s[L%d] -- stayopen: %d", __func__, __LINE__, stayopen); 115 | } 116 | 117 | json_t *root = octopass_teams(&con); 118 | if (!root) { 119 | if (con.syslog) { 120 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "UNAVAIL"); 121 | } 122 | return NSS_STATUS_UNAVAIL; 123 | } 124 | 125 | if (!json_is_array(root) || json_array_size(root) == 0) { 126 | if (con.syslog) { 127 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "UNAVAIL"); 128 | } 129 | json_decref(root); 130 | return NSS_STATUS_UNAVAIL; 131 | } 132 | 133 | if (ent_json_root) { 134 | json_decref(ent_json_root); 135 | } 136 | 137 | ent_json_root = root; 138 | ent_json_idx = 0; 139 | 140 | return NSS_STATUS_SUCCESS; 141 | } 142 | 143 | // Called to open the group file 144 | enum nss_status _nss_octopass_setgrent(int stayopen) 145 | { 146 | enum nss_status status; 147 | 148 | OCTOPASS_LOCK(); 149 | status = _nss_octopass_setgrent_locked(stayopen); 150 | OCTOPASS_UNLOCK(); 151 | 152 | return status; 153 | } 154 | 155 | enum nss_status _nss_octopass_endgrent_locked(void) 156 | { 157 | if (ent_json_root) { 158 | json_decref(ent_json_root); 159 | ent_json_root = NULL; 160 | } 161 | 162 | ent_json_root = NULL; 163 | ent_json_idx = 0; 164 | 165 | return NSS_STATUS_SUCCESS; 166 | } 167 | 168 | // Called to close the group file 169 | enum nss_status _nss_octopass_endgrent(void) 170 | { 171 | enum nss_status status; 172 | 173 | OCTOPASS_LOCK(); 174 | status = _nss_octopass_endgrent_locked(); 175 | OCTOPASS_UNLOCK(); 176 | 177 | return status; 178 | } 179 | 180 | enum nss_status _nss_octopass_getgrent_r_locked(struct group *result, char *buffer, size_t buflen, int *errnop) 181 | { 182 | enum nss_status ret = NSS_STATUS_SUCCESS; 183 | 184 | if (ent_json_root == NULL) { 185 | ret = _nss_octopass_setgrent_locked(0); 186 | if (ret != NSS_STATUS_SUCCESS || ent_json_root == NULL) { 187 | *errnop = ENOENT; 188 | return NSS_STATUS_UNAVAIL; 189 | } 190 | } 191 | 192 | size_t json_size = json_array_size(ent_json_root); 193 | 194 | // Return notfound when there's nothing else to read. 195 | if (ent_json_idx >= json_size) { 196 | *errnop = ENOENT; 197 | return NSS_STATUS_NOTFOUND; 198 | } 199 | 200 | struct config con; 201 | if (octopass_config_loading(&con, OCTOPASS_CONFIG_FILE) != 0) { 202 | *errnop = EIO; 203 | return NSS_STATUS_UNAVAIL; 204 | } 205 | 206 | if (con.syslog) { 207 | syslog(LOG_INFO, "%s[L%d]", __func__, __LINE__); 208 | } 209 | 210 | int pack_result = pack_group_struct(ent_json_root, result, buffer, buflen, &con); 211 | 212 | if (pack_result == -1) { 213 | *errnop = ENOENT; 214 | if (con.syslog) { 215 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "UNAVAIL"); 216 | } 217 | return NSS_STATUS_UNAVAIL; 218 | } 219 | 220 | if (pack_result == -2) { 221 | *errnop = ERANGE; 222 | if (con.syslog) { 223 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "TRYAGAIN"); 224 | } 225 | return NSS_STATUS_TRYAGAIN; 226 | } 227 | 228 | if (con.syslog) { 229 | syslog(LOG_INFO, "%s[L%d] -- status: %s, gr_name: %s", __func__, __LINE__, "SUCCESS", result->gr_name); 230 | } 231 | 232 | ent_json_idx++; 233 | return NSS_STATUS_SUCCESS; 234 | } 235 | 236 | // Called to look up next entry in group file 237 | enum nss_status _nss_octopass_getgrent_r(struct group *result, char *buffer, size_t buflen, int *errnop) 238 | { 239 | enum nss_status status; 240 | 241 | OCTOPASS_LOCK(); 242 | status = _nss_octopass_getgrent_r_locked(result, buffer, buflen, errnop); 243 | OCTOPASS_UNLOCK(); 244 | 245 | return status; 246 | } 247 | 248 | enum nss_status _nss_octopass_getgrgid_r_locked(gid_t gid, struct group *result, char *buffer, size_t buflen, 249 | int *errnop) 250 | { 251 | enum nss_status status = NSS_STATUS_UNAVAIL; 252 | struct config con; 253 | 254 | if (octopass_config_loading(&con, OCTOPASS_CONFIG_FILE) != 0) { 255 | *errnop = EIO; 256 | return NSS_STATUS_UNAVAIL; 257 | } 258 | 259 | if (con.syslog) { 260 | syslog(LOG_INFO, "%s[L%d] -- gid: %d", __func__, __LINE__, gid); 261 | } 262 | 263 | if (gid != con.gid) { 264 | *errnop = ENOENT; 265 | if (con.syslog) { 266 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "NOTFOUND"); 267 | } 268 | return NSS_STATUS_NOTFOUND; 269 | } 270 | 271 | json_t *root = octopass_teams(&con); 272 | if (!root) { 273 | *errnop = ENOENT; 274 | if (con.syslog) { 275 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "UNAVAIL"); 276 | } 277 | return NSS_STATUS_UNAVAIL; 278 | } 279 | 280 | if (!json_is_array(root) || json_array_size(root) == 0) { 281 | status = NSS_STATUS_NOTFOUND; 282 | *errnop = ENOENT; 283 | if (con.syslog) { 284 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "NOTFOUND"); 285 | } 286 | goto cleanup; 287 | } 288 | 289 | int pack_result = pack_group_struct(root, result, buffer, buflen, &con); 290 | 291 | if (pack_result == -1) { 292 | status = NSS_STATUS_NOTFOUND; 293 | *errnop = ENOENT; 294 | if (con.syslog) { 295 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "NOTFOUND"); 296 | } 297 | goto cleanup; 298 | } 299 | 300 | if (pack_result == -2) { 301 | status = NSS_STATUS_TRYAGAIN; 302 | *errnop = ERANGE; 303 | if (con.syslog) { 304 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "TRYAGAIN"); 305 | } 306 | goto cleanup; 307 | } 308 | 309 | if (con.syslog) { 310 | syslog(LOG_INFO, "%s[L%d] -- status: %s, gr_name: %s", __func__, __LINE__, "SUCCESS", result->gr_name); 311 | } 312 | 313 | status = NSS_STATUS_SUCCESS; 314 | 315 | cleanup: 316 | json_decref(root); 317 | return status; 318 | } 319 | 320 | // Find a group by gid 321 | enum nss_status _nss_octopass_getgrgid_r(gid_t gid, struct group *result, char *buffer, size_t buflen, int *errnop) 322 | { 323 | enum nss_status ret; 324 | 325 | OCTOPASS_LOCK(); 326 | ret = _nss_octopass_getgrgid_r_locked(gid, result, buffer, buflen, errnop); 327 | OCTOPASS_UNLOCK(); 328 | 329 | return ret; 330 | } 331 | 332 | enum nss_status _nss_octopass_getgrnam_r_locked(const char *name, struct group *result, char *buffer, size_t buflen, 333 | int *errnop) 334 | { 335 | enum nss_status status = NSS_STATUS_UNAVAIL; 336 | struct config con; 337 | 338 | if (octopass_config_loading(&con, OCTOPASS_CONFIG_FILE) != 0) { 339 | *errnop = EIO; 340 | return NSS_STATUS_UNAVAIL; 341 | } 342 | 343 | if (con.syslog) { 344 | syslog(LOG_INFO, "%s[L%d] -- name: %s", __func__, __LINE__, name); 345 | } 346 | 347 | if (strcmp(name, con.group_name) != 0) { 348 | *errnop = ENOENT; 349 | if (con.syslog) { 350 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "NOTFOUND"); 351 | } 352 | return NSS_STATUS_NOTFOUND; 353 | } 354 | 355 | json_t *root = octopass_teams(&con); 356 | if (!root) { 357 | *errnop = ENOENT; 358 | if (con.syslog) { 359 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "UNAVAIL"); 360 | } 361 | return NSS_STATUS_UNAVAIL; 362 | } 363 | 364 | if (!json_is_array(root) || json_array_size(root) == 0) { 365 | status = NSS_STATUS_NOTFOUND; 366 | *errnop = ENOENT; 367 | if (con.syslog) { 368 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "NOTFOUND"); 369 | } 370 | goto cleanup; 371 | } 372 | 373 | int pack_result = pack_group_struct(root, result, buffer, buflen, &con); 374 | 375 | if (pack_result == -1) { 376 | status = NSS_STATUS_NOTFOUND; 377 | *errnop = ENOENT; 378 | if (con.syslog) { 379 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "NOTFOUND"); 380 | } 381 | goto cleanup; 382 | } 383 | 384 | if (pack_result == -2) { 385 | status = NSS_STATUS_TRYAGAIN; 386 | *errnop = ERANGE; 387 | if (con.syslog) { 388 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "TRYAGAIN"); 389 | } 390 | goto cleanup; 391 | } 392 | 393 | if (con.syslog) { 394 | syslog(LOG_INFO, "%s[L%d] -- status: %s, gr_name: %s", __func__, __LINE__, "SUCCESS", result->gr_name); 395 | } 396 | 397 | status = NSS_STATUS_SUCCESS; 398 | 399 | cleanup: 400 | json_decref(root); 401 | return status; 402 | } 403 | 404 | // Find a group by name 405 | enum nss_status _nss_octopass_getgrnam_r(const char *name, struct group *result, char *buffer, size_t buflen, 406 | int *errnop) 407 | { 408 | enum nss_status ret; 409 | 410 | OCTOPASS_LOCK(); 411 | ret = _nss_octopass_getgrnam_r_locked(name, result, buffer, buflen, errnop); 412 | OCTOPASS_UNLOCK(); 413 | 414 | return ret; 415 | } 416 | -------------------------------------------------------------------------------- /nss_octopass-group_cli.c: -------------------------------------------------------------------------------- 1 | /* Management linux user and authentication with the organization/team on Github. 2 | Copyright (C) 2017 Tomohisa Oda 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . */ 16 | 17 | #include "nss_octopass-group.c" 18 | 19 | void show_grent(struct group *grent) 20 | { 21 | if (!grent || !grent->gr_name || !grent->gr_passwd) { 22 | fprintf(stderr, "Error: Invalid group entry\n"); 23 | return; 24 | } 25 | 26 | printf("%s:%s:%d", grent->gr_name, grent->gr_passwd, grent->gr_gid); 27 | 28 | if (grent->gr_mem) { 29 | for (int i = 0; grent->gr_mem[i] != NULL; i++) { 30 | printf(":%s", grent->gr_mem[i]); 31 | } 32 | } 33 | 34 | printf("\n"); 35 | } 36 | 37 | void call_getgrnam_r(const char *name) 38 | { 39 | enum nss_status status; 40 | struct group grent; 41 | int err = 0; 42 | int buflen = 2048; 43 | char *buf = malloc(buflen); 44 | if (!buf) { 45 | fprintf(stderr, "Error: Memory allocation failed\n"); 46 | return; 47 | } 48 | 49 | status = _nss_octopass_getgrnam_r(name, &grent, buf, buflen, &err); 50 | if (status == NSS_STATUS_SUCCESS) { 51 | show_grent(&grent); 52 | } else { 53 | fprintf(stderr, "Error: Failed to retrieve group entry for %s\n", name); 54 | } 55 | 56 | free(buf); 57 | } 58 | 59 | void call_getgrgid_r(gid_t gid) 60 | { 61 | enum nss_status status; 62 | struct group grent; 63 | int err = 0; 64 | int buflen = 2048; 65 | char *buf = malloc(buflen); 66 | if (!buf) { 67 | fprintf(stderr, "Error: Memory allocation failed\n"); 68 | return; 69 | } 70 | 71 | status = _nss_octopass_getgrgid_r(gid, &grent, buf, buflen, &err); 72 | if (status == NSS_STATUS_SUCCESS) { 73 | show_grent(&grent); 74 | } else { 75 | fprintf(stderr, "Error: Failed to retrieve group for gid %d\n", gid); 76 | } 77 | 78 | free(buf); 79 | } 80 | 81 | void call_grlist(void) 82 | { 83 | enum nss_status status; 84 | struct group grent; 85 | int err = 0; 86 | int buflen = 2048; 87 | char *buf = malloc(buflen); 88 | if (!buf) { 89 | fprintf(stderr, "Error: Memory allocation failed\n"); 90 | return; 91 | } 92 | 93 | status = _nss_octopass_setgrent(0); 94 | if (status != NSS_STATUS_SUCCESS) { 95 | fprintf(stderr, "Error: Failed to initialize group enumeration\n"); 96 | free(buf); 97 | return; 98 | } 99 | 100 | while ((status = _nss_octopass_getgrent_r(&grent, buf, buflen, &err)) == NSS_STATUS_SUCCESS) { 101 | show_grent(&grent); 102 | } 103 | 104 | status = _nss_octopass_endgrent(); 105 | if (status != NSS_STATUS_SUCCESS) { 106 | fprintf(stderr, "Warning: Failed to end group enumeration\n"); 107 | } 108 | 109 | free(buf); 110 | } 111 | -------------------------------------------------------------------------------- /nss_octopass-group_test.c: -------------------------------------------------------------------------------- 1 | /* Management linux user and authentication with the organization/team on Github. 2 | Copyright (C) 2017 Tomohisa Oda 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . */ 16 | 17 | #define OCTOPASS_CONFIG_FILE "test/octopass.conf" 18 | #include 19 | #include "nss_octopass-group.c" 20 | 21 | extern void setup(void); 22 | 23 | Test(nss_octopass, getgrnam_r, .init = setup) 24 | { 25 | enum nss_status status; 26 | struct group grent; 27 | int err = 0; 28 | int buflen = 2048; 29 | char buf[buflen]; 30 | 31 | const char *name = "admin"; 32 | status = _nss_octopass_getgrnam_r(name, &grent, buf, buflen, &err); 33 | 34 | cr_assert_eq(err, 0); 35 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 36 | cr_assert_str_eq(grent.gr_name, "admin"); 37 | cr_assert_str_eq(grent.gr_passwd, "x"); 38 | cr_assert_eq(grent.gr_gid, 2000); 39 | cr_assert_str_eq(grent.gr_mem[0], "linyows"); 40 | } 41 | 42 | Test(nss_octopass, getgrnam_r__when_team_member_not_found, .init = setup) 43 | { 44 | enum nss_status status; 45 | struct group grent; 46 | int err = 0; 47 | int buflen = 2048; 48 | char buf[buflen]; 49 | 50 | const char *name = "adminno"; 51 | status = _nss_octopass_getgrnam_r(name, &grent, buf, buflen, &err); 52 | 53 | cr_assert_eq(err, ENOENT); 54 | cr_assert_eq(status, NSS_STATUS_NOTFOUND); 55 | } 56 | 57 | Test(nss_octopass, grent_list, .init = setup) 58 | { 59 | enum nss_status status; 60 | struct group grent; 61 | int err = 0; 62 | unsigned long entry_number = 0; 63 | int buflen = 2048; 64 | char buf[buflen]; 65 | 66 | status = _nss_octopass_setgrent(0); 67 | cr_assert_eq(ent_json_idx, 0); 68 | cr_assert_eq(json_object_size(ent_json_root), 0); 69 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 70 | 71 | while (status == NSS_STATUS_SUCCESS) { 72 | entry_number += 1; 73 | err = 0; 74 | status = _nss_octopass_getgrent_r(&grent, buf, buflen, &err); 75 | if (status != NSS_STATUS_SUCCESS) { 76 | continue; 77 | } 78 | 79 | cr_assert_eq(ent_json_idx, entry_number); 80 | cr_assert_eq(json_is_array(ent_json_root), 1); 81 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 82 | 83 | if (strcmp(grent.gr_name, "admin") != 0) { 84 | printf("Unknown group: %s(%lu)\n", grent.gr_name, entry_number); 85 | continue; 86 | } 87 | 88 | cr_assert_eq(err, 0); 89 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 90 | cr_assert_str_eq(grent.gr_name, "admin"); 91 | cr_assert_str_eq(grent.gr_passwd, "x"); 92 | cr_assert_eq(grent.gr_gid, 2000); 93 | cr_assert_str_eq(grent.gr_mem[0], "linyows"); 94 | } 95 | 96 | status = _nss_octopass_endgrent(); 97 | cr_assert_eq(ent_json_idx, 0); 98 | cr_assert_eq(ent_json_root, NULL); 99 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 100 | } 101 | 102 | Test(nss_octopass, grent_list__when_team_not_exist, .init = setup) 103 | { 104 | putenv("OCTOPASS_TEAM=team_not_exists"); 105 | 106 | enum nss_status status; 107 | struct group grent; 108 | int err = 0; 109 | unsigned long entry_number = 0; 110 | int buflen = 2048; 111 | char buf[buflen]; 112 | 113 | status = _nss_octopass_setgrent(0); 114 | cr_assert_eq(ent_json_idx, 0); 115 | cr_assert_eq(json_object_size(ent_json_root), 0); 116 | cr_assert_eq(status, NSS_STATUS_UNAVAIL); 117 | 118 | while (status == NSS_STATUS_SUCCESS) { 119 | entry_number += 1; 120 | err = 0; 121 | status = _nss_octopass_getgrent_r(&grent, buf, buflen, &err); 122 | cr_assert_eq(status, NSS_STATUS_UNAVAIL); 123 | cr_assert_eq(ent_json_idx, 0); 124 | cr_assert_eq(ent_json_root, NULL); 125 | } 126 | 127 | status = _nss_octopass_endgrent(); 128 | cr_assert_eq(ent_json_idx, 0); 129 | cr_assert_eq(ent_json_root, NULL); 130 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 131 | 132 | clearenv(); 133 | } 134 | 135 | Test(nss_octopass, grent_list__when_wrong_token, .init = setup) 136 | { 137 | putenv("OCTOPASS_TOKEN=wrong_token"); 138 | 139 | enum nss_status status; 140 | struct group grent; 141 | int err = 0; 142 | unsigned long entry_number = 0; 143 | int buflen = 2048; 144 | char buf[buflen]; 145 | 146 | status = _nss_octopass_setgrent(0); 147 | cr_assert_eq(ent_json_idx, 0); 148 | cr_assert_eq(json_object_size(ent_json_root), 0); 149 | cr_assert_eq(status, NSS_STATUS_UNAVAIL); 150 | 151 | while (status == NSS_STATUS_SUCCESS) { 152 | entry_number += 1; 153 | err = 0; 154 | status = _nss_octopass_getgrent_r(&grent, buf, buflen, &err); 155 | cr_assert_eq(status, NSS_STATUS_UNAVAIL); 156 | cr_assert_eq(ent_json_idx, 0); 157 | cr_assert_eq(ent_json_root, NULL); 158 | } 159 | 160 | status = _nss_octopass_endgrent(); 161 | cr_assert_eq(ent_json_idx, 0); 162 | cr_assert_eq(ent_json_root, NULL); 163 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 164 | 165 | clearenv(); 166 | } 167 | -------------------------------------------------------------------------------- /nss_octopass-passwd.c: -------------------------------------------------------------------------------- 1 | /* Management linux user and authentication with the organization/team on Github. 2 | Copyright (C) 2017 Tomohisa Oda 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . */ 16 | 17 | #include "octopass.h" 18 | 19 | static pthread_mutex_t OCTOPASS_MUTEX = PTHREAD_MUTEX_INITIALIZER; 20 | static json_t *ent_json_root = NULL; 21 | static int ent_json_idx = 0; 22 | 23 | static int pack_passwd_struct(json_t *root, struct passwd *result, char *buffer, size_t buflen, struct config *con) 24 | { 25 | char *next_buf = buffer; 26 | size_t bufleft = buflen; 27 | 28 | if (!json_is_object(root)) { 29 | return -1; 30 | } 31 | 32 | json_t *j_pw_name = json_object_get(root, "login"); 33 | if (!json_is_string(j_pw_name)) { 34 | return -1; 35 | } 36 | const char *login = json_string_value(j_pw_name); 37 | 38 | json_t *j_pw_uid = json_object_get(root, "id"); 39 | if (!json_is_integer(j_pw_uid)) { 40 | return -1; 41 | } 42 | const json_int_t id = json_integer_value(j_pw_uid); 43 | 44 | memset(buffer, '\0', buflen); 45 | 46 | if (bufleft <= strlen(login) + 1) { 47 | return -2; 48 | } 49 | 50 | snprintf(next_buf, bufleft, "%s", login); 51 | result->pw_name = next_buf; 52 | 53 | next_buf += strlen(result->pw_name) + 1; 54 | bufleft -= strlen(result->pw_name) + 1; 55 | 56 | result->pw_passwd = "x"; 57 | result->pw_uid = con->uid_starts + id; 58 | result->pw_gid = con->gid; 59 | result->pw_gecos = "managed by octopass"; 60 | 61 | char dir[MAXBUF]; 62 | snprintf(dir, sizeof(dir), con->home, result->pw_name); 63 | result->pw_dir = strdup(dir); 64 | 65 | result->pw_shell = strdup(con->shell); 66 | 67 | return 0; 68 | } 69 | 70 | enum nss_status _nss_octopass_setpwent_locked(int stayopen) 71 | { 72 | json_t *root = NULL; 73 | json_error_t error; 74 | 75 | struct config con; 76 | struct response res; 77 | octopass_config_loading(&con, OCTOPASS_CONFIG_FILE); 78 | 79 | if (con.syslog) { 80 | syslog(LOG_INFO, "%s[L%d] -- stayopen: %d", __func__, __LINE__, stayopen); 81 | } 82 | 83 | int status = octopass_members(&con, &res); 84 | if (status != 0 || res.data == NULL) { 85 | if (con.syslog) { 86 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "UNAVAIL"); 87 | } 88 | return NSS_STATUS_UNAVAIL; 89 | } 90 | 91 | root = json_loads(res.data, 0, &error); 92 | free(res.data); 93 | res.data = NULL; 94 | 95 | if (!root) { 96 | if (con.syslog) { 97 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "UNAVAIL"); 98 | } 99 | return NSS_STATUS_UNAVAIL; 100 | } 101 | 102 | if (!json_is_array(root)) { 103 | if (con.syslog) { 104 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "UNAVAIL"); 105 | } 106 | json_decref(root); 107 | return NSS_STATUS_UNAVAIL; 108 | } 109 | 110 | if (ent_json_root) { 111 | json_decref(ent_json_root); 112 | } 113 | 114 | ent_json_root = root; 115 | ent_json_idx = 0; 116 | 117 | if (con.syslog) { 118 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "SUCCESS"); 119 | } 120 | 121 | return NSS_STATUS_SUCCESS; 122 | } 123 | 124 | // Called to open the passwd file 125 | enum nss_status _nss_octopass_setpwent(int stayopen) 126 | { 127 | enum nss_status status; 128 | 129 | OCTOPASS_LOCK(); 130 | status = _nss_octopass_setpwent_locked(stayopen); 131 | OCTOPASS_UNLOCK(); 132 | 133 | return status; 134 | } 135 | 136 | enum nss_status _nss_octopass_endpwent_locked(void) 137 | { 138 | if (ent_json_root) { 139 | json_decref(ent_json_root); 140 | ent_json_root = NULL; 141 | } 142 | 143 | ent_json_root = NULL; 144 | ent_json_idx = 0; 145 | 146 | return NSS_STATUS_SUCCESS; 147 | } 148 | 149 | // Called to close the passwd file 150 | enum nss_status _nss_octopass_endpwent(void) 151 | { 152 | enum nss_status ret; 153 | 154 | OCTOPASS_LOCK(); 155 | ret = _nss_octopass_endpwent_locked(); 156 | OCTOPASS_UNLOCK(); 157 | 158 | return ret; 159 | } 160 | 161 | enum nss_status _nss_octopass_getpwent_r_locked(struct passwd *result, char *buffer, size_t buflen, int *errnop) 162 | { 163 | enum nss_status ret = NSS_STATUS_SUCCESS; 164 | 165 | if (ent_json_root == NULL) { 166 | ret = _nss_octopass_setpwent_locked(0); 167 | if (ret != NSS_STATUS_SUCCESS || ent_json_root == NULL) { 168 | *errnop = ENOENT; 169 | return NSS_STATUS_UNAVAIL; 170 | } 171 | } 172 | 173 | size_t json_size = json_array_size(ent_json_root); 174 | 175 | if (ent_json_idx >= json_size) { 176 | *errnop = ENOENT; 177 | return NSS_STATUS_NOTFOUND; 178 | } 179 | 180 | json_t *json_entry = json_array_get(ent_json_root, ent_json_idx); 181 | if (json_entry == NULL || !json_is_object(json_entry)) { 182 | *errnop = ENOENT; 183 | return NSS_STATUS_NOTFOUND; 184 | } 185 | 186 | struct config con; 187 | if (octopass_config_loading(&con, OCTOPASS_CONFIG_FILE) != 0) { 188 | *errnop = EIO; 189 | return NSS_STATUS_UNAVAIL; 190 | } 191 | 192 | if (con.syslog) { 193 | syslog(LOG_INFO, "%s[L%d]", __func__, __LINE__); 194 | } 195 | 196 | int pack_result = pack_passwd_struct(json_entry, result, buffer, buflen, &con); 197 | 198 | if (pack_result == -1) { 199 | *errnop = ENOENT; 200 | if (con.syslog) { 201 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "NOTFOUND"); 202 | } 203 | return NSS_STATUS_NOTFOUND; 204 | } 205 | 206 | if (pack_result == -2) { 207 | *errnop = ERANGE; 208 | if (con.syslog) { 209 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "TRYAGAIN"); 210 | } 211 | return NSS_STATUS_TRYAGAIN; 212 | } 213 | 214 | if (con.syslog) { 215 | syslog(LOG_INFO, "%s[L%d] -- status: %s, pw_name: %s, pw_uid: %d", 216 | __func__, __LINE__, "SUCCESS", result->pw_name, result->pw_uid); 217 | } 218 | 219 | ent_json_idx++; 220 | return NSS_STATUS_SUCCESS; 221 | } 222 | 223 | // Called to look up next entry in passwd file 224 | enum nss_status _nss_octopass_getpwent_r(struct passwd *result, char *buffer, size_t buflen, int *errnop) 225 | { 226 | enum nss_status ret; 227 | 228 | OCTOPASS_LOCK(); 229 | ret = _nss_octopass_getpwent_r_locked(result, buffer, buflen, errnop); 230 | OCTOPASS_UNLOCK(); 231 | 232 | return ret; 233 | } 234 | 235 | // Find a passwd by uid 236 | enum nss_status _nss_octopass_getpwuid_r_locked(uid_t uid, struct passwd *result, char *buffer, size_t buflen, 237 | int *errnop) 238 | { 239 | json_t *root = NULL; 240 | json_error_t error; 241 | enum nss_status status = NSS_STATUS_UNAVAIL; 242 | 243 | struct config con; 244 | struct response res; 245 | 246 | if (octopass_config_loading(&con, OCTOPASS_CONFIG_FILE) != 0) { 247 | *errnop = EIO; 248 | return NSS_STATUS_UNAVAIL; 249 | } 250 | 251 | if (con.syslog) { 252 | syslog(LOG_INFO, "%s[L%d] -- uid: %d", __func__, __LINE__, uid); 253 | } 254 | 255 | if (octopass_members(&con, &res) != 0 || res.data == NULL) { 256 | *errnop = ENOENT; 257 | if (con.syslog) { 258 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "UNAVAIL"); 259 | } 260 | return NSS_STATUS_UNAVAIL; 261 | } 262 | 263 | root = json_loads(res.data, 0, &error); 264 | free(res.data); 265 | res.data = NULL; 266 | 267 | if (!root) { 268 | *errnop = ENOENT; 269 | if (con.syslog) { 270 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "UNAVAIL"); 271 | } 272 | return NSS_STATUS_UNAVAIL; 273 | } 274 | 275 | int gh_id = uid - con.uid_starts; 276 | json_t *data = octopass_github_team_member_by_id(gh_id, root); 277 | 278 | if (!data || json_object_size(data) == 0) { 279 | status = NSS_STATUS_NOTFOUND; 280 | *errnop = ENOENT; 281 | if (con.syslog) { 282 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "NOTFOUND"); 283 | } 284 | goto cleanup; 285 | } 286 | 287 | int pack_result = pack_passwd_struct(data, result, buffer, buflen, &con); 288 | if (pack_result == -1) { 289 | status = NSS_STATUS_NOTFOUND; 290 | *errnop = ENOENT; 291 | if (con.syslog) { 292 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "NOTFOUND"); 293 | } 294 | goto cleanup; 295 | } 296 | 297 | if (pack_result == -2) { 298 | status = NSS_STATUS_TRYAGAIN; 299 | *errnop = ERANGE; 300 | goto cleanup; 301 | } 302 | 303 | if (con.syslog) { 304 | syslog(LOG_INFO, "%s[L%d] -- status: %s, pw_name: %s, pw_uid: %d", 305 | __func__, __LINE__, "SUCCESS", result->pw_name, result->pw_uid); 306 | } 307 | 308 | status = NSS_STATUS_SUCCESS; 309 | 310 | cleanup: 311 | json_decref(root); 312 | return status; 313 | } 314 | 315 | enum nss_status _nss_octopass_getpwuid_r(uid_t uid, struct passwd *result, char *buffer, size_t buflen, int *errnop) 316 | { 317 | enum nss_status ret; 318 | 319 | OCTOPASS_LOCK(); 320 | ret = _nss_octopass_getpwuid_r_locked(uid, result, buffer, buflen, errnop); 321 | OCTOPASS_UNLOCK(); 322 | 323 | return ret; 324 | } 325 | 326 | enum nss_status _nss_octopass_getpwnam_r_locked(const char *name, struct passwd *result, char *buffer, size_t buflen, 327 | int *errnop) 328 | { 329 | json_t *root = NULL; 330 | json_error_t error; 331 | enum nss_status status = NSS_STATUS_UNAVAIL; 332 | 333 | struct config con; 334 | struct response res; 335 | 336 | if (octopass_config_loading(&con, OCTOPASS_CONFIG_FILE) != 0) { 337 | *errnop = EIO; 338 | return NSS_STATUS_UNAVAIL; 339 | } 340 | 341 | if (con.syslog) { 342 | syslog(LOG_INFO, "%s[L%d] -- name: %s", __func__, __LINE__, name); 343 | } 344 | 345 | if (octopass_members(&con, &res) != 0 || res.data == NULL) { 346 | *errnop = ENOENT; 347 | if (con.syslog) { 348 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "UNAVAIL"); 349 | } 350 | return NSS_STATUS_UNAVAIL; 351 | } 352 | 353 | root = json_loads(res.data, 0, &error); 354 | free(res.data); 355 | res.data = NULL; 356 | 357 | if (!root) { 358 | *errnop = ENOENT; 359 | if (con.syslog) { 360 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "UNAVAIL"); 361 | } 362 | return NSS_STATUS_UNAVAIL; 363 | } 364 | 365 | json_t *data = octopass_github_team_member_by_name((char *)name, root); 366 | if (!data || json_object_size(data) == 0) { 367 | status = NSS_STATUS_NOTFOUND; 368 | *errnop = ENOENT; 369 | if (con.syslog) { 370 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "NOTFOUND"); 371 | } 372 | goto cleanup; 373 | } 374 | 375 | int pack_result = pack_passwd_struct(data, result, buffer, buflen, &con); 376 | if (pack_result == -1) { 377 | status = NSS_STATUS_NOTFOUND; 378 | *errnop = ENOENT; 379 | if (con.syslog) { 380 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "NOTFOUND"); 381 | } 382 | goto cleanup; 383 | } 384 | 385 | if (pack_result == -2) { 386 | status = NSS_STATUS_TRYAGAIN; 387 | *errnop = ERANGE; 388 | if (con.syslog) { 389 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "TRYAGAIN"); 390 | } 391 | goto cleanup; 392 | } 393 | 394 | if (con.syslog) { 395 | syslog(LOG_INFO, "%s[L%d] -- status: %s, pw_name: %s, pw_uid: %d", 396 | __func__, __LINE__, "SUCCESS", result->pw_name, result->pw_uid); 397 | } 398 | 399 | status = NSS_STATUS_SUCCESS; 400 | 401 | cleanup: 402 | json_decref(root); 403 | return status; 404 | } 405 | 406 | // Find a passwd by name 407 | enum nss_status _nss_octopass_getpwnam_r(const char *name, struct passwd *result, char *buffer, size_t buflen, 408 | int *errnop) 409 | { 410 | enum nss_status ret; 411 | 412 | OCTOPASS_LOCK(); 413 | ret = _nss_octopass_getpwnam_r_locked(name, result, buffer, buflen, errnop); 414 | OCTOPASS_UNLOCK(); 415 | 416 | return ret; 417 | } 418 | -------------------------------------------------------------------------------- /nss_octopass-passwd_cli.c: -------------------------------------------------------------------------------- 1 | /* Management linux user and authentication with the organization/team on Github. 2 | Copyright (C) 2017 Tomohisa Oda 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . */ 16 | 17 | #include "nss_octopass-passwd.c" 18 | 19 | void show_pwent(struct passwd *pwent) 20 | { 21 | if (!pwent || !pwent->pw_name || !pwent->pw_passwd || !pwent->pw_gecos || !pwent->pw_dir || !pwent->pw_shell) { 22 | fprintf(stderr, "Error: Invalid passwd entry\n"); 23 | return; 24 | } 25 | 26 | printf("%s:%s:%d:%d:%s:%s:%s\n", 27 | pwent->pw_name ? pwent->pw_name : "N/A", 28 | pwent->pw_passwd ? pwent->pw_passwd : "N/A", 29 | pwent->pw_uid, 30 | pwent->pw_gid, 31 | pwent->pw_gecos ? pwent->pw_gecos : "N/A", 32 | pwent->pw_dir ? pwent->pw_dir : "N/A", 33 | pwent->pw_shell ? pwent->pw_shell : "N/A"); 34 | } 35 | 36 | void call_getpwnam_r(const char *name) 37 | { 38 | enum nss_status status; 39 | struct passwd pwent; 40 | int err = 0; 41 | int buflen = 2048; 42 | char *buf = malloc(buflen); 43 | if (!buf) { 44 | fprintf(stderr, "Error: Memory allocation failed\n"); 45 | return; 46 | } 47 | 48 | status = _nss_octopass_getpwnam_r(name, &pwent, buf, buflen, &err); 49 | if (status == NSS_STATUS_SUCCESS) { 50 | show_pwent(&pwent); 51 | } else { 52 | fprintf(stderr, "Error: Failed to retrieve passwd entry for %s\n", name); 53 | } 54 | 55 | free(buf); 56 | } 57 | 58 | void call_getpwuid_r(uid_t uid) 59 | { 60 | enum nss_status status; 61 | struct passwd pwent; 62 | int err = 0; 63 | int buflen = 2048; 64 | char *buf = malloc(buflen); 65 | if (!buf) { 66 | fprintf(stderr, "Error: Memory allocation failed\n"); 67 | return; 68 | } 69 | 70 | status = _nss_octopass_getpwuid_r(uid, &pwent, buf, buflen, &err); 71 | if (status == NSS_STATUS_SUCCESS) { 72 | show_pwent(&pwent); 73 | } else { 74 | fprintf(stderr, "Error: Failed to retrieve passwd for uid %d\n", uid); 75 | } 76 | 77 | free(buf); 78 | } 79 | 80 | void call_pwlist(void) 81 | { 82 | enum nss_status status; 83 | struct passwd pwent; 84 | int err = 0; 85 | int buflen = 2048; 86 | char *buf = malloc(buflen); 87 | if (!buf) { 88 | fprintf(stderr, "Error: Memory allocation failed\n"); 89 | return; 90 | } 91 | 92 | status = _nss_octopass_setpwent(0); 93 | if (status != NSS_STATUS_SUCCESS) { 94 | fprintf(stderr, "Error: Failed to initialize passwd enumeration\n"); 95 | free(buf); 96 | return; 97 | } 98 | 99 | while ((status = _nss_octopass_getpwent_r(&pwent, buf, buflen, &err)) == NSS_STATUS_SUCCESS) { 100 | show_pwent(&pwent); 101 | } 102 | 103 | _nss_octopass_endpwent(); 104 | free(buf); 105 | } 106 | -------------------------------------------------------------------------------- /nss_octopass-passwd_test.c: -------------------------------------------------------------------------------- 1 | /* Management linux user and authentication with the organization/team on Github. 2 | Copyright (C) 2017 Tomohisa Oda 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . */ 16 | 17 | #define OCTOPASS_CONFIG_FILE "test/octopass.conf" 18 | #include 19 | #include "nss_octopass-passwd.c" 20 | 21 | extern void setup(void); 22 | 23 | Test(nss_octopass, getpwnam_r, .init = setup) 24 | { 25 | enum nss_status status; 26 | struct passwd pwent; 27 | int err = 0; 28 | int buflen = 2048; 29 | char buf[buflen]; 30 | 31 | const char *name = "linyows"; 32 | status = _nss_octopass_getpwnam_r(name, &pwent, buf, buflen, &err); 33 | 34 | cr_assert_eq(err, 0); 35 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 36 | cr_assert_str_eq(pwent.pw_name, "linyows"); 37 | cr_assert_str_eq(pwent.pw_passwd, "x"); 38 | cr_assert_eq(pwent.pw_uid, 74049); 39 | cr_assert_eq(pwent.pw_gid, 2000); 40 | cr_assert_str_eq(pwent.pw_gecos, "managed by octopass"); 41 | cr_assert_str_eq(pwent.pw_dir, "/home/linyows"); 42 | cr_assert_str_eq(pwent.pw_shell, "/bin/bash"); 43 | } 44 | 45 | Test(nss_octopass, getpwnam_r__when_team_member_not_found, .init = setup) 46 | { 47 | enum nss_status status; 48 | struct passwd pwent; 49 | int err = 0; 50 | int buflen = 2048; 51 | char buf[buflen]; 52 | 53 | const char *name = "linyowsno"; 54 | status = _nss_octopass_getpwnam_r(name, &pwent, buf, buflen, &err); 55 | 56 | cr_assert_eq(err, ENOENT); 57 | cr_assert_eq(status, NSS_STATUS_NOTFOUND); 58 | } 59 | 60 | Test(nss_octopass, pwent_list, .init = setup) 61 | { 62 | enum nss_status status; 63 | struct passwd pwent; 64 | int err = 0; 65 | unsigned long entry_number = 0; 66 | int buflen = 2048; 67 | char buf[buflen]; 68 | 69 | status = _nss_octopass_setpwent(0); 70 | cr_assert_eq(ent_json_idx, 0); 71 | cr_assert_eq(json_object_size(ent_json_root), 0); 72 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 73 | 74 | while (status == NSS_STATUS_SUCCESS) { 75 | entry_number += 1; 76 | err = 0; 77 | status = _nss_octopass_getpwent_r(&pwent, buf, buflen, &err); 78 | if (status != NSS_STATUS_SUCCESS) { 79 | continue; 80 | } 81 | 82 | cr_assert_eq(ent_json_idx, entry_number); 83 | cr_assert_eq(json_is_array(ent_json_root), 1); 84 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 85 | 86 | if (strcmp(pwent.pw_name, "linyows") != 0) { 87 | continue; 88 | } 89 | 90 | cr_assert_str_eq(pwent.pw_name, "linyows"); 91 | cr_assert_str_eq(pwent.pw_passwd, "x"); 92 | cr_assert_eq(pwent.pw_uid, 74049); 93 | cr_assert_eq(pwent.pw_gid, 2000); 94 | cr_assert_str_eq(pwent.pw_gecos, "managed by octopass"); 95 | cr_assert_str_eq(pwent.pw_dir, "/home/linyows"); 96 | cr_assert_str_eq(pwent.pw_shell, "/bin/bash"); 97 | } 98 | 99 | status = _nss_octopass_endpwent(); 100 | cr_assert_eq(ent_json_idx, 0); 101 | cr_assert_eq(ent_json_root, NULL); 102 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 103 | } 104 | 105 | Test(nss_octopass, pwent_list__when_team_not_exist, .init = setup) 106 | { 107 | putenv("OCTOPASS_TEAM=team_not_exists"); 108 | 109 | enum nss_status status; 110 | struct passwd pwent; 111 | int err = 0; 112 | unsigned long entry_number = 0; 113 | int buflen = 2048; 114 | char buf[buflen]; 115 | 116 | status = _nss_octopass_setpwent(0); 117 | cr_assert_eq(ent_json_idx, 0); 118 | cr_assert_eq(json_object_size(ent_json_root), 0); 119 | cr_assert_eq(status, NSS_STATUS_UNAVAIL); 120 | 121 | while (status == NSS_STATUS_SUCCESS) { 122 | entry_number += 1; 123 | err = 0; 124 | status = _nss_octopass_getpwent_r(&pwent, buf, buflen, &err); 125 | cr_assert_eq(status, NSS_STATUS_UNAVAIL); 126 | cr_assert_eq(ent_json_idx, 0); 127 | cr_assert_eq(ent_json_root, NULL); 128 | } 129 | 130 | status = _nss_octopass_endpwent(); 131 | cr_assert_eq(ent_json_idx, 0); 132 | cr_assert_eq(ent_json_root, NULL); 133 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 134 | 135 | clearenv(); 136 | } 137 | 138 | Test(nss_octopass, pwent_list__when_wrong_token, .init = setup) 139 | { 140 | putenv("OCTOPASS_TOKEN=wrong_token"); 141 | 142 | enum nss_status status; 143 | struct passwd pwent; 144 | int err = 0; 145 | unsigned long entry_number = 0; 146 | int buflen = 2048; 147 | char buf[buflen]; 148 | 149 | status = _nss_octopass_setpwent(0); 150 | cr_assert_eq(ent_json_idx, 0); 151 | cr_assert_eq(json_object_size(ent_json_root), 0); 152 | cr_assert_eq(status, NSS_STATUS_UNAVAIL); 153 | 154 | while (status == NSS_STATUS_SUCCESS) { 155 | entry_number += 1; 156 | err = 0; 157 | status = _nss_octopass_getpwent_r(&pwent, buf, buflen, &err); 158 | cr_assert_eq(status, NSS_STATUS_UNAVAIL); 159 | cr_assert_eq(ent_json_idx, 0); 160 | cr_assert_eq(ent_json_root, NULL); 161 | } 162 | 163 | status = _nss_octopass_endpwent(); 164 | cr_assert_eq(ent_json_idx, 0); 165 | cr_assert_eq(ent_json_root, NULL); 166 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 167 | 168 | clearenv(); 169 | } 170 | 171 | Test(nss_octopass, getpwuid_r, .init = setup) 172 | { 173 | enum nss_status status; 174 | struct passwd pwent; 175 | int err = 0; 176 | int buflen = 2048; 177 | char buf[buflen]; 178 | 179 | uid_t uid = 74049; 180 | status = _nss_octopass_getpwuid_r(uid, &pwent, buf, buflen, &err); 181 | 182 | cr_assert_eq(err, 0); 183 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 184 | cr_assert_str_eq(pwent.pw_name, "linyows"); 185 | cr_assert_str_eq(pwent.pw_passwd, "x"); 186 | cr_assert_eq(pwent.pw_uid, 74049); 187 | cr_assert_eq(pwent.pw_gid, 2000); 188 | cr_assert_str_eq(pwent.pw_gecos, "managed by octopass"); 189 | cr_assert_str_eq(pwent.pw_dir, "/home/linyows"); 190 | cr_assert_str_eq(pwent.pw_shell, "/bin/bash"); 191 | } 192 | -------------------------------------------------------------------------------- /nss_octopass-shadow.c: -------------------------------------------------------------------------------- 1 | /* Management linux user and authentication with the organization/team on Github. 2 | Copyright (C) 2017 Tomohisa Oda 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . */ 16 | 17 | #include "octopass.h" 18 | 19 | static pthread_mutex_t OCTOPASS_MUTEX = PTHREAD_MUTEX_INITIALIZER; 20 | static json_t *ent_json_root = NULL; 21 | static int ent_json_idx = 0; 22 | 23 | static int pack_shadow_struct(json_t *root, struct spwd *result, char *buffer, size_t buflen) 24 | { 25 | char *next_buf = buffer; 26 | size_t bufleft = buflen; 27 | 28 | if (!json_is_object(root)) { 29 | return -1; 30 | } 31 | 32 | json_t *j_sp_name = json_object_get(root, "login"); 33 | if (!json_is_string(j_sp_name)) { 34 | return -1; 35 | } 36 | 37 | const char *login = json_string_value(j_sp_name); 38 | memset(buffer, '\0', buflen); 39 | 40 | if (bufleft <= strlen(login) + 1) { 41 | return -2; 42 | } 43 | result->sp_namp = next_buf; 44 | strncpy(next_buf, login, bufleft - 1); 45 | next_buf[strlen(login)] = '\0'; 46 | 47 | next_buf += strlen(result->sp_namp) + 1; 48 | bufleft -= strlen(result->sp_namp) + 1; 49 | 50 | result->sp_pwdp = "!!"; 51 | result->sp_lstchg = -1; 52 | result->sp_min = -1; 53 | result->sp_max = -1; 54 | result->sp_warn = -1; 55 | result->sp_inact = -1; 56 | result->sp_expire = -1; 57 | result->sp_flag = ~0ul; 58 | 59 | return 0; 60 | } 61 | 62 | enum nss_status _nss_octopass_setspent_locked(int stayopen) 63 | { 64 | json_t *root = NULL; 65 | json_error_t error; 66 | 67 | struct config con; 68 | struct response res; 69 | octopass_config_loading(&con, OCTOPASS_CONFIG_FILE); 70 | 71 | if (con.syslog) { 72 | syslog(LOG_INFO, "%s[L%d] -- stay_open: %d", __func__, __LINE__, stayopen); 73 | } 74 | 75 | int status = octopass_members(&con, &res); 76 | if (status != 0) { 77 | if (con.syslog) { 78 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "UNAVAIL"); 79 | } 80 | return NSS_STATUS_UNAVAIL; 81 | } 82 | 83 | if (res.data == NULL) { 84 | if (con.syslog) { 85 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "UNAVAIL"); 86 | } 87 | return NSS_STATUS_UNAVAIL; 88 | } 89 | 90 | root = json_loads(res.data, 0, &error); 91 | free(res.data); 92 | res.data = NULL; 93 | 94 | if (!root) { 95 | if (con.syslog) { 96 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "UNAVAIL"); 97 | } 98 | return NSS_STATUS_UNAVAIL; 99 | } 100 | 101 | if (!json_is_array(root)) { 102 | json_decref(root); 103 | if (con.syslog) { 104 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "UNAVAIL"); 105 | } 106 | return NSS_STATUS_UNAVAIL; 107 | } 108 | 109 | if (ent_json_root) { 110 | json_decref(ent_json_root); 111 | } 112 | 113 | ent_json_root = root; 114 | ent_json_idx = 0; 115 | 116 | return NSS_STATUS_SUCCESS; 117 | } 118 | 119 | // Called to open the shadow file 120 | enum nss_status _nss_octopass_setspent(int stayopen) 121 | { 122 | enum nss_status status; 123 | 124 | OCTOPASS_LOCK(); 125 | status = _nss_octopass_setspent_locked(stayopen); 126 | OCTOPASS_UNLOCK(); 127 | 128 | return status; 129 | } 130 | 131 | enum nss_status _nss_octopass_endspent_locked(void) 132 | { 133 | if (ent_json_root) { 134 | json_decref(ent_json_root); 135 | ent_json_root = NULL; 136 | } 137 | 138 | ent_json_root = NULL; 139 | ent_json_idx = 0; 140 | 141 | return NSS_STATUS_SUCCESS; 142 | } 143 | 144 | // Called to close the shadow file 145 | enum nss_status _nss_octopass_endspent(void) 146 | { 147 | enum nss_status status; 148 | 149 | OCTOPASS_LOCK(); 150 | status = _nss_octopass_endspent_locked(); 151 | OCTOPASS_UNLOCK(); 152 | 153 | return status; 154 | } 155 | 156 | enum nss_status _nss_octopass_getspent_r_locked(struct spwd *result, char *buffer, size_t buflen, int *errnop) 157 | { 158 | enum nss_status status = NSS_STATUS_SUCCESS; 159 | 160 | if (ent_json_root == NULL) { 161 | status = _nss_octopass_setspent_locked(0); 162 | } 163 | 164 | if (status != NSS_STATUS_SUCCESS || ent_json_root == NULL) { 165 | if (status == NSS_STATUS_NOTFOUND || ent_json_root == NULL) { 166 | *errnop = ENOENT; 167 | } 168 | return NSS_STATUS_NOTFOUND; 169 | } 170 | 171 | size_t json_size = json_array_size(ent_json_root); 172 | 173 | // Return notfound when there's nothing else to read. 174 | if (ent_json_idx >= json_size) { 175 | *errnop = ENOENT; 176 | return NSS_STATUS_NOTFOUND; 177 | } 178 | 179 | json_t *json_entry = json_array_get(ent_json_root, ent_json_idx); 180 | if (json_entry == NULL || !json_is_object(json_entry)) { 181 | *errnop = ENOENT; 182 | return NSS_STATUS_NOTFOUND; 183 | } 184 | 185 | int pack_result = pack_shadow_struct(json_entry, result, buffer, buflen); 186 | 187 | // A necessary input file cannot be found. 188 | if (pack_result == -1) { 189 | *errnop = ENOENT; 190 | return NSS_STATUS_NOTFOUND; 191 | } 192 | 193 | if (pack_result == -2) { 194 | *errnop = ERANGE; 195 | return NSS_STATUS_TRYAGAIN; 196 | } 197 | 198 | ent_json_idx++; 199 | 200 | return NSS_STATUS_SUCCESS; 201 | } 202 | 203 | // Called to look up next entry in shadow file 204 | enum nss_status _nss_octopass_getspent_r(struct spwd *result, char *buffer, size_t buflen, int *errnop) 205 | { 206 | enum nss_status status; 207 | 208 | OCTOPASS_LOCK(); 209 | status = _nss_octopass_getspent_r_locked(result, buffer, buflen, errnop); 210 | OCTOPASS_UNLOCK(); 211 | 212 | return status; 213 | } 214 | 215 | enum nss_status _nss_octopass_getspnam_r_locked(const char *name, struct spwd *result, char *buffer, size_t buflen, 216 | int *errnop) 217 | { 218 | json_t *root = NULL; 219 | json_error_t error; 220 | enum nss_status status = NSS_STATUS_UNAVAIL; 221 | 222 | struct config con; 223 | struct response res; 224 | octopass_config_loading(&con, OCTOPASS_CONFIG_FILE); 225 | 226 | if (con.syslog) { 227 | syslog(LOG_INFO, "%s[L%d] -- name: %s", __func__, __LINE__, name); 228 | } 229 | 230 | int members_status = octopass_members(&con, &res); 231 | if (members_status != 0 || res.data == NULL) { 232 | *errnop = ENOENT; 233 | if (con.syslog) { 234 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "UNAVAIL"); 235 | } 236 | return NSS_STATUS_UNAVAIL; 237 | } 238 | 239 | root = json_loads(res.data, 0, &error); 240 | free(res.data); 241 | res.data = NULL; 242 | 243 | if (!root) { 244 | *errnop = ENOENT; 245 | if (con.syslog) { 246 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "UNAVAIL"); 247 | } 248 | return NSS_STATUS_UNAVAIL; 249 | } 250 | 251 | json_t *data = octopass_github_team_member_by_name((char *)name, root); 252 | if (!data || json_object_size(data) == 0) { 253 | status = NSS_STATUS_NOTFOUND; 254 | *errnop = ENOENT; 255 | if (con.syslog) { 256 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "NOTFOUND"); 257 | } 258 | goto cleanup; 259 | } 260 | 261 | int pack_result = pack_shadow_struct(data, result, buffer, buflen); 262 | if (pack_result == -1) { 263 | status = NSS_STATUS_NOTFOUND; 264 | *errnop = ENOENT; 265 | if (con.syslog) { 266 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "NOTFOUND"); 267 | } 268 | goto cleanup; 269 | } 270 | 271 | if (pack_result == -2) { 272 | status = NSS_STATUS_TRYAGAIN; 273 | *errnop = ERANGE; 274 | if (con.syslog) { 275 | syslog(LOG_INFO, "%s[L%d] -- status: %s", __func__, __LINE__, "TRYAGAIN"); 276 | } 277 | goto cleanup; 278 | } 279 | 280 | if (con.syslog) { 281 | syslog(LOG_INFO, "%s[L%d] -- status: %s, sp_namp: %s", __func__, __LINE__, "SUCCESS", result->sp_namp); 282 | } 283 | 284 | status = NSS_STATUS_SUCCESS; 285 | 286 | cleanup: 287 | json_decref(root); 288 | return status; 289 | } 290 | 291 | // Find a shadow by name 292 | enum nss_status _nss_octopass_getspnam_r(const char *name, struct spwd *result, char *buffer, size_t buflen, 293 | int *errnop) 294 | { 295 | enum nss_status status; 296 | 297 | OCTOPASS_LOCK(); 298 | status = _nss_octopass_getspnam_r_locked(name, result, buffer, buflen, errnop); 299 | OCTOPASS_UNLOCK(); 300 | 301 | return status; 302 | } 303 | -------------------------------------------------------------------------------- /nss_octopass-shadow_cli.c: -------------------------------------------------------------------------------- 1 | /* Management linux user and authentication with the organization/team on Github. 2 | Copyright (C) 2017 Tomohisa Oda 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . */ 16 | 17 | #include "nss_octopass-shadow.c" 18 | 19 | void show_spent(struct spwd *spent) 20 | { 21 | if (!spent || !spent->sp_namp || !spent->sp_pwdp) { 22 | fprintf(stderr, "Error: Invalid shadow entry\n"); 23 | return; 24 | } 25 | 26 | if (spent->sp_lstchg == -1 && spent->sp_min == -1 && spent->sp_max == -1 && spent->sp_warn == -1 && 27 | spent->sp_inact == -1 && spent->sp_expire == -1) { 28 | printf("%s:%s:::::::\n", 29 | spent->sp_namp ? spent->sp_namp : "N/A", 30 | spent->sp_pwdp ? spent->sp_pwdp : "N/A"); 31 | } else { 32 | printf("%s:%s:%ld:%ld:%ld:%ld:%ld:%ld:%ld\n", 33 | spent->sp_namp ? spent->sp_namp : "N/A", 34 | spent->sp_pwdp ? spent->sp_pwdp : "N/A", 35 | spent->sp_lstchg, 36 | spent->sp_min, 37 | spent->sp_max, 38 | spent->sp_warn, 39 | spent->sp_inact, 40 | spent->sp_expire, 41 | spent->sp_flag); 42 | } 43 | } 44 | 45 | void call_getspnam_r(const char *name) 46 | { 47 | enum nss_status status; 48 | struct spwd spent; 49 | int err = 0; 50 | int buflen = 2048; 51 | char *buf = malloc(buflen); 52 | if (!buf) { 53 | fprintf(stderr, "Error: Memory allocation failed\n"); 54 | return; 55 | } 56 | 57 | status = _nss_octopass_getspnam_r(name, &spent, buf, buflen, &err); 58 | if (status == NSS_STATUS_SUCCESS) { 59 | show_spent(&spent); 60 | } else { 61 | fprintf(stderr, "Error: Failed to retrieve shadow entry for %s\n", name); 62 | } 63 | 64 | free(buf); 65 | } 66 | 67 | void call_splist(void) 68 | { 69 | enum nss_status status; 70 | struct spwd spent; 71 | int err = 0; 72 | int buflen = 2048; 73 | char *buf = malloc(buflen); 74 | if (!buf) { 75 | fprintf(stderr, "Error: Memory allocation failed\n"); 76 | return; 77 | } 78 | 79 | status = _nss_octopass_setspent(0); 80 | if (status != NSS_STATUS_SUCCESS) { 81 | fprintf(stderr, "Error: Failed to initialize shadow enumeration\n"); 82 | free(buf); 83 | return; 84 | } 85 | 86 | while ((status = _nss_octopass_getspent_r(&spent, buf, buflen, &err)) == NSS_STATUS_SUCCESS) { 87 | show_spent(&spent); 88 | } 89 | 90 | status = _nss_octopass_endspent(); 91 | if (status != NSS_STATUS_SUCCESS) { 92 | fprintf(stderr, "Warning: Failed to end shadow enumeration\n"); 93 | } 94 | 95 | free(buf); 96 | } 97 | -------------------------------------------------------------------------------- /nss_octopass-shadow_test.c: -------------------------------------------------------------------------------- 1 | /* Management linux user and authentication with the organization/team on Github. 2 | Copyright (C) 2017 Tomohisa Oda 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . */ 16 | 17 | #define OCTOPASS_CONFIG_FILE "test/octopass.conf" 18 | #include 19 | #include "nss_octopass-shadow.c" 20 | 21 | extern void setup(void); 22 | 23 | Test(nss_octopass, getspnam_r, .init = setup) 24 | { 25 | enum nss_status status; 26 | struct spwd spent; 27 | int err = 0; 28 | int buflen = 2048; 29 | char buf[buflen]; 30 | 31 | const char *name = "linyows"; 32 | status = _nss_octopass_getspnam_r(name, &spent, buf, buflen, &err); 33 | 34 | cr_assert_eq(err, 0); 35 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 36 | cr_assert_str_eq(spent.sp_namp, "linyows"); 37 | cr_assert_str_eq(spent.sp_pwdp, "!!"); 38 | cr_assert_eq(spent.sp_lstchg, -1); 39 | cr_assert_eq(spent.sp_min, -1); 40 | cr_assert_eq(spent.sp_max, -1); 41 | cr_assert_eq(spent.sp_warn, -1); 42 | cr_assert_eq(spent.sp_inact, -1); 43 | cr_assert_eq(spent.sp_expire, -1); 44 | cr_assert_eq(spent.sp_flag, ~0ul); 45 | } 46 | 47 | Test(nss_octopass, getspnam_r__when_team_member_not_found, .init = setup) 48 | { 49 | enum nss_status status; 50 | struct spwd spent; 51 | int err = 0; 52 | int buflen = 2048; 53 | char buf[buflen]; 54 | 55 | const char *name = "linyowsno"; 56 | status = _nss_octopass_getspnam_r(name, &spent, buf, buflen, &err); 57 | 58 | cr_assert_eq(err, ENOENT); 59 | cr_assert_eq(status, NSS_STATUS_NOTFOUND); 60 | } 61 | 62 | Test(nss_octopass, spent_list, .init = setup) 63 | { 64 | enum nss_status status; 65 | struct spwd spent; 66 | int err = 0; 67 | unsigned long entry_number = 0; 68 | int buflen = 2048; 69 | char buf[buflen]; 70 | 71 | status = _nss_octopass_setspent(0); 72 | cr_assert_eq(ent_json_idx, 0); 73 | cr_assert_eq(json_object_size(ent_json_root), 0); 74 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 75 | 76 | while (status == NSS_STATUS_SUCCESS) { 77 | entry_number += 1; 78 | status = _nss_octopass_getspent_r(&spent, buf, buflen, &err); 79 | if (status != NSS_STATUS_SUCCESS) { 80 | continue; 81 | } 82 | 83 | cr_assert_eq(ent_json_idx, entry_number); 84 | cr_assert_eq(json_is_array(ent_json_root), 1); 85 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 86 | 87 | if (strcmp(spent.sp_namp, "linyows") != 0) { 88 | continue; 89 | } 90 | 91 | cr_assert_str_eq(spent.sp_namp, "linyows"); 92 | cr_assert_str_eq(spent.sp_pwdp, "!!"); 93 | cr_assert_eq(spent.sp_lstchg, -1); 94 | cr_assert_eq(spent.sp_min, -1); 95 | cr_assert_eq(spent.sp_max, -1); 96 | cr_assert_eq(spent.sp_warn, -1); 97 | cr_assert_eq(spent.sp_inact, -1); 98 | cr_assert_eq(spent.sp_expire, -1); 99 | cr_assert_eq(spent.sp_flag, ~0ul); 100 | } 101 | 102 | status = _nss_octopass_endspent(); 103 | cr_assert_eq(ent_json_idx, 0); 104 | cr_assert_eq(ent_json_root, NULL); 105 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 106 | } 107 | 108 | Test(nss_octopass, spent_list__when_team_not_exist, .init = setup) 109 | { 110 | putenv("OCTOPASS_TEAM=team_not_exists"); 111 | 112 | enum nss_status status; 113 | struct spwd spent; 114 | int err = 0; 115 | unsigned long entry_number = 0; 116 | int buflen = 2048; 117 | char buf[buflen]; 118 | 119 | status = _nss_octopass_setspent(0); 120 | cr_assert_eq(ent_json_idx, 0); 121 | cr_assert_eq(json_object_size(ent_json_root), 0); 122 | cr_assert_eq(status, NSS_STATUS_UNAVAIL); 123 | 124 | while (status == NSS_STATUS_SUCCESS) { 125 | entry_number += 1; 126 | status = _nss_octopass_getspent_r(&spent, buf, buflen, &err); 127 | cr_assert_eq(status, NSS_STATUS_UNAVAIL); 128 | cr_assert_eq(ent_json_idx, 0); 129 | cr_assert_eq(ent_json_root, NULL); 130 | } 131 | 132 | status = _nss_octopass_endspent(); 133 | cr_assert_eq(ent_json_idx, 0); 134 | cr_assert_eq(ent_json_root, NULL); 135 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 136 | 137 | clearenv(); 138 | } 139 | 140 | Test(nss_octopass, spent_list__when_wrong_token, .init = setup) 141 | { 142 | putenv("OCTOPASS_TOKEN=wrong_token"); 143 | 144 | enum nss_status status; 145 | struct spwd spent; 146 | int err = 0; 147 | unsigned long entry_number = 0; 148 | int buflen = 2048; 149 | char buf[buflen]; 150 | 151 | status = _nss_octopass_setspent(0); 152 | cr_assert_eq(ent_json_idx, 0); 153 | cr_assert_eq(json_object_size(ent_json_root), 0); 154 | cr_assert_eq(status, NSS_STATUS_UNAVAIL); 155 | 156 | while (status == NSS_STATUS_SUCCESS) { 157 | entry_number += 1; 158 | status = _nss_octopass_getspent_r(&spent, buf, buflen, &err); 159 | cr_assert_eq(status, NSS_STATUS_UNAVAIL); 160 | cr_assert_eq(ent_json_idx, 0); 161 | cr_assert_eq(ent_json_root, NULL); 162 | } 163 | 164 | status = _nss_octopass_endspent(); 165 | cr_assert_eq(ent_json_idx, 0); 166 | cr_assert_eq(ent_json_root, NULL); 167 | cr_assert_eq(status, NSS_STATUS_SUCCESS); 168 | 169 | clearenv(); 170 | } 171 | -------------------------------------------------------------------------------- /octopass.c: -------------------------------------------------------------------------------- 1 | /* Management linux user and authentication with the organization/team on Github. 2 | Copyright (C) 2017 Tomohisa Oda 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . */ 16 | 17 | #include 18 | #include "octopass.h" 19 | 20 | static size_t write_response_callback(void *contents, size_t size, size_t nmemb, void *userp) 21 | { 22 | size_t realsize = size * nmemb; 23 | struct response *res = (struct response *)userp; 24 | 25 | if (realsize > OCTOPASS_MAX_BUFFER_SIZE) { 26 | fprintf(stderr, "Response is too large\n"); 27 | return 0; 28 | } 29 | 30 | res->data = realloc(res->data, res->size + realsize + 1); 31 | if (res->data == NULL) { 32 | // out of memory! 33 | fprintf(stderr, "Not enough memory (realloc returned NULL)\n"); 34 | return 0; 35 | } 36 | 37 | memcpy(&(res->data[res->size]), contents, realsize); 38 | res->size += realsize; 39 | res->data[res->size] = 0; 40 | 41 | return realsize; 42 | } 43 | 44 | void octopass_remove_quotes(char *s) 45 | { 46 | if (s == NULL) { 47 | return; 48 | } 49 | 50 | if (s[strlen(s) - 1] == '"') { 51 | s[strlen(s) - 1] = '\0'; 52 | } 53 | 54 | int i = 0; 55 | while (s[i] != '\0' && s[i] == '"') 56 | i++; 57 | memmove(s, &s[i], strlen(s)); 58 | } 59 | 60 | const char *octopass_truncate(const char *str, int len) 61 | { 62 | char s[len + 1]; 63 | strncpy(s, str, len); 64 | *(s + len) = '\0'; 65 | char *res = strdup(s); 66 | return res; 67 | } 68 | 69 | const char *octopass_masking(const char *token) 70 | { 71 | int len = 5; 72 | char s[strlen(token) + 1]; 73 | sprintf(s, "%s ************ REDACTED ************", octopass_truncate(token, len)); 74 | char *mask = strdup(s); 75 | return mask; 76 | } 77 | 78 | char *octopass_url_normalization(char *url) 79 | { 80 | if (url == NULL) { 81 | fprintf(stderr, "Error: NULL URL passed to octopass_url_normalization\n"); 82 | return NULL; 83 | } 84 | 85 | size_t url_length = strlen(url); 86 | 87 | // only if the URL has no slashes and does not end with a slash 88 | if (url_length > 0 && url[url_length - 1] == '/') { 89 | return strdup(url); 90 | } 91 | 92 | // add a trailing slash and terminator 93 | size_t new_length = url_length + 2; 94 | char *res = malloc(new_length); 95 | if (!res) { 96 | fprintf(stderr, "Memory allocation failed in octopass_url_normalization\n"); 97 | return NULL; 98 | } 99 | 100 | // add URL and slash to dynamic buffer 101 | snprintf(res, new_length, "%s/", url); 102 | 103 | // needs free 104 | return res; 105 | } 106 | 107 | // Unatched: 0 108 | // Matched: 1 109 | int octopass_match(char *str, char *pattern, char **matched) 110 | { 111 | regex_t re; 112 | regmatch_t pm; 113 | int res = regcomp(&re, pattern, REG_EXTENDED); 114 | if (res != 0) { 115 | regfree(&re); 116 | return 0; 117 | } 118 | 119 | int cnt = 0; 120 | int offset = 0; 121 | 122 | // try the first match with a regular expression 123 | res = regexec(&re, str + offset, 1, &pm, 0); 124 | if (res != 0) { 125 | regfree(&re); 126 | return 0; 127 | } 128 | 129 | while (res == 0) { 130 | // calculate match start and end positions 131 | // IMPORTANT: +1, -1 for getting the quotes inside 132 | int match_start = pm.rm_so + 1; 133 | int match_end = pm.rm_eo - 1; 134 | int match_len = match_end - match_start; 135 | 136 | char *match_word = malloc(match_len + 1); 137 | if (!match_word) { 138 | fprintf(stderr, "Memory allocation failed in octopass_match\n"); 139 | regfree(&re); 140 | return cnt; 141 | } 142 | 143 | // copy for matched 144 | strncpy(match_word, str + offset + match_start, match_len); 145 | match_word[match_len] = '\0'; 146 | 147 | // append matched 148 | matched[cnt] = match_word; 149 | cnt++; 150 | 151 | // update offset for next match 152 | offset += pm.rm_eo; 153 | 154 | // try next match 155 | res = regexec(&re, str + offset, 1, &pm, 0); 156 | } 157 | 158 | regfree(&re); 159 | return cnt; 160 | } 161 | 162 | void octopass_override_config_by_env(struct config *con) 163 | { 164 | if (!con) { 165 | fprintf(stderr, "Error: NULL pointer passed to octopass_override_config_by_env\n"); 166 | return; 167 | } 168 | 169 | const char *env_vars[] = { 170 | "OCTOPASS_TOKEN", "OCTOPASS_ENDPOINT", "OCTOPASS_ORGANIZATION", 171 | "OCTOPASS_TEAM", "OCTOPASS_OWNER", "OCTOPASS_REPOSITORY", "OCTOPASS_PERMISSION" 172 | }; 173 | char *config_vars[] = { 174 | con->token, con->endpoint, con->organization, 175 | con->team, con->owner, con->repository, con->permission 176 | }; 177 | 178 | for (size_t i = 0; i < sizeof(env_vars) / sizeof(env_vars[0]); i++) { 179 | char *value = getenv(env_vars[i]); 180 | if (value && strlen(value) < MAXBUF) { 181 | snprintf(config_vars[i], MAXBUF, "%s", value); 182 | } else if (value) { 183 | fprintf(stderr, "Warning: Environment variable %s is too long and will be truncated.\n", env_vars[i]); 184 | strncpy(config_vars[i], value, MAXBUF - 1); 185 | config_vars[i][MAXBUF - 1] = '\0'; 186 | } 187 | } 188 | 189 | char *endpoint = getenv("OCTOPASS_ENDPOINT"); 190 | if (endpoint) { 191 | char *url = octopass_url_normalization(endpoint); 192 | if (url) { 193 | snprintf(con->endpoint, MAXBUF, "%s", url); 194 | free(url); 195 | } 196 | } 197 | } 198 | 199 | int octopass_config_loading(struct config *con, char *filename) 200 | { 201 | if (!con || !filename) { 202 | fprintf(stderr, "Invalid arguments to octopass_config_loading\n"); 203 | return -1; 204 | } 205 | 206 | memset(con->endpoint, '\0', sizeof(con->endpoint)); 207 | memset(con->token, '\0', sizeof(con->token)); 208 | memset(con->organization, '\0', sizeof(con->organization)); 209 | memset(con->team, '\0', sizeof(con->team)); 210 | memset(con->repository, '\0', sizeof(con->repository)); 211 | memset(con->permission, '\0', sizeof(con->permission)); 212 | memset(con->group_name, '\0', sizeof(con->group_name)); 213 | memset(con->home, '\0', sizeof(con->home)); 214 | memset(con->shell, '\0', sizeof(con->shell)); 215 | con->uid_starts = 2000; 216 | con->gid = 2000; 217 | con->cache = 500; 218 | con->syslog = false; 219 | con->shared_users_count = 0; 220 | 221 | FILE *file = fopen(filename, "r"); 222 | if (file == NULL) { 223 | fprintf(stderr, "Config not found: %s\n", filename); 224 | return -1; 225 | } 226 | 227 | char *line = NULL; 228 | size_t len = 0; 229 | 230 | while (getline(&line, &len, file) != -1) { 231 | // delete line-feeds 232 | line[strcspn(line, "\r\n")] = '\0'; 233 | 234 | // skip empty lines 235 | if (line[0] == '\0') { 236 | continue; 237 | } 238 | 239 | char *lasts; 240 | char *key = strtok_r(line, DELIM, &lasts); 241 | char *value = strtok_r(NULL, DELIM, &lasts); 242 | 243 | if (!key || !value) { 244 | continue; 245 | } 246 | 247 | if (strcmp(key, "SharedUsers") == 0 && lasts && strlen(lasts) > 0) { 248 | size_t vlen = strlen(value) + strlen(lasts) + 2; 249 | char *v = malloc(vlen); 250 | if (!v) { 251 | fprintf(stderr, "Memory allocation failed for SharedUsers\n"); 252 | free(line); 253 | fclose(file); 254 | return -1; 255 | } 256 | snprintf(v, vlen, "%s %s", value, lasts); 257 | value = v; 258 | } else { 259 | octopass_remove_quotes(value); 260 | } 261 | 262 | if (strcmp(key, "Endpoint") == 0) { 263 | char *url = octopass_url_normalization(value); 264 | snprintf(con->endpoint, sizeof(con->endpoint), "%s", url); 265 | } else if (strcmp(key, "Token") == 0) { 266 | snprintf(con->token, sizeof(con->token), "%s", value); 267 | } else if (strcmp(key, "Organization") == 0) { 268 | snprintf(con->organization, sizeof(con->organization), "%s", value); 269 | } else if (strcmp(key, "Team") == 0) { 270 | snprintf(con->team, sizeof(con->team), "%s", value); 271 | } else if (strcmp(key, "Owner") == 0) { 272 | snprintf(con->owner, sizeof(con->owner), "%s", value); 273 | } else if (strcmp(key, "Repository") == 0) { 274 | snprintf(con->repository, sizeof(con->repository), "%s", value); 275 | } else if (strcmp(key, "Permission") == 0) { 276 | snprintf(con->permission, sizeof(con->permission), "%s", value); 277 | } else if (strcmp(key, "Group") == 0) { 278 | snprintf(con->group_name, sizeof(con->group_name), "%s", value); 279 | } else if (strcmp(key, "Home") == 0) { 280 | snprintf(con->home, sizeof(con->home), "%s", value); 281 | } else if (strcmp(key, "Shell") == 0) { 282 | snprintf(con->shell, sizeof(con->shell), "%s", value); 283 | } else if (strcmp(key, "UidStarts") == 0 && isdigit(value[0])) { 284 | con->uid_starts = atoi(value); 285 | } else if (strcmp(key, "Gid") == 0 && isdigit(value[0])) { 286 | con->gid = atoi(value); 287 | } else if (strcmp(key, "Cache") == 0 && isdigit(value[0])) { 288 | con->cache = atoi(value); 289 | } else if (strcmp(key, "Syslog") == 0) { 290 | con->syslog = (strcmp(value, "true") == 0); 291 | } else if (strcmp(key, "SharedUsers") == 0) { 292 | char *pattern = "\"([A-Za-z0-9_-]+)\""; 293 | con->shared_users = calloc(MAXBUF, sizeof(char *)); 294 | if (!con->shared_users) { 295 | fprintf(stderr, "Memory allocation failed for shared_users\n"); 296 | free(value); 297 | free(line); 298 | fclose(file); 299 | return -1; 300 | } 301 | con->shared_users_count = octopass_match(value, pattern, con->shared_users); 302 | free(value); 303 | } 304 | } 305 | 306 | free(line); 307 | fclose(file); 308 | 309 | octopass_override_config_by_env(con); 310 | 311 | if (con->endpoint[0] == '\0') { 312 | snprintf(con->endpoint, sizeof(con->endpoint), "%s", OCTOPASS_API_ENDPOINT); 313 | } 314 | if (con->group_name[0] == '\0') { 315 | snprintf(con->group_name, sizeof(con->group_name), "%s", 316 | con->repository[0] != '\0' ? con->repository : con->team); 317 | } 318 | if (con->owner[0] == '\0' && con->organization[0] != '\0') { 319 | snprintf(con->owner, sizeof(con->owner), "%s", con->organization); 320 | } 321 | if (con->repository[0] != '\0' && con->permission[0] == '\0') { 322 | snprintf(con->permission, sizeof(con->permission), "write"); 323 | } 324 | if (con->home[0] == '\0') { 325 | snprintf(con->home, sizeof(con->home), "%s", "/home/%s"); 326 | } 327 | if (con->shell[0] == '\0') { 328 | snprintf(con->shell, sizeof(con->shell), "/bin/bash"); 329 | } 330 | 331 | if (con->syslog) { 332 | const char *pg_name = "octopass"; 333 | openlog(pg_name, LOG_CONS | LOG_PID, LOG_USER); 334 | syslog(LOG_INFO, "config {endpoint: %s, token: %s, organization: %s, team: %s, owner: %s, repository: %s, permission: %s " 335 | "syslog: %d, uid_starts: %ld, gid: %ld, group_name: %s, home: %s, shell: %s, cache: %ld}", 336 | con->endpoint, octopass_masking(con->token), con->organization, con->team, con->owner, con->repository, con->permission, 337 | con->syslog, con->uid_starts, con->gid, con->group_name, con->home, con->shell, con->cache); 338 | } 339 | 340 | return 0; 341 | } 342 | 343 | void octopass_export_file(struct config *con, char *dir, char *file, char *data) 344 | { 345 | struct stat statbuf; 346 | if (stat(dir, &statbuf) != 0) { 347 | mode_t um = {0}; 348 | um = umask(0); 349 | mkdir(dir, S_IRUSR | S_IWUSR | S_IXUSR); 350 | umask(um); 351 | } 352 | 353 | FILE *fp = fopen(file, "w"); 354 | if (!fp) { 355 | if (con->syslog) { 356 | syslog(LOG_ERR, "File open failure: %s %s", file, strerror(errno)); 357 | } else { 358 | fprintf(stderr, "File open failure: %s %s\n", file, strerror(errno)); 359 | } 360 | exit(1); 361 | return; 362 | } 363 | fprintf(fp, "%s", data); 364 | 365 | mode_t um = {0}; 366 | um = umask(0); 367 | fchmod(fileno(fp), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IROTH); 368 | umask(um); 369 | 370 | fclose(fp); 371 | } 372 | 373 | const char *octopass_import_file(struct config *con, char *file) 374 | { 375 | FILE *fp = fopen(file, "r"); 376 | if (!fp) { 377 | if (con->syslog) { 378 | syslog(LOG_ERR, "File open failure: %s %s", file, strerror(errno)); 379 | } else { 380 | fprintf(stderr, "File open failure: %s %s\n", file, strerror(errno)); 381 | } 382 | exit(1); 383 | } 384 | 385 | char *data = NULL; 386 | size_t data_size = 0; 387 | size_t data_capacity = MAXBUF; 388 | 389 | data = malloc(data_capacity); 390 | if (!data) { 391 | fprintf(stderr, "Malloc failed\n"); 392 | fclose(fp); 393 | return NULL; 394 | } 395 | data[0] = '\0'; 396 | 397 | char *line = NULL; 398 | size_t line_len = 0; 399 | 400 | while (getline(&line, &line_len, fp) != -1) { 401 | size_t line_size = strlen(line); 402 | 403 | // Expand buffers when over data_capacity 404 | if (data_size + line_size + 1 > data_capacity) { 405 | data_capacity *= 2; 406 | char *new_data = realloc(data, data_capacity); 407 | if (!new_data) { 408 | fprintf(stderr, "Realloc failed\n"); 409 | free(data); 410 | free(line); 411 | fclose(fp); 412 | return NULL; 413 | } 414 | data = new_data; 415 | } 416 | 417 | // append a row to data 418 | strcat(data, line); 419 | data_size += line_size; 420 | } 421 | 422 | free(line); 423 | fclose(fp); 424 | 425 | // return copy 426 | const char *res = strdup(data); 427 | free(data); 428 | 429 | return res; 430 | } 431 | 432 | void octopass_github_request_without_cache(struct config *con, char *url, struct response *res, char *token) 433 | { 434 | if (!con || !url || !res) { 435 | fprintf(stderr, "Invalid arguments passed to octopass_github_request_without_cache\n"); 436 | return; 437 | } 438 | 439 | if (con->syslog) { 440 | syslog(LOG_INFO, "http get -- %s", url); 441 | } 442 | 443 | size_t auth_size = snprintf(NULL, 0, "Authorization: token %s", token ? token : con->token) + 1; 444 | char *auth = malloc(auth_size); 445 | if (!auth) { 446 | fprintf(stderr, "Memory allocation failed for auth header\n"); 447 | return; 448 | } 449 | snprintf(auth, auth_size, "Authorization: token %s", token ? token : con->token); 450 | 451 | CURL *hnd = curl_easy_init(); 452 | if (!hnd) { 453 | fprintf(stderr, "Failed to initialize cURL\n"); 454 | free(auth); 455 | return; 456 | } 457 | 458 | CURLcode result; 459 | struct curl_slist *headers = NULL; 460 | res->data = calloc(1, sizeof(char)); 461 | res->size = 0; 462 | res->httpstatus = 0; 463 | 464 | headers = curl_slist_append(headers, auth); 465 | if (!headers) { 466 | fprintf(stderr, "Failed to set cURL headers\n"); 467 | curl_easy_cleanup(hnd); 468 | free(auth); 469 | return; 470 | } 471 | 472 | curl_easy_setopt(hnd, CURLOPT_URL, url); 473 | curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 1L); 474 | curl_easy_setopt(hnd, CURLOPT_USERAGENT, OCTOPASS_VERSION_WITH_NAME); 475 | curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, headers); 476 | curl_easy_setopt(hnd, CURLOPT_MAXREDIRS, 3L); 477 | curl_easy_setopt(hnd, CURLOPT_TIMEOUT, 15L); 478 | curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, write_response_callback); 479 | curl_easy_setopt(hnd, CURLOPT_WRITEDATA, res); 480 | 481 | result = curl_easy_perform(hnd); 482 | 483 | if (result != CURLE_OK) { 484 | if (con->syslog) { 485 | syslog(LOG_ERR, "cURL failed: %s", curl_easy_strerror(result)); 486 | } else { 487 | fprintf(stderr, "cURL failed: %s\n", curl_easy_strerror(result)); 488 | } 489 | } else { 490 | long http_code = 0; 491 | curl_easy_getinfo(hnd, CURLINFO_RESPONSE_CODE, &http_code); 492 | res->httpstatus = http_code; 493 | if (con->syslog) { 494 | syslog(LOG_INFO, "http status: %ld -- %lu bytes retrieved", http_code, (long)res->size); 495 | } 496 | } 497 | 498 | // clean-up 499 | curl_easy_cleanup(hnd); 500 | curl_slist_free_all(headers); 501 | free(auth); 502 | 503 | if (!res->data) { 504 | res->data = calloc(1, sizeof(char)); 505 | } 506 | } 507 | 508 | void octopass_github_request(struct config *con, char *url, struct response *res) 509 | { 510 | char *token = NULL; 511 | if (con->cache == 0) { 512 | octopass_github_request_without_cache(con, url, res, token); 513 | return; 514 | } 515 | 516 | char *base = curl_escape(url, strlen(url)); 517 | if (!base) { 518 | fprintf(stderr, "Failed to escape URL\n"); 519 | exit(1); 520 | } 521 | 522 | // create dir path with dynamic buffers 523 | size_t dpath_size = snprintf(NULL, 0, "%s/%d", OCTOPASS_CACHE_DIR, geteuid()) + 1; 524 | char *dpath = malloc(dpath_size); 525 | if (!dpath) { 526 | fprintf(stderr, "Memory allocation failed for dpath\n"); 527 | curl_free(base); 528 | exit(1); 529 | } 530 | snprintf(dpath, dpath_size, "%s/%d", OCTOPASS_CACHE_DIR, geteuid()); 531 | 532 | // create file path with dynamic buffers 533 | size_t fpath_size = snprintf(NULL, 0, "%s/%s-%s", dpath, base, octopass_truncate(con->token, 6)) + 1; 534 | char *fpath = malloc(fpath_size); 535 | if (!fpath) { 536 | fprintf(stderr, "Memory allocation failed for fpath\n"); 537 | free(dpath); 538 | curl_free(base); 539 | exit(1); 540 | } 541 | snprintf(fpath, fpath_size, "%s/%s-%s", dpath, base, octopass_truncate(con->token, 6)); 542 | 543 | curl_free(base); 544 | FILE *fp = fopen(fpath, "r"); 545 | 546 | if (fp == NULL) { 547 | octopass_github_request_without_cache(con, url, res, token); 548 | if (res->httpstatus == 200) { 549 | octopass_export_file(con, dpath, fpath, res->data); 550 | } 551 | } else { 552 | fclose(fp); 553 | 554 | struct stat statbuf; 555 | if (stat(fpath, &statbuf) != -1) { 556 | unsigned long now = time(NULL); 557 | unsigned long diff = now - statbuf.st_mtime; 558 | if (diff > con->cache) { 559 | octopass_github_request_without_cache(con, url, res, token); 560 | if (res->httpstatus == 200) { 561 | octopass_export_file(con, dpath, fpath, res->data); 562 | } 563 | free(dpath); 564 | free(fpath); 565 | return; 566 | } 567 | } 568 | 569 | if (con->syslog) { 570 | syslog(LOG_INFO, "use cache: %s", fpath); 571 | } 572 | 573 | res->data = (char *)octopass_import_file(con, fpath); 574 | res->size = strlen(res->data); 575 | } 576 | 577 | free(dpath); 578 | free(fpath); 579 | } 580 | 581 | int octopass_github_team_id(char *team_name, char *data) 582 | { 583 | json_error_t error; 584 | json_t *teams = json_loads(data, 0, &error); 585 | json_t *team; 586 | int i; 587 | int team_id = -1; 588 | 589 | if (!teams || !json_is_array(teams)) { 590 | return -1; 591 | } 592 | 593 | json_array_foreach(teams, i, team) 594 | { 595 | if (!json_is_object(team)) { 596 | continue; 597 | } 598 | const char *name = json_string_value(json_object_get(team, "name")); 599 | if (name != NULL && strcmp(team_name, name) == 0) { 600 | team_id = json_integer_value(json_object_get(team, "id")); 601 | break; 602 | } 603 | const char *slug = json_string_value(json_object_get(team, "slug")); 604 | if (slug != NULL && strcmp(team_name, slug) == 0) { 605 | team_id = json_integer_value(json_object_get(team, "id")); 606 | break; 607 | } 608 | } 609 | 610 | json_decref(teams); 611 | 612 | return team_id; 613 | } 614 | 615 | json_t *octopass_github_team_member_by_name(char *name, json_t *members) 616 | { 617 | json_t *member; 618 | int i; 619 | 620 | json_array_foreach(members, i, member) 621 | { 622 | const char *u = json_string_value(json_object_get(member, "login")); 623 | if (strcmp(name, u) == 0) { 624 | return member; 625 | } 626 | } 627 | 628 | return json_object(); 629 | } 630 | 631 | json_t *octopass_github_team_member_by_id(int gh_id, json_t *members) 632 | { 633 | json_t *member; 634 | int i; 635 | 636 | json_array_foreach(members, i, member) 637 | { 638 | const json_int_t id = json_integer_value(json_object_get(member, "id")); 639 | if (id == gh_id) { 640 | return member; 641 | } 642 | } 643 | 644 | return json_object(); 645 | } 646 | 647 | int octopass_team_id(struct config *con) 648 | { 649 | size_t url_size = snprintf(NULL, 0, OCTOPASS_TEAMS_URL, con->endpoint, con->organization) + 1; 650 | char *url = malloc(url_size); 651 | if (!url) { 652 | fprintf(stderr, "Memory allocation failed for teams URL\n"); 653 | return -1; 654 | } 655 | snprintf(url, url_size, OCTOPASS_TEAMS_URL, con->endpoint, con->organization); 656 | 657 | struct response res; 658 | octopass_github_request(con, url, &res); 659 | 660 | if (!res.data) { 661 | fprintf(stderr, "Request failure\n"); 662 | if (con->syslog) { 663 | closelog(); 664 | } 665 | return -1; 666 | } 667 | 668 | int id = octopass_github_team_id(con->team, res.data); 669 | free(res.data); 670 | 671 | return id; 672 | } 673 | 674 | int octopass_team_members_by_team_id(struct config *con, int team_id, struct response *res) 675 | { 676 | size_t url_size = snprintf(NULL, 0, OCTOPASS_TEAMS_MEMBERS_URL, con->endpoint, team_id) + 1; 677 | char *url = malloc(url_size); 678 | if (!url) { 679 | fprintf(stderr, "Memory allocation failed for team members URL\n"); 680 | return -1; 681 | } 682 | snprintf(url, url_size, OCTOPASS_TEAMS_MEMBERS_URL, con->endpoint, team_id); 683 | 684 | octopass_github_request(con, url, res); 685 | 686 | if (!res->data) { 687 | fprintf(stderr, "Request failure\n"); 688 | if (con->syslog) { 689 | closelog(); 690 | } 691 | return -1; 692 | } 693 | 694 | return 0; 695 | } 696 | 697 | int octopass_team_members(struct config *con, struct response *res) 698 | { 699 | int team_id = octopass_team_id(con); 700 | if (team_id == -1) { 701 | if (con->syslog) { 702 | syslog(LOG_INFO, "team not found: %s", con->team); 703 | } 704 | return -1; 705 | } 706 | 707 | int status = octopass_team_members_by_team_id(con, team_id, res); 708 | if (status == -1) { 709 | return -1; 710 | } 711 | 712 | return 0; 713 | } 714 | 715 | char *octopass_permission_level(char *permission) 716 | { 717 | char *level; 718 | 719 | if (strcmp(permission, "admin") == 0) { 720 | level = "admin"; 721 | } else if (strcmp(permission, "write") == 0) { 722 | level = "push"; 723 | } else if (strcmp(permission, "read") == 0) { 724 | level = "pull"; 725 | } else { 726 | fprintf(stderr, "Unknown permission: %s\n", permission); 727 | } 728 | 729 | return level; 730 | } 731 | 732 | int octopass_is_authorized_collaborator(struct config *con, json_t *collaborator) 733 | { 734 | if (!json_is_object(collaborator)) { 735 | return 0; 736 | } 737 | 738 | json_t *permissions = json_object_get(collaborator, "permissions"); 739 | if (!json_is_object(permissions)) { 740 | return 0; 741 | } 742 | 743 | char *level = octopass_permission_level(con->permission); 744 | json_t *permission = json_object_get(permissions, level); 745 | return json_is_true(permission) ? 1 : 0; 746 | } 747 | 748 | int octopass_rebuild_data_with_authorized(struct config *con, struct response *res) 749 | { 750 | json_error_t error; 751 | json_t *collaborators = json_loads(res->data, 0, &error); 752 | json_t *collaborator; 753 | json_t *new_data; 754 | int i; 755 | 756 | if (!collaborators || !json_is_array(collaborators)) { 757 | return -1; 758 | } 759 | new_data = json_array(); 760 | 761 | json_array_foreach(collaborators, i, collaborator) 762 | { 763 | if (1 == octopass_is_authorized_collaborator(con, collaborator)) { 764 | json_array_append(new_data, collaborator); 765 | } 766 | } 767 | 768 | if (res->data) { 769 | free(res->data); 770 | res->data = NULL; 771 | } 772 | res->data = json_dumps(new_data, 0); 773 | 774 | json_decref(collaborators); 775 | json_decref(new_data); 776 | 777 | return 0; 778 | } 779 | 780 | int octopass_repository_collaborators(struct config *con, struct response *res) 781 | { 782 | size_t url_size = snprintf(NULL, 0, OCTOPASS_COLLABORATORS_URL, con->endpoint, con->owner, con->repository) + 1; 783 | char *url = malloc(url_size); 784 | if (!url) { 785 | fprintf(stderr, "Memory allocation failed for collaborators URL\n"); 786 | return -1; 787 | } 788 | snprintf(url, url_size, OCTOPASS_COLLABORATORS_URL, con->endpoint, con->owner, con->repository); 789 | 790 | octopass_github_request(con, url, res); 791 | 792 | if (!res->data) { 793 | fprintf(stderr, "Request failure\n"); 794 | if (con->syslog) { 795 | closelog(); 796 | } 797 | return -1; 798 | } 799 | 800 | return octopass_rebuild_data_with_authorized(con, res); 801 | } 802 | 803 | int is_target_team(const char *name, const char *targets[], size_t target_count) { 804 | for (size_t i = 0; i < target_count; i++) { 805 | if (strcmp(name, targets[i]) == 0) { 806 | return 1; 807 | } 808 | } 809 | return 0; 810 | } 811 | 812 | json_t *filter_teams(json_t *teams, const char *targets[], size_t target_count) { 813 | if (!json_is_array(teams)) { 814 | fprintf(stderr, "Error: Input JSON is not an array.\n"); 815 | return NULL; 816 | } 817 | 818 | json_t *filtered_array = json_array(); 819 | 820 | size_t index; 821 | json_t *team; 822 | json_array_foreach(teams, index, team) { 823 | json_t *name_json = json_object_get(team, "name"); 824 | if (!json_is_string(name_json)) continue; 825 | 826 | const char *name = json_string_value(name_json); 827 | if (is_target_team(name, targets, target_count)) { 828 | json_array_append(filtered_array, team); 829 | } 830 | } 831 | 832 | return filtered_array; 833 | } 834 | 835 | json_t *octopass_teams(struct config *con) 836 | { 837 | size_t url_size = snprintf(NULL, 0, OCTOPASS_TEAMS_URL, con->endpoint, con->organization) + 1; 838 | char *url = malloc(url_size); 839 | if (!url) { 840 | fprintf(stderr, "Memory allocation failed for teams URL\n"); 841 | return NULL; 842 | } 843 | snprintf(url, url_size, OCTOPASS_TEAMS_URL, con->endpoint, con->organization); 844 | 845 | struct response res; 846 | octopass_github_request(con, url, &res); 847 | 848 | if (!res.data) { 849 | fprintf(stderr, "Request failure\n"); 850 | if (con->syslog) { 851 | closelog(); 852 | } 853 | return NULL; 854 | } 855 | 856 | json_t *root; 857 | json_error_t error; 858 | root = json_loads(res.data, 0, &error); 859 | if (!root) { 860 | fprintf(stderr, "Error parsing JSON: %s\n", error.text); 861 | return NULL; 862 | } 863 | 864 | const char *target_teams[] = {con->team}; 865 | size_t target_count = sizeof(target_teams) / sizeof(target_teams[0]); 866 | 867 | json_t *filtered_json = filter_teams(root, target_teams, target_count); 868 | if (!filtered_json) { 869 | fprintf(stderr, "Error: Filtering JSON failed.\n"); 870 | json_decref(root); 871 | return NULL; 872 | } 873 | 874 | json_decref(root); 875 | 876 | return filtered_json; 877 | } 878 | 879 | int octopass_members(struct config *con, struct response *res) 880 | { 881 | if (strlen(con->repository) != 0) { 882 | return octopass_repository_collaborators(con, res); 883 | } else { 884 | return octopass_team_members(con, res); 885 | } 886 | } 887 | 888 | // OK: 0 889 | // NG: 1 890 | int octopass_autentication_with_token(struct config *con, char *user, char *token) 891 | { 892 | size_t url_size = snprintf(NULL, 0, OCTOPASS_USER_URL, con->endpoint) + 1; 893 | char *url = malloc(url_size); 894 | if (!url) { 895 | fprintf(stderr, "Memory allocation failed for user URL\n"); 896 | return 1; 897 | } 898 | 899 | snprintf(url, url_size, OCTOPASS_USER_URL, con->endpoint); 900 | struct response res; 901 | octopass_github_request_without_cache(con, url, &res, token); 902 | 903 | if (res.httpstatus == 200) { 904 | json_error_t error; 905 | json_t *root = json_loads(res.data, 0, &error); 906 | if (root) { 907 | const char *login = json_string_value(json_object_get(root, "login")); 908 | if (login && strcmp(login, user) == 0) { 909 | json_decref(root); 910 | free(url); 911 | return 0; 912 | } 913 | json_decref(root); 914 | } 915 | } 916 | 917 | if (con->syslog) { 918 | closelog(); 919 | } 920 | free(url); 921 | return 1; 922 | } 923 | 924 | const char *octopass_only_keys(char *data) 925 | { 926 | json_error_t error; 927 | json_t *root = json_loads(data, 0, &error); 928 | if (!root || !json_is_array(root)) { 929 | return NULL; 930 | } 931 | 932 | char *keys = calloc(OCTOPASS_MAX_BUFFER_SIZE, sizeof(char)); 933 | if (!keys) { 934 | json_decref(root); 935 | return NULL; 936 | } 937 | 938 | size_t i; 939 | for (i = 0; i < json_array_size(root); i++) { 940 | json_t *obj = json_array_get(root, i); 941 | const char *key = json_string_value(json_object_get(obj, "key")); 942 | if (key) { 943 | strncat(keys, key, OCTOPASS_MAX_BUFFER_SIZE - strlen(keys) - 1); 944 | strncat(keys, "\n", OCTOPASS_MAX_BUFFER_SIZE - strlen(keys) - 1); 945 | } 946 | } 947 | 948 | char *res = strdup(keys); 949 | free(keys); 950 | json_decref(root); 951 | 952 | // needs free 953 | return res; 954 | } 955 | 956 | const char *octopass_github_user_keys(struct config *con, char *user) 957 | { 958 | size_t url_size = snprintf(NULL, 0, OCTOPASS_USERS_KEYS_URL, con->endpoint, user) + 1; 959 | char *url = malloc(url_size); 960 | if (!url) { 961 | fprintf(stderr, "Memory allocation failed for user keys URL\n"); 962 | return NULL; 963 | } 964 | snprintf(url, url_size, OCTOPASS_USERS_KEYS_URL, con->endpoint, user); 965 | 966 | struct response res; 967 | octopass_github_request(con, url, &res); 968 | 969 | if (!res.data) { 970 | fprintf(stderr, "Request failure\n"); 971 | if (con->syslog) { 972 | closelog(); 973 | } 974 | return NULL; 975 | } 976 | 977 | return octopass_only_keys(res.data); 978 | } 979 | 980 | const char *octopass_github_team_members_keys(struct config *con) 981 | { 982 | json_t *root; 983 | json_error_t error; 984 | 985 | struct response res; 986 | int status = octopass_team_members(con, &res); 987 | 988 | if (status != 0) { 989 | free(res.data); 990 | return NULL; 991 | } 992 | 993 | root = json_loads(res.data, 0, &error); 994 | free(res.data); 995 | 996 | if (!root || !json_is_array(root)) { 997 | json_decref(root); 998 | return NULL; 999 | } 1000 | 1001 | char *members_keys = calloc(OCTOPASS_MAX_BUFFER_SIZE, sizeof(char)); 1002 | if (!members_keys) { 1003 | json_decref(root); 1004 | return NULL; 1005 | } 1006 | 1007 | size_t cnt = json_array_size(root); 1008 | int i; 1009 | 1010 | for (i = 0; i < cnt; i++) { 1011 | json_t *j_obj = json_array_get(root, i); 1012 | if (!json_is_object(j_obj)) { 1013 | continue; 1014 | } 1015 | json_t *j_name = json_object_get(j_obj, "login"); 1016 | if (!json_is_string(j_name)) { 1017 | continue; 1018 | } 1019 | 1020 | const char *login = json_string_value(j_name); 1021 | const char *keys = octopass_github_user_keys(con, (char *)login); 1022 | if (keys) { 1023 | strncat(members_keys, keys, OCTOPASS_MAX_BUFFER_SIZE - strlen(members_keys) - 1); 1024 | } 1025 | } 1026 | 1027 | json_decref(root); 1028 | 1029 | if (strlen(members_keys) == 0) { 1030 | free(members_keys); 1031 | return NULL; 1032 | } 1033 | 1034 | char *result = strdup(members_keys); 1035 | free(members_keys); 1036 | 1037 | // needs free 1038 | return result; 1039 | } 1040 | -------------------------------------------------------------------------------- /octopass.conf.example: -------------------------------------------------------------------------------- 1 | # O C T O P A S S 2 | 3 | # Required 4 | Token = "iad87dih122ce66a1e20a751664c8a9dkoak87g7" 5 | 6 | ## Use team 7 | #Organization = "yourorganization" 8 | #Team = "yourteam" 9 | 10 | ## Use collaborators 11 | #Owner = "yourname" 12 | #Repository = "yourrepository" 13 | 14 | # Default 15 | #Endpoint = "https://api.github.com/" 16 | #Group = "yourgroup" 17 | #Home = "/home/foo/%s" 18 | #Shell = "/bin/zsh" 19 | #UidStarts = 2000 20 | #Gid = 2000 21 | #Cache = 300 22 | #Syslog = false 23 | 24 | # Advanced 25 | #SharedUsers = [ "admin", "deploy" ] 26 | -------------------------------------------------------------------------------- /octopass.h: -------------------------------------------------------------------------------- 1 | /* Management linux user and authentication with the organization/team on Github. 2 | Copyright (C) 2017 Tomohisa Oda 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . */ 16 | 17 | #ifndef OCTOPASS_H 18 | #define OCTOPASS_H 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #define OCTOPASS_VERSION "0.9.0" 39 | #define OCTOPASS_VERSION_WITH_NAME "octopass/" OCTOPASS_VERSION 40 | #ifndef OCTOPASS_CONFIG_FILE 41 | #define OCTOPASS_CONFIG_FILE "/etc/octopass.conf" 42 | #endif 43 | #define OCTOPASS_CACHE_DIR "/var/cache/octopass" 44 | #define OCTOPASS_LOCK() \ 45 | do { \ 46 | pthread_mutex_lock(&OCTOPASS_MUTEX); \ 47 | } while (0); 48 | #define OCTOPASS_UNLOCK() \ 49 | do { \ 50 | pthread_mutex_unlock(&OCTOPASS_MUTEX); \ 51 | } while (0); 52 | 53 | // 10MB 54 | #define OCTOPASS_MAX_BUFFER_SIZE (10 * 1024 * 1024) 55 | 56 | #define OCTOPASS_API_ENDPOINT "https://api.github.com/" 57 | #define OCTOPASS_USER_URL "%suser" 58 | #define OCTOPASS_TEAMS_URL "%sorgs/%s/teams?per_page=100" 59 | #define OCTOPASS_TEAMS_MEMBERS_URL "%steams/%d/members?per_page=100" 60 | #define OCTOPASS_COLLABORATORS_URL "%srepos/%s/%s/collaborators?per_page=100" 61 | #define OCTOPASS_USERS_KEYS_URL "%susers/%s/keys?per_page=100" 62 | 63 | #define MAXBUF 2048 64 | #define DELIM "= " 65 | 66 | // This macro is available with more than 2.5 67 | #ifndef json_array_foreach 68 | #define json_array_foreach(array, index, value) \ 69 | for (index = 0; index < json_array_size(array) && (value = json_array_get(array, index)); index++) 70 | #endif 71 | 72 | struct response { 73 | char *data; 74 | size_t size; 75 | long httpstatus; 76 | }; 77 | 78 | struct config { 79 | char endpoint[MAXBUF]; 80 | char token[MAXBUF]; 81 | char organization[MAXBUF]; 82 | char team[MAXBUF]; 83 | char owner[MAXBUF]; 84 | char repository[MAXBUF]; 85 | char permission[MAXBUF]; 86 | char group_name[MAXBUF]; 87 | char home[MAXBUF]; 88 | char shell[MAXBUF]; 89 | long uid_starts; 90 | long gid; 91 | long cache; 92 | bool syslog; 93 | char **shared_users; 94 | int shared_users_count; 95 | }; 96 | 97 | extern int octopass_members(struct config *con, struct response *res); 98 | extern int octopass_config_loading(struct config *con, char *filename); 99 | extern json_t *octopass_github_team_member_by_name(char *name, json_t *root); 100 | extern json_t *octopass_github_team_member_by_id(int gh_id, json_t *root); 101 | int octopass_autentication_with_token(struct config *con, char *user, char *token); 102 | extern char *express_github_user_keys(struct config *con, char *user); 103 | extern const char *octopass_github_team_members_keys(struct config *con); 104 | extern const char *octopass_github_user_keys(struct config *con, char *user); 105 | extern json_t *octopass_teams(struct config *con); 106 | extern int octopass_team_members_by_team_id(struct config *con, int team_id, struct response *res); 107 | 108 | #endif /* OCTOPASS_H */ 109 | -------------------------------------------------------------------------------- /octopass_cli.c: -------------------------------------------------------------------------------- 1 | /* Management linux user and authentication with the organization/team on Github. 2 | Copyright (C) 2017 Tomohisa Oda 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . */ 16 | 17 | #include "octopass.h" 18 | static pthread_mutex_t OCTOPASS_MUTEX = PTHREAD_MUTEX_INITIALIZER; 19 | 20 | #define no_argument 0 21 | #define required_argument 1 22 | #define optional_argument 2 23 | #define ANSI_COLOR_RED "\x1b[31m" 24 | #define ANSI_COLOR_GREEN "\x1b[32m" 25 | #define ANSI_COLOR_RESET "\x1b[0m" 26 | 27 | extern void call_getpwnam_r(const char *name); 28 | extern void call_getpwuid_r(uid_t uid); 29 | extern void call_pwlist(void); 30 | extern void call_getgrnam_r(const char *name); 31 | extern void call_getgrgid_r(gid_t gid); 32 | extern void call_grlist(void); 33 | extern void call_getspnam_r(const char *name); 34 | extern void call_splist(void); 35 | 36 | void help(void) 37 | { 38 | printf(ANSI_COLOR_GREEN " _ ____ _ __ _____" ANSI_COLOR_RESET "\n"); 39 | printf(ANSI_COLOR_GREEN "/ \\/ | / \\|_)/\\(__(__" ANSI_COLOR_RESET "\n"); 40 | printf(ANSI_COLOR_GREEN "\\_/\\_ | \\_/| /--\\__)__)" ANSI_COLOR_RESET "\n"); 41 | printf("\n"); 42 | printf("Usage: octopass [options] [CMD]\n"); 43 | printf("\n"); 44 | printf("Commands:\n"); 45 | printf(" [USER] get public keys from github\n"); 46 | printf(" pam [user] receive the password from stdin and return the auth result with the exit status\n"); 47 | printf(" passwd [key] displays passwd entries as octopass nss module\n"); 48 | printf(" shadow [key] displays shadow passwd entries as octopass nss module\n"); 49 | printf(" group [key] displays group entries as octopass nss module\n"); 50 | printf("\n"); 51 | printf("Options:\n"); 52 | printf(" -h, --help show this help message and exit\n"); 53 | printf(" -v, --version print the version and exit\n"); 54 | printf("\n"); 55 | } 56 | 57 | int octopass_public_keys_unlocked(char *name) 58 | { 59 | struct config con; 60 | if (octopass_config_loading(&con, OCTOPASS_CONFIG_FILE) != 0) { 61 | fprintf(stderr, ANSI_COLOR_RED "Error: " ANSI_COLOR_RESET "Failed to load config\n"); 62 | return 1; 63 | } 64 | 65 | if (con.shared_users_count > 0) { 66 | for (int idx = 0; idx < con.shared_users_count; idx++) { 67 | if (strcmp(name, con.shared_users[idx]) == 0) { 68 | const char *members_keys = octopass_github_team_members_keys(&con); 69 | if (members_keys) { 70 | printf("%s", members_keys); 71 | } 72 | return 0; 73 | } 74 | } 75 | } 76 | 77 | const char *keys = octopass_github_user_keys(&con, name); 78 | if (keys) { 79 | printf("%s", keys); 80 | } 81 | 82 | return 0; 83 | } 84 | 85 | int octopass_public_keys(char *name) 86 | { 87 | OCTOPASS_LOCK(); 88 | int res = octopass_public_keys_unlocked(name); 89 | OCTOPASS_UNLOCK(); 90 | return res; 91 | } 92 | 93 | int octopass_authentication_unlocked(int argc, char **argv) 94 | { 95 | char *token = NULL; 96 | size_t token_size = 0; 97 | 98 | if (getline(&token, &token_size, stdin) == -1) { 99 | fprintf(stderr, ANSI_COLOR_RED "Error: " ANSI_COLOR_RESET "Failed to read token from stdin\n"); 100 | free(token); 101 | return 2; 102 | } 103 | 104 | token[strcspn(token, "\n")] = '\0'; 105 | 106 | char *user = NULL; 107 | if (argc < 3) { 108 | user = getenv("PAM_USER"); 109 | if (!user) { 110 | fprintf(stderr, ANSI_COLOR_RED "Error: " ANSI_COLOR_RESET "User is required\n"); 111 | free(token); 112 | return 2; 113 | } 114 | } else { 115 | user = argv[2]; 116 | } 117 | 118 | struct config con; 119 | if (octopass_config_loading(&con, OCTOPASS_CONFIG_FILE) != 0) { 120 | fprintf(stderr, ANSI_COLOR_RED "Error: " ANSI_COLOR_RESET "Failed to load config\n"); 121 | free(token); 122 | return 2; 123 | } 124 | 125 | int result = octopass_autentication_with_token(&con, user, token); 126 | free(token); 127 | return result; 128 | } 129 | 130 | int octopass_authentication(int argc, char **argv) 131 | { 132 | OCTOPASS_LOCK(); 133 | int res = octopass_authentication_unlocked(argc, argv); 134 | OCTOPASS_UNLOCK(); 135 | return res; 136 | } 137 | 138 | int main(int argc, char **argv) 139 | { 140 | if (argc < 2 || !argv[1] || !strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")) { 141 | help(); 142 | return 2; 143 | } 144 | 145 | if (!strcmp(argv[1], "--version") || !strcmp(argv[1], "-v")) { 146 | printf("%s\n", OCTOPASS_VERSION_WITH_NAME); 147 | return 0; 148 | } 149 | 150 | // PASSWD 151 | if (!strcmp(argv[1], "passwd")) { 152 | if (argc < 3) { 153 | call_pwlist(); 154 | } else { 155 | char *endptr; 156 | long id = strtol(argv[2], &endptr, 10); 157 | 158 | if (*endptr == '\0' && id > 0) { 159 | call_getpwuid_r((uid_t)id); 160 | } else { 161 | call_getpwnam_r(argv[2]); 162 | } 163 | } 164 | return 0; 165 | } 166 | 167 | // GROUP 168 | if (!strcmp(argv[1], "group")) { 169 | if (argc < 3) { 170 | call_grlist(); 171 | } else { 172 | char *endptr; 173 | long id = strtol(argv[2], &endptr, 10); 174 | 175 | if (*endptr == '\0' && id > 0) { 176 | call_getgrgid_r((gid_t)id); 177 | } else { 178 | call_getgrnam_r(argv[2]); 179 | } 180 | } 181 | return 0; 182 | } 183 | 184 | // SHADOW 185 | if (!strcmp(argv[1], "shadow")) { 186 | if (argc < 3) { 187 | call_splist(); 188 | } else { 189 | char *endptr; 190 | long id = strtol(argv[2], &endptr, 10); 191 | 192 | if (*endptr == '\0' && id > 0) { 193 | fprintf(stderr, ANSI_COLOR_RED "Error: " ANSI_COLOR_RESET "Invalid arguments: %s\n", argv[2]); 194 | } else { 195 | call_getspnam_r(argv[2]); 196 | } 197 | } 198 | return 0; 199 | } 200 | 201 | // PAM 202 | if (!strcmp(argv[1], "pam")) { 203 | return octopass_authentication(argc, argv); 204 | } 205 | 206 | // PUBLIC KEYS 207 | //return octopass_public_keys((char *)argv[1]); 208 | return octopass_public_keys(argv[1]); 209 | } 210 | -------------------------------------------------------------------------------- /octopass_test.c: -------------------------------------------------------------------------------- 1 | /* Management linux user and authentication with the organization/team on Github. 2 | Copyright (C) 2017 Tomohisa Oda 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . */ 16 | 17 | #define OCTOPASS_CONFIG_FILE "test/octopass.conf" 18 | #include 19 | #include "octopass.c" 20 | 21 | void setup(void) 22 | { 23 | char *token = getenv("OCTOPASS_TOKEN"); 24 | char *endpoint = getenv("OCTOPASS_ENDPOINT"); 25 | if (!token || !endpoint) { 26 | cr_skip_test("Missing environment variables, token or endpoint"); 27 | } 28 | } 29 | 30 | Test(octopass, remove_quotes) 31 | { 32 | char s[] = "\"foo\""; 33 | octopass_remove_quotes(&s[0]); 34 | 35 | cr_assert_str_eq(s, "foo"); 36 | 37 | char s_contains[] = "\"I'm a \"foo\"\""; 38 | octopass_remove_quotes(&s_contains[0]); 39 | 40 | cr_assert_str_eq(s_contains, "I'm a \"foo\""); 41 | } 42 | 43 | Test(octopass, truncate) 44 | { 45 | char *s = "abcdefghijklmnopqrstuvwxyz0123456789!@#$"; 46 | const char *res1 = octopass_truncate(s, 6); 47 | cr_assert_str_eq(res1, "abcdef"); 48 | 49 | const char *res2 = octopass_truncate(s, 2); 50 | cr_assert_str_eq(res2, "ab"); 51 | 52 | const char *res3 = octopass_truncate(s, 300); 53 | cr_assert_str_eq(res3, s); 54 | } 55 | 56 | Test(octopass, masking) 57 | { 58 | char *s1 = "abcdefghijklmnopqrstuvwxyz0123456789!@#$"; 59 | const char *mask1 = octopass_masking(s1); 60 | cr_assert_str_eq(mask1, "abcde ************ REDACTED ************"); 61 | 62 | char *s2 = "----------klmnopqrstuvwxyz0123456789!@#$"; 63 | const char *mask2 = octopass_masking(s2); 64 | cr_assert_str_eq(mask2, "----- ************ REDACTED ************"); 65 | } 66 | 67 | Test(octopass, url_normalization) 68 | { 69 | char *base_url1 = "https://foo.com/api/v3"; 70 | const char *url1 = octopass_url_normalization(base_url1); 71 | cr_assert_str_eq(url1, "https://foo.com/api/v3/"); 72 | 73 | char *base_url2 = "https://foo.com/api/v3/"; 74 | const char *url2 = octopass_url_normalization(base_url2); 75 | cr_assert_str_eq(url2, "https://foo.com/api/v3/"); 76 | 77 | char *base_url3 = "https://api.github.com"; 78 | const char *url3 = octopass_url_normalization(base_url3); 79 | cr_assert_str_eq(url3, "https://api.github.com/"); 80 | 81 | char *base_url4 = "https://api.github.com/"; 82 | const char *url4 = octopass_url_normalization(base_url4); 83 | cr_assert_str_eq(url4, "https://api.github.com/"); 84 | } 85 | 86 | Test(octopass, match) 87 | { 88 | char *str = "[ \"abc\", \"de\", \"F012\" ]"; 89 | char *pattern = "\"([A-z0-9_-]+)\""; 90 | char **matched; 91 | matched = malloc(MAXBUF); 92 | int cnt = octopass_match(str, pattern, matched); 93 | 94 | cr_assert_eq(cnt, 3); 95 | cr_assert_str_eq(matched[0], (char *)"abc"); 96 | cr_assert_str_eq(matched[1], (char *)"de"); 97 | cr_assert_str_eq(matched[2], (char *)"F012"); 98 | free(matched); 99 | } 100 | 101 | Test(octopass, override_config_by_env) 102 | { 103 | clearenv(); 104 | 105 | struct config con = { 0 }; 106 | octopass_override_config_by_env(&con); 107 | cr_assert_str_empty(con.token); 108 | cr_assert_str_empty(con.endpoint); 109 | cr_assert_str_empty(con.organization); 110 | cr_assert_str_empty(con.team); 111 | cr_assert_str_empty(con.repository); 112 | 113 | putenv("OCTOPASS_TOKEN=secret-token"); 114 | putenv("OCTOPASS_ENDPOINT=https://api.github.com/"); 115 | putenv("OCTOPASS_ORGANIZATION=octopass"); 116 | putenv("OCTOPASS_TEAM=operation"); 117 | putenv("OCTOPASS_OWNER=linyows"); 118 | putenv("OCTOPASS_REPOSITORY=foo"); 119 | putenv("OCTOPASS_PERMISSION=admin"); 120 | octopass_override_config_by_env(&con); 121 | cr_assert_str_eq(con.token, "secret-token"); 122 | cr_assert_str_eq(con.endpoint, "https://api.github.com/"); 123 | cr_assert_str_eq(con.organization, "octopass"); 124 | cr_assert_str_eq(con.team, "operation"); 125 | cr_assert_str_eq(con.owner, "linyows"); 126 | cr_assert_str_eq(con.repository, "foo"); 127 | cr_assert_str_eq(con.permission, "admin"); 128 | 129 | clearenv(); 130 | } 131 | 132 | Test(octopass, config_loading) 133 | { 134 | clearenv(); 135 | 136 | struct config con; 137 | char *f = "test/octopass.conf"; 138 | octopass_config_loading(&con, f); 139 | 140 | cr_assert_str_eq(con.endpoint, "https://your.github.com/api/v3/"); 141 | cr_assert_str_eq(con.token, "iad87dih122ce66a1e20a751664c8a9dkoak87g7"); 142 | cr_assert_str_eq(con.organization, "yourorganization"); 143 | cr_assert_str_eq(con.team, "yourteam"); 144 | cr_assert_str_eq(con.group_name, "yourteam"); 145 | cr_assert_str_eq(con.home, "/home/%s"); 146 | cr_assert_str_eq(con.shell, "/bin/bash"); 147 | cr_assert_eq(con.uid_starts, 2000); 148 | cr_assert_eq(con.gid, 2000); 149 | cr_assert_eq(con.cache, 300); 150 | cr_assert(con.syslog == false); 151 | cr_assert_eq(con.shared_users_count, 2); 152 | cr_assert_str_eq(con.shared_users[0], (char *)"admin"); 153 | cr_assert_str_eq(con.shared_users[1], (char *)"deploy"); 154 | } 155 | 156 | Test(octopass, config_loading__when_use_repository) 157 | { 158 | clearenv(); 159 | 160 | struct config con; 161 | char *f = "test/octopass_repo.conf"; 162 | octopass_config_loading(&con, f); 163 | 164 | cr_assert_str_eq(con.endpoint, "https://your.github.com/api/v3/"); 165 | cr_assert_str_eq(con.token, "iad87dih122ce66a1e20a751664c8a9dkoak87g7"); 166 | cr_assert_str_eq(con.owner, "yourorganization"); 167 | cr_assert_str_eq(con.repository, "yourrepo"); 168 | cr_assert_str_eq(con.permission, "write"); 169 | cr_assert_str_eq(con.group_name, "yourrepo"); 170 | cr_assert_str_eq(con.home, "/home/%s"); 171 | cr_assert_str_eq(con.shell, "/bin/bash"); 172 | cr_assert_eq(con.uid_starts, 2000); 173 | cr_assert_eq(con.gid, 2000); 174 | cr_assert_eq(con.cache, 300); 175 | cr_assert(con.syslog == false); 176 | cr_assert_eq(con.shared_users_count, 0); 177 | } 178 | 179 | Test(octopass, export_file) 180 | { 181 | struct config con; 182 | char *cf = "test/octopass.conf"; 183 | octopass_config_loading(&con, cf); 184 | 185 | char *dir = "/tmp"; 186 | char *f = "/tmp/octopass-export_file_test_1.txt"; 187 | char *d = "LINE1\nLINE2\nLINE3\n"; 188 | octopass_export_file(&con, dir, f, d); 189 | 190 | FILE *fp = fopen(f, "r"); 191 | 192 | if (fp == NULL) { 193 | cr_assert_fail("File open failure"); 194 | } 195 | 196 | char line[10]; 197 | int i = 0; 198 | 199 | while (fgets(line, sizeof(line), fp) != NULL) { 200 | switch (i) { 201 | case 0: 202 | cr_assert_str_eq(line, "LINE1\n"); 203 | break; 204 | case 1: 205 | cr_assert_str_eq(line, "LINE2\n"); 206 | break; 207 | case 2: 208 | cr_assert_str_eq(line, "LINE3\n"); 209 | break; 210 | } 211 | i += 1; 212 | } 213 | 214 | fclose(fp); 215 | } 216 | 217 | Test(octopass, import_file) 218 | { 219 | struct config con; 220 | char *cf = "test/octopass.conf"; 221 | octopass_config_loading(&con, cf); 222 | 223 | char *dir = "/tmp"; 224 | char *f1 = "/tmp/octopass-import_file_test_1.txt"; 225 | char *d1 = "LINE1\nLINE2\nLINE3\n"; 226 | octopass_export_file(&con, dir, f1, d1); 227 | const char *data1 = octopass_import_file(&con, f1); 228 | cr_assert_str_eq(data1, d1); 229 | 230 | char *f2 = "/tmp/octopass-import_file_test_2.txt"; 231 | char *d2 = "LINEa\nLINEb\nLINEc\n"; 232 | octopass_export_file(&con, dir, f2, d2); 233 | const char *data2 = octopass_import_file(&con, f2); 234 | cr_assert_str_eq(data2, d2); 235 | } 236 | 237 | Test(octopass, github_request_without_cache, .init = setup) 238 | { 239 | struct config con; 240 | struct response res; 241 | char *f = "test/octopass.conf"; 242 | octopass_config_loading(&con, f); 243 | char *url = "https://api.github.com/"; 244 | char *token; 245 | octopass_github_request_without_cache(&con, url, &res, token); 246 | 247 | cr_assert_eq(200, res.httpstatus); 248 | 249 | //cr_assert_str_eq( 250 | // "{\"current_user_url\":\"https://api.github.com/user\",\"current_user_authorizations_html_url\":\"https://" 251 | // "github.com/settings/connections/applications{/client_id}\",\"authorizations_url\":\"https://api.github.com/" 252 | // "authorizations\",\"code_search_url\":\"https://api.github.com/search/" 253 | // "code?q={query}{&page,per_page,sort,order}\",\"commit_search_url\":\"https://api.github.com/search/" 254 | // "commits?q={query}{&page,per_page,sort,order}\",\"emails_url\":\"https://api.github.com/user/" 255 | // "emails\",\"emojis_url\":\"https://api.github.com/emojis\",\"events_url\":\"https://api.github.com/" 256 | // "events\",\"feeds_url\":\"https://api.github.com/feeds\",\"followers_url\":\"https://api.github.com/user/" 257 | // "followers\",\"following_url\":\"https://api.github.com/user/following{/target}\",\"gists_url\":\"https://" 258 | // "api.github.com/gists{/gist_id}\",\"hub_url\":\"https://api.github.com/hub\",\"issue_search_url\":\"https://" 259 | // "api.github.com/search/issues?q={query}{&page,per_page,sort,order}\",\"issues_url\":\"https://api.github.com/" 260 | // "issues\",\"keys_url\":\"https://api.github.com/user/keys\",\"notifications_url\":\"https://api.github.com/" 261 | // "notifications\",\"organization_repositories_url\":\"https://api.github.com/orgs/{org}/" 262 | // "repos{?type,page,per_page,sort}\",\"organization_url\":\"https://api.github.com/orgs/" 263 | // "{org}\",\"public_gists_url\":\"https://api.github.com/gists/public\",\"rate_limit_url\":\"https://" 264 | // "api.github.com/rate_limit\",\"repository_url\":\"https://api.github.com/repos/{owner}/" 265 | // "{repo}\",\"repository_search_url\":\"https://api.github.com/search/" 266 | // "repositories?q={query}{&page,per_page,sort,order}\",\"current_user_repositories_url\":\"https://api.github.com/" 267 | // "user/repos{?type,page,per_page,sort}\",\"starred_url\":\"https://api.github.com/user/starred{/owner}{/" 268 | // "repo}\",\"starred_gists_url\":\"https://api.github.com/gists/starred\",\"team_url\":\"https://api.github.com/" 269 | // "teams\",\"user_url\":\"https://api.github.com/users/{user}\",\"user_organizations_url\":\"https://" 270 | // "api.github.com/user/orgs\",\"user_repositories_url\":\"https://api.github.com/users/{user}/" 271 | // "repos{?type,page,per_page,sort}\",\"user_search_url\":\"https://api.github.com/search/" 272 | // "users?q={query}{&page,per_page,sort,order}\"}", 273 | // res.data); 274 | } 275 | 276 | Test(octopass, github_request_without_cache__when_use_dummy_token, .init = setup) 277 | { 278 | struct config con; 279 | struct response res; 280 | char *f = "test/octopass.conf"; 281 | octopass_config_loading(&con, f); 282 | char *url = "https://api.github.com/"; 283 | char *token = "dummydummydummydummydummydummydummydummy"; 284 | octopass_github_request_without_cache(&con, url, &res, token); 285 | 286 | cr_assert_eq(401, res.httpstatus); 287 | cr_assert_str_eq(res.data, "{\"message\":\"Bad credentials\",\"documentation_url\":\"https://docs.github.com/rest\",\"status\":\"401\"}"); 288 | } 289 | 290 | Test(octopass, team_id, .init = setup) 291 | { 292 | struct config con; 293 | char *f = "test/octopass.conf"; 294 | octopass_config_loading(&con, f); 295 | int id = octopass_team_id(&con); 296 | 297 | cr_assert_eq(id, 2244789); 298 | } 299 | 300 | Test(octopass, permission_level, .init = setup) 301 | { 302 | char *adm = "admin"; 303 | char *admin = octopass_permission_level(adm); 304 | cr_assert_str_eq(admin, "admin"); 305 | 306 | char *write = "write"; 307 | char *push = octopass_permission_level(write); 308 | cr_assert_str_eq(push, "push"); 309 | 310 | char *read = "read"; 311 | char *pull = octopass_permission_level(read); 312 | cr_assert_str_eq(pull, "pull"); 313 | } 314 | 315 | Test(octopass, is_authorized_collaborator, .init = setup) 316 | { 317 | putenv("OCTOPASS_OWNER=linyows"); 318 | putenv("OCTOPASS_REPOSITORY=octopass"); 319 | 320 | struct config con; 321 | struct response res; 322 | char *f = "test/octopass_repo.conf"; 323 | octopass_config_loading(&con, f); 324 | int status1 = octopass_repository_collaborators(&con, &res); 325 | cr_assert_eq(status1, 0); 326 | 327 | size_t i; 328 | json_error_t error; 329 | json_t *collaborators; 330 | json_t *collaborator; 331 | collaborators = json_loads(res.data, 0, &error); 332 | cr_assert_eq(json_array_size(collaborators), 1); 333 | 334 | json_array_foreach(collaborators, i, collaborator) { 335 | int status2 = octopass_is_authorized_collaborator(&con, collaborator); 336 | cr_assert_eq(status2, 1); 337 | } 338 | 339 | json_decref(collaborators); 340 | clearenv(); 341 | } 342 | 343 | Test(octopass, rebuild_data_with_authorized, .init = setup) 344 | { 345 | putenv("OCTOPASS_OWNER=linyows"); 346 | putenv("OCTOPASS_REPOSITORY=octopass"); 347 | 348 | struct config con; 349 | struct response res; 350 | char *f = "test/octopass_repo.conf"; 351 | octopass_config_loading(&con, f); 352 | 353 | char *stub = "test/collaborators.json"; 354 | res.httpstatus = 200; 355 | res.data = (char *)octopass_import_file(&con, stub); 356 | res.size = strlen(res.data); 357 | octopass_rebuild_data_with_authorized(&con, &res); 358 | 359 | size_t i; 360 | json_error_t error; 361 | json_t *collaborators; 362 | json_t *collaborator; 363 | collaborators = json_loads(res.data, 0, &error); 364 | 365 | cr_assert_eq(json_array_size(collaborators), 1); 366 | 367 | json_t *me = json_array_get(collaborators, 0); 368 | const char *login = json_string_value(json_object_get(me, "login")); 369 | cr_assert_str_eq(login, "linyows"); 370 | 371 | json_decref(collaborators); 372 | clearenv(); 373 | } 374 | 375 | Test(octopass, rebuild_data_with_authorized__when_permission_is_read, .init = setup) 376 | { 377 | putenv("OCTOPASS_OWNER=linyows"); 378 | putenv("OCTOPASS_REPOSITORY=octopass"); 379 | putenv("OCTOPASS_PERMISSION=read"); 380 | 381 | struct config con; 382 | struct response res; 383 | char *f = "test/octopass_repo.conf"; 384 | octopass_config_loading(&con, f); 385 | 386 | char *stub = "test/collaborators.json"; 387 | res.httpstatus = 200; 388 | res.data = (char *)octopass_import_file(&con, stub); 389 | res.size = strlen(res.data); 390 | octopass_rebuild_data_with_authorized(&con, &res); 391 | 392 | size_t i; 393 | json_error_t error; 394 | json_t *collaborators; 395 | json_t *collaborator; 396 | collaborators = json_loads(res.data, 0, &error); 397 | 398 | cr_assert_eq(json_array_size(collaborators), 2); 399 | 400 | json_t *me = json_array_get(collaborators, 0); 401 | const char *login1 = json_string_value(json_object_get(me, "login")); 402 | cr_assert_str_eq(login1, "linyows"); 403 | 404 | json_t *other = json_array_get(collaborators, 1); 405 | const char *login2 = json_string_value(json_object_get(other, "login")); 406 | cr_assert_str_eq(login2, "nolinyows"); 407 | 408 | json_decref(collaborators); 409 | clearenv(); 410 | } 411 | 412 | Test(octopass, repository_collaborators, .init = setup) 413 | { 414 | putenv("OCTOPASS_OWNER=linyows"); 415 | putenv("OCTOPASS_REPOSITORY=octopass"); 416 | 417 | struct config con; 418 | struct response res; 419 | char *f = "test/octopass_repo.conf"; 420 | octopass_config_loading(&con, f); 421 | int status = octopass_repository_collaborators(&con, &res); 422 | cr_assert_eq(status, 0); 423 | 424 | json_error_t error; 425 | json_t *collaborators = json_loads(res.data, 0, &error); 426 | cr_assert_eq(json_array_size(collaborators), 1); 427 | json_t *me = json_array_get(collaborators, 0); 428 | const char *login = json_string_value(json_object_get(me, "login")); 429 | cr_assert_str_eq(login, "linyows"); 430 | 431 | json_decref(collaborators); 432 | clearenv(); 433 | } 434 | 435 | Test(octopass, authentication_with_token, .init = setup) 436 | { 437 | struct config con; 438 | char *f = "test/octopass.conf"; 439 | octopass_config_loading(&con, f); 440 | char *user = "linyows"; 441 | char *token = getenv("OCTOPASS_TOKEN"); 442 | int status = octopass_autentication_with_token(&con, user, token); 443 | cr_assert_eq(status, 0); 444 | } 445 | 446 | Test(octopass, authentication_with_token__when_wrong_user, .init = setup) 447 | { 448 | struct config con; 449 | char *f = "test/octopass.conf"; 450 | octopass_config_loading(&con, f); 451 | char *user = "dummy"; 452 | char *token = getenv("OCTOPASS_TOKEN"); 453 | int status = octopass_autentication_with_token(&con, user, token); 454 | cr_assert_eq(status, 1); 455 | } 456 | 457 | Test(octopass, authentication_with_token__when_wrong_token, .init = setup) 458 | { 459 | struct config con; 460 | char *f = "test/octopass.conf"; 461 | octopass_config_loading(&con, f); 462 | char *user = "linyows"; 463 | char *token = "dummydummydummydummydummydummydummydummy"; 464 | int status = octopass_autentication_with_token(&con, user, token); 465 | cr_assert_eq(status, 1); 466 | } 467 | 468 | Test(octopass, github_user_keys, .init = setup) 469 | { 470 | struct config con; 471 | char *f = "test/octopass.conf"; 472 | octopass_config_loading(&con, f); 473 | char *user = "linyows"; 474 | const char *keys = octopass_github_user_keys(&con, user); 475 | cr_assert_str_eq(keys, 476 | "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCbBkU87QyUEmecsQjCcMTdS6iARCUXzMo2awb4c+irGPUvkXxQUljmLFRXCIw+cEKajiS7VY5NLCZ6WVCbd4yIK+3jdNzrf74isiG8GdU+m64gNGaXtKGFaQEXBp9uWqqZgSw+bVMX2ArOtoh3lP96WJQOoXsOuX0izNS5qf1Z9E01J6IpE3xfudpaL4/vY1RnljM+KUoiIPFqS1Q7kJ+8LpHvV1T9SRiocpLThXOzifzwwoo9I6emwHr+kGwODERYWYvkMEwFyOh8fKAcTdt8huUz8n6k59V9y5hZWDuxP/zhnArUMwWHiiS1C5im8baX8jxSW6RoHuetBxSUn5vR\n" 477 | "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCpBXvpUsPK8zREThB1L2pk6VHLMMlM7oKwBUIrvd6Pa/PjER8oP4nXhztOUiSIeRkgShSjMtAK/O5PGeesc9RLtCn53kj9a731bCc56jxYef39PMSrS8CkF26YX0tm6Jq1cmEAkNZo+L6FY1wdLskEM+6w84XWRAE9H+/eI4ca0wY3f9VRqYjNE/rx+tr4IhLxmSgoLBKnyoRalsKsYItS5hDUcI6l6wceOmrAYrCU+oSg79Ylxc05IMbTKjnWn2A5DMcPspAftu2HEoEUD/8H4akL1HSLpac+F2zcBitc9cCBtxVh5TL3p3hXpqMDc/OmZDTPqAXz2k5h2UrYKcgdVHDVxCQL+bb/m+t6IsPk1O/bJvDZjV0yxNOsRXGtWcL6I1xkPEaR/yJNnNwsZQ/ynnmleXXB4t6H/7f96+ob+sQbpPPoN1E2ZrCfCyjp2awfN/ERVR4njBmXcbZdurcWln6i3Tv7hrARweErewr3QrBONxDm8LUWup4dHKjXSms=\n" 478 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAPF3u5K+4mXQjWLYhLuTO7Eg1XUnqh0K5lryfEmhMER\n"); 479 | } 480 | 481 | Test(octopass, github_team_members_keys, .init = setup) 482 | { 483 | struct config con; 484 | char *f = "test/octopass.conf"; 485 | octopass_config_loading(&con, f); 486 | const char *keys = octopass_github_team_members_keys(&con); 487 | cr_assert_str_eq(keys, 488 | "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCbBkU87QyUEmecsQjCcMTdS6iARCUXzMo2awb4c+irGPUvkXxQUljmLFRXCIw+cEKajiS7VY5NLCZ6WVCbd4yIK+3jdNzrf74isiG8GdU+m64gNGaXtKGFaQEXBp9uWqqZgSw+bVMX2ArOtoh3lP96WJQOoXsOuX0izNS5qf1Z9E01J6IpE3xfudpaL4/vY1RnljM+KUoiIPFqS1Q7kJ+8LpHvV1T9SRiocpLThXOzifzwwoo9I6emwHr+kGwODERYWYvkMEwFyOh8fKAcTdt8huUz8n6k59V9y5hZWDuxP/zhnArUMwWHiiS1C5im8baX8jxSW6RoHuetBxSUn5vR\n" 489 | "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCpBXvpUsPK8zREThB1L2pk6VHLMMlM7oKwBUIrvd6Pa/PjER8oP4nXhztOUiSIeRkgShSjMtAK/O5PGeesc9RLtCn53kj9a731bCc56jxYef39PMSrS8CkF26YX0tm6Jq1cmEAkNZo+L6FY1wdLskEM+6w84XWRAE9H+/eI4ca0wY3f9VRqYjNE/rx+tr4IhLxmSgoLBKnyoRalsKsYItS5hDUcI6l6wceOmrAYrCU+oSg79Ylxc05IMbTKjnWn2A5DMcPspAftu2HEoEUD/8H4akL1HSLpac+F2zcBitc9cCBtxVh5TL3p3hXpqMDc/OmZDTPqAXz2k5h2UrYKcgdVHDVxCQL+bb/m+t6IsPk1O/bJvDZjV0yxNOsRXGtWcL6I1xkPEaR/yJNnNwsZQ/ynnmleXXB4t6H/7f96+ob+sQbpPPoN1E2ZrCfCyjp2awfN/ERVR4njBmXcbZdurcWln6i3Tv7hrARweErewr3QrBONxDm8LUWup4dHKjXSms=\n" 490 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAPF3u5K+4mXQjWLYhLuTO7Eg1XUnqh0K5lryfEmhMER\n" 491 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOm6M4O8B9dS4fwYMny+mfid2H3t/6l1BKBewrvRGJ+Z\n"); 492 | } 493 | -------------------------------------------------------------------------------- /rpm/octopass.spec: -------------------------------------------------------------------------------- 1 | Summary: Management linux user and authentication with team or collaborator on Github. 2 | Name: octopass 3 | Version: 0.7.1 4 | Release: 1 5 | License: GPLv3 6 | URL: https://github.com/linyows/octopass 7 | Source: %{name}-%{version}.tar.gz 8 | Group: System Environment/Base 9 | Packager: linyows 10 | %if 0%{?rhel} < 6 11 | Requires: glibc curl-devel jansson-devel 12 | %else 13 | Requires: glibc libcurl-devel jansson-devel 14 | %endif 15 | BuildRequires: gcc, make, selinux-policy-devel 16 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) 17 | BuildArch: i386, x86_64 18 | 19 | %define debug_package %{nil} 20 | 21 | %description 22 | This is user management tool for linux by github. 23 | The name-resloves and authentication is provided the team or collaborator on 24 | github. Features easy handling and ease of operation. 25 | 26 | %prep 27 | %setup -q -n %{name}-%{version} 28 | 29 | %build 30 | make 31 | 32 | %install 33 | %{__rm} -rf %{buildroot} 34 | mkdir -p %{buildroot}/usr/{lib64,bin} 35 | mkdir -p %{buildroot}%{_sysconfdir} 36 | make PREFIX=%{buildroot}/usr install 37 | install -d -m 755 %{buildroot}/var/cache/octopass 38 | install -m 644 octopass.conf.example %{buildroot}%{_sysconfdir}/octopass.conf.example 39 | mkdir -p %{buildroot}%{_datadir}/selinux/packages/%{name} 40 | install -m 644 %{name}.pp %{buildroot}%{_datadir}/selinux/packages/%{name}/%{name}.pp 41 | 42 | %clean 43 | %{__rm} -rf %{buildroot} 44 | 45 | %post 46 | # First install 47 | if [ "$1" -le "1" ]; then 48 | /usr/sbin/semodule -i %{_datadir}/selinux/packages/%{name}/%{name}.pp 2>/dev/null || : 49 | fixfiles -R octopass restore 50 | fi 51 | 52 | %preun 53 | # Final removal 54 | if [ "$1" -lt "1" ]; then 55 | /usr/sbin/semodule -r octopass 2>/dev/null || : 56 | fixfiles -R octopass restore 57 | fi 58 | 59 | %postun 60 | # Upgrade 61 | if [ "$1" -ge "1" ]; then 62 | /usr/sbin/semodule -i %{_datadir}/selinux/packages/%{name}/%{name}.pp 2>/dev/null || : 63 | fi 64 | 65 | %files 66 | %defattr(-, root, root) 67 | /usr/lib64/libnss_octopass.so 68 | /usr/lib64/libnss_octopass.so.2 69 | /usr/lib64/libnss_octopass.so.2.0 70 | /usr/bin/octopass 71 | %attr(0777,root,root) /var/cache/octopass 72 | /etc/octopass.conf.example 73 | %{_datadir}/selinux/packages/%{name}/%{name}.pp 74 | 75 | %changelog 76 | * Thu Apr 27 2021 linyows - 0.7.1-1 77 | - Bugfixes 78 | * Fri Jun 21 2019 linyows - 0.7.0-1 79 | - Resolve a problem of cache file permission 80 | * Mon Oct 22 2018 linyows - 0.6.0-1 81 | - Add policy for SELinux 82 | * Wed Oct 10 2018 linyows - 0.5.1-1 83 | - Fix for systemd-networkd SEGV 84 | * Thu Oct 02 2018 linyows - 0.5.0-1 85 | - Support slug for GitHub team API 86 | * Mon Apr 02 2018 linyows - 0.4.1-1 87 | - Page size changes to 100 from 30 on Github organization API 88 | * Mon Sep 25 2017 linyows - 0.4.0-1 89 | - Support github repository collaborators as name resolve 90 | * Thu Sep 14 2017 linyows - 0.3.5-1 91 | - Bugfixes 92 | * Sun May 07 2017 linyows - 0.3.3-1 93 | - Fix segmentation fault 94 | * Tue Feb 28 2017 linyows - 0.3.2-1 95 | - Example typo 96 | * Mon Feb 27 2017 linyows - 0.3.1-1 97 | - Bugfixes 98 | * Sun Feb 26 2017 linyows - 0.3.0-1 99 | - Support shared-users option 100 | * Sun Feb 19 2017 linyows - 0.2.0-1 101 | - Change implementation in Go to C 102 | * Fri Feb 03 2017 linyows - 0.1.0-1 103 | - Initial packaging 104 | -------------------------------------------------------------------------------- /selinux/octopass.te: -------------------------------------------------------------------------------- 1 | 2 | module octopass 1.0.0; 3 | 4 | require { 5 | type chkpwd_t; 6 | type oddjob_mkhomedir_t; 7 | type sshd_t; 8 | type admin_home_t; 9 | type var_t; 10 | type http_port_t; 11 | type cert_t; 12 | class tcp_socket name_connect; 13 | class dir { add_name create remove_name write }; 14 | class file { create getattr open read setattr unlink write }; 15 | } 16 | 17 | #============= chkpwd_t ============== 18 | 19 | #!!!! This avc can be allowed using the boolean 'nis_enabled' 20 | allow chkpwd_t http_port_t:tcp_socket name_connect; 21 | 22 | #!!!! WARNING: 'var_t' is a base type. 23 | allow chkpwd_t var_t:file read; 24 | 25 | #!!!! This avc is allowed in the current policy 26 | allow chkpwd_t var_t:file { getattr open }; 27 | 28 | #============= oddjob_mkhomedir_t ============== 29 | 30 | #!!!! WARNING: 'var_t' is a base type. 31 | allow oddjob_mkhomedir_t var_t:file read; 32 | 33 | #!!!! This avc is allowed in the current policy 34 | allow oddjob_mkhomedir_t var_t:file { getattr open }; 35 | 36 | #============= sshd_t ============== 37 | 38 | #!!!! This avc is allowed in the current policy 39 | allow sshd_t admin_home_t:dir create; 40 | 41 | #!!!! This avc is allowed in the current policy 42 | allow sshd_t cert_t:dir { add_name remove_name write }; 43 | 44 | #!!!! This avc is allowed in the current policy 45 | allow sshd_t cert_t:file { create setattr unlink }; 46 | 47 | #!!!! This avc is allowed in the current policy 48 | allow sshd_t http_port_t:tcp_socket name_connect; 49 | 50 | #!!!! This avc is allowed in the current policy 51 | allow sshd_t var_t:file { create getattr open read write }; 52 | -------------------------------------------------------------------------------- /test/collaborators.json: -------------------------------------------------------------------------------- 1 | [{"login":"linyows","id":72049,"avatar_url":"https://avatars1.githubusercontent.com/u/72049?v=4","gravatar_id":"","url":"https://api.github.com/users/linyows","html_url":"https://github.com/linyows","followers_url":"https://api.github.com/users/linyows/followers","following_url":"https://api.github.com/users/linyows/following{/other_user}","gists_url":"https://api.github.com/users/linyows/gists{/gist_id}","starred_url":"https://api.github.com/users/linyows/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/linyows/subscriptions","organizations_url":"https://api.github.com/users/linyows/orgs","repos_url":"https://api.github.com/users/linyows/repos","events_url":"https://api.github.com/users/linyows/events{/privacy}","received_events_url":"https://api.github.com/users/linyows/received_events","type":"User","site_admin":false,"permissions":{"admin":true,"push":true,"pull":true}},{"login":"nolinyows","id":72050,"avatar_url":"https://avatars1.githubusercontent.com/u/72050?v=4","gravatar_id":"","url":"https://api.github.com/users/nolinyows","html_url":"https://github.com/nolinyows","followers_url":"https://api.github.com/users/nolinyows/followers","following_url":"https://api.github.com/users/nolinyows/following{/other_user}","gists_url":"https://api.github.com/users/nolinyows/gists{/gist_id}","starred_url":"https://api.github.com/users/nolinyows/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nolinyows/subscriptions","organizations_url":"https://api.github.com/users/nolinyows/orgs","repos_url":"https://api.github.com/users/nolinyows/repos","events_url":"https://api.github.com/users/nolinyows/events{/privacy}","received_events_url":"https://api.github.com/users/nolinyows/received_events","type":"User","site_admin":false,"permissions":{"admin":false,"push":false,"pull":true}}] 2 | -------------------------------------------------------------------------------- /test/integration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CLR_PASS="\\033[0;32m" 4 | CLR_FAIL="\\033[0;31m" 5 | CLR_WARN="\\033[0;33m" 6 | CLR_INFO="\\033[0;34m" 7 | CLR_RESET="\\033[0;39m" 8 | ALL_PASSED=0 9 | 10 | function pass() { 11 | echo -e "[${CLR_PASS}PASS${CLR_RESET}] octopass::$(echo $1 | sed -e "s/test_//")" 12 | } 13 | 14 | function fail() { 15 | ALL_PASSED=1 16 | echo -e "[${CLR_FAIL}FAIL${CLR_RESET}] octopass::$(echo $1 | sed -e "s/test_//")" 17 | echo -e "${CLR_INFO}Expected${CLR_RESET}:" 18 | echo -e "$2" 19 | echo -e "${CLR_WARN}Actual${CLR_RESET}:" 20 | echo -e "$3" 21 | } 22 | 23 | function test_octopass_passwd() { 24 | actual="$(/usr/bin/octopass passwd linyows)" 25 | expected="linyows:x:74049:2000:managed by octopass:/home/linyows:/bin/bash" 26 | 27 | if [ "x$actual" == "x$expected" ]; then 28 | pass "${FUNCNAME[0]}" 29 | else 30 | fail "${FUNCNAME[0]}" "$expected" "$actual" 31 | fi 32 | } 33 | 34 | function test_octopass_shadow() { 35 | actual="$(/usr/bin/octopass shadow linyows)" 36 | expected="linyows:!!:::::::" 37 | 38 | if [ "x$actual" == "x$expected" ]; then 39 | pass "${FUNCNAME[0]}" 40 | else 41 | fail "${FUNCNAME[0]}" "$expected" "$actual" 42 | fi 43 | } 44 | 45 | function test_getent_passwd() { 46 | actual="$(getent passwd linyows)" 47 | expected="linyows:x:74049:2000:managed by octopass:/home/linyows:/bin/bash" 48 | 49 | if [ "x$actual" == "x$expected" ]; then 50 | pass "${FUNCNAME[0]}" 51 | else 52 | fail "${FUNCNAME[0]}" "$expected" "$actual" 53 | fi 54 | } 55 | 56 | function test_getent_shadow() { 57 | actual="$(getent shadow linyows)" 58 | expected="linyows:!!:::::::" 59 | 60 | if [ "x$actual" == "x$expected" ]; then 61 | pass "${FUNCNAME[0]}" 62 | else 63 | fail "${FUNCNAME[0]}" "$expected" "$actual" 64 | fi 65 | } 66 | 67 | function test_id() { 68 | actual="$(id linyows)" 69 | if [ "x$TRAVIS" == "xtrue" ]; then 70 | expected="uid=74049(linyows) gid=2000(travis) groups=2000(travis)" 71 | else 72 | expected="uid=74049(linyows) gid=2000(admin) groups=2000(admin)" 73 | fi 74 | 75 | if [ "x$actual" == "x$expected" ]; then 76 | pass "${FUNCNAME[0]}" 77 | else 78 | fail "${FUNCNAME[0]}" "$expected" "$actual" 79 | fi 80 | } 81 | 82 | function test_public_keys() { 83 | actual="$(/usr/bin/octopass linyows | head -1)" 84 | expected="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCbBkU87QyUEmecsQjCcMTdS6iARCUXzMo2awb4c+irGPUvkXxQUljmLFRXCIw+cEKajiS7VY5NLCZ6WVCbd4yIK+3jdNzrf74isiG8GdU+m64gNGaXtKGFaQEXBp9uWqqZgSw+bVMX2ArOtoh3lP96WJQOoXsOuX0izNS5qf1Z9E01J6IpE3xfudpaL4/vY1RnljM+KUoiIPFqS1Q7kJ+8LpHvV1T9SRiocpLThXOzifzwwoo9I6emwHr+kGwODERYWYvkMEwFyOh8fKAcTdt8huUz8n6k59V9y5hZWDuxP/zhnArUMwWHiiS1C5im8baX8jxSW6RoHuetBxSUn5vR" 85 | 86 | if [ "x$actual" == "x$expected" ]; then 87 | pass "${FUNCNAME[0]}" 88 | else 89 | fail "${FUNCNAME[0]}" "$expected" "$actual" 90 | fi 91 | } 92 | 93 | function test_public_keys_user_not_on_github() { 94 | actual="$(/usr/bin/octopass linyowsfoo | head -1)" 95 | expected="" 96 | 97 | if [ "x$actual" == "x$expected" ]; then 98 | pass "${FUNCNAME[0]}" 99 | else 100 | fail "${FUNCNAME[0]}" "$expected" "$actual" 101 | fi 102 | } 103 | 104 | function test_shared_user_public_keys() { 105 | sed -i -e 's/^#SharedUser/SharedUser/g' /etc/octopass.conf 106 | 107 | actual="$(/usr/bin/octopass admin | wc -l)" 108 | expected="4" 109 | 110 | if [ "x$actual" == "x$expected" ]; then 111 | pass "${FUNCNAME[0]}" 112 | else 113 | fail "${FUNCNAME[0]}" "$expected" "$actual" 114 | fi 115 | 116 | sed -i -e 's/^SharedUser/#SharedUser/g' /etc/octopass.conf 117 | } 118 | 119 | function test_authentication() { 120 | actual="$(echo $OCTOPASS_TOKEN | /usr/bin/octopass pam linyows; echo $?)" 121 | expected="0" 122 | 123 | if [ "x$actual" == "x$expected" ]; then 124 | pass "${FUNCNAME[0]}" 125 | else 126 | fail "${FUNCNAME[0]}" "$expected" "$actual" 127 | fi 128 | } 129 | 130 | function test_authentication_wrong_token() { 131 | actual="$(echo "1234567890123456789012345678901234567890" | /usr/bin/octopass pam linyows; echo $?)" 132 | expected="1" 133 | 134 | if [ "x$actual" == "x$expected" ]; then 135 | pass "${FUNCNAME[0]}" 136 | else 137 | fail "${FUNCNAME[0]}" "$expected" "$actual" 138 | fi 139 | } 140 | 141 | function test_authentication_wrong_user() { 142 | actual="$(echo $OCTOPASS_TOKEN | /usr/bin/octopass pam foobar; echo $?)" 143 | expected="1" 144 | 145 | if [ "x$actual" == "x$expected" ]; then 146 | pass "${FUNCNAME[0]}" 147 | else 148 | fail "${FUNCNAME[0]}" "$expected" "$actual" 149 | fi 150 | } 151 | 152 | function test_member_with_collaborators() { 153 | export OCTOPASS_OWNER=linyows 154 | export OCTOPASS_REPOSITORY=octopass 155 | actual="$(getent passwd linyows)" 156 | expected="linyows:x:74049:2000:managed by octopass:/home/linyows:/bin/bash" 157 | 158 | if [ "x$actual" == "x$expected" ]; then 159 | pass "${FUNCNAME[0]}" 160 | else 161 | fail "${FUNCNAME[0]}" "$expected" "$actual" 162 | fi 163 | unset OCTOPASS_OWNER 164 | unset OCTOPASS_REPOSITORY 165 | } 166 | 167 | function run_test() { 168 | self=$(cd $(dirname $0) && pwd)/$(basename $0) 169 | tests="$(grep "^function test_" $self | sed -E "s/function (.*)\(\) \{/\1/g")" 170 | for t in $(echo $tests); do 171 | $t 172 | done 173 | } 174 | 175 | run_test 176 | exit $ALL_PASSED 177 | -------------------------------------------------------------------------------- /test/octopass.conf: -------------------------------------------------------------------------------- 1 | # For Test 2 | 3 | Endpoint = "https://your.github.com/api/v3/" 4 | Token = "iad87dih122ce66a1e20a751664c8a9dkoak87g7" 5 | Organization = "yourorganization" 6 | Team = "yourteam" 7 | 8 | #Group = "yourgroup" 9 | #Home = "/home/foo/%s" 10 | #Shell = "/bin/zsh" 11 | 12 | UidStarts = 2000 13 | Gid = 2000 14 | Cache = 300 15 | Syslog = false 16 | 17 | SharedUsers = [ "admin", "deploy" ] 18 | -------------------------------------------------------------------------------- /test/octopass_repo.conf: -------------------------------------------------------------------------------- 1 | # For Test 2 | 3 | Endpoint = "https://your.github.com/api/v3/" 4 | Token = "iad87dih122ce66a1e20a751664c8a9dkoak87g7" 5 | 6 | Owner = "yourorganization" 7 | Repository = "yourrepo" 8 | Permission = "write" 9 | 10 | #Group = "yourgroup" 11 | #Home = "/home/foo/%s" 12 | #Shell = "/bin/zsh" 13 | 14 | UidStarts = 2000 15 | Gid = 2000 16 | Cache = 300 17 | Syslog = false 18 | -------------------------------------------------------------------------------- /website/.env.local: -------------------------------------------------------------------------------- 1 | HOMEPAGE_ID=4ac44a8f-a4fd-4c87-a9ba-5fae0d4a9713 2 | ROTION_INCREMENTAL_CACHE=true 3 | -------------------------------------------------------------------------------- /website/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | /public 39 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 20 | 21 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 22 | 23 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 24 | 25 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 26 | 27 | ## Learn More 28 | 29 | To learn more about Next.js, take a look at the following resources: 30 | 31 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 32 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 33 | 34 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 35 | 36 | ## Deploy on Vercel 37 | 38 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 39 | 40 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 41 | -------------------------------------------------------------------------------- /website/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | output: 'export', 5 | images: { 6 | unoptimized: true, 7 | }, 8 | }; 9 | 10 | export default nextConfig; 11 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "next": "14.2.3", 13 | "react": "^18", 14 | "react-dom": "^18", 15 | "rotion": "^1.5.1" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^20", 19 | "@types/react": "^18", 20 | "@types/react-dom": "^18", 21 | "eslint": "^8", 22 | "eslint-config-next": "14.2.3", 23 | "typescript": "^5" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /website/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { Inter } from 'next/font/google' 2 | import 'rotion/style.css' 3 | import '@/styles/globals.css' 4 | import type { AppProps } from 'next/app' 5 | 6 | const inter = Inter({ 7 | weight: ['400', '700'], 8 | subsets: ['latin'], 9 | display: 'swap', 10 | }) 11 | 12 | export default function App({ Component, pageProps }: AppProps) { 13 | return ( 14 | <> 15 | 16 | 22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /website/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from "next/document"; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /website/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { GetStaticProps, InferGetStaticPropsType } from 'next' 2 | import Image from 'next/image' 3 | import Head from 'next/head' 4 | import Link from 'next/link' 5 | import { 6 | FetchBlocks, 7 | FetchPage, 8 | FetchBlocksRes, 9 | } from 'rotion' 10 | import { Page, Link as RotionLink } from 'rotion/ui' 11 | import styles from '@/styles/Home.module.css' 12 | 13 | type Props = { 14 | icon: string 15 | logo: string 16 | blocks: FetchBlocksRes 17 | } 18 | 19 | export const getStaticProps: GetStaticProps = async (context) => { 20 | const id = process.env.HOMEPAGE_ID as string 21 | const page = await FetchPage({ page_id: id, last_edited_time: 'force' }) 22 | const logo = page.cover?.src || '' 23 | const icon = page.icon!.src 24 | const blocks = await FetchBlocks({ block_id: id, last_edited_time: page.last_edited_time }) 25 | 26 | return { 27 | props: { 28 | icon, 29 | logo, 30 | blocks, 31 | } 32 | } 33 | } 34 | 35 | export default function Home({ logo, icon, blocks }: InferGetStaticPropsType) { 36 | const y = new Date(Date.now()).getFullYear() 37 | return ( 38 | <> 39 | 40 | Octopass 41 | 42 | 43 |
44 |
45 |
46 |
47 |

Octopass

48 |
49 |
50 | Icon 51 |
52 |
53 | 54 |
55 | 56 |
57 | 58 | 65 |
66 |
67 | 68 | ) 69 | } 70 | -------------------------------------------------------------------------------- /website/styles/Home.module.css: -------------------------------------------------------------------------------- 1 | 2 | .header { 3 | padding: 6rem var(--side-padding) 0; 4 | max-width: 400px; 5 | margin: 0; 6 | } 7 | 8 | .icon img, 9 | .logo img { 10 | width: 100%; 11 | } 12 | 13 | .icon { 14 | top: 4rem; 15 | left: var(--side-padding); 16 | position: absolute; 17 | } 18 | 19 | .page { 20 | padding: 0 var(--side-padding) 8rem; 21 | max-width: 2400px; 22 | column-width: 30rem; 23 | column-gap: 6rem; 24 | column-rule-style: solid; 25 | column-rule-width: 1px; 26 | column-rule-color: #ddd; 27 | } 28 | 29 | .footer { 30 | padding: 2rem var(--side-padding) 6rem; 31 | background: var(--secondary-color); 32 | color: #fff; 33 | font-size: 1rem; 34 | } 35 | 36 | /* Mobile */ 37 | @media (max-width: 700px) { 38 | .header { 39 | max-width: 260px; 40 | } 41 | .icon { 42 | width: 80px; 43 | } 44 | } 45 | 46 | @media (prefers-color-scheme: dark) { 47 | .icon { 48 | top: 4.6rem; 49 | } 50 | .icon img, 51 | .logo img { 52 | filter: invert(100%); 53 | } 54 | .footer { 55 | background: var(--primary-color); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /website/styles/globals.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --max-width: 1100px; 3 | --border-radius: 12px; 4 | --side-padding: 4rem; 5 | --font-size: 1.1rem; 6 | 7 | --background-color: rgb(255, 255, 255); 8 | --foreground-color: rgb(94, 73, 85); 9 | --primary-color: rgb(153, 104, 136); 10 | --secondary-color: rgb(201, 157, 163); 11 | --accent-color: rgb(198, 221, 240); 12 | --rotion-primary-text: var(--foreground-color); 13 | } 14 | 15 | @media (prefers-color-scheme: dark) { 16 | :root { 17 | --background-color: rgb(42, 43, 42); 18 | --foreground-color: rgb(255, 255, 255); 19 | --primary-color: rgb(201, 157, 163); 20 | --secondary-color: rgb(153, 104, 136); 21 | --rotion-primary-text: var(--foreground-color); 22 | } 23 | } 24 | 25 | @media (max-width: 700px) { 26 | :root { 27 | --side-padding: 1rem; 28 | --font-size: 1rem; 29 | } 30 | } 31 | 32 | * { 33 | box-sizing: border-box; 34 | padding: 0; 35 | margin: 0; 36 | } 37 | 38 | html, 39 | body { 40 | max-width: 100vw; 41 | overflow-x: hidden; 42 | font-size: var(--font-size); 43 | } 44 | 45 | body { 46 | color: var(--foreground-color); 47 | background-color: var(--background-color); 48 | } 49 | 50 | a { 51 | color: inherit; 52 | text-decoration: none; 53 | } 54 | 55 | body .rotion-text-h1, 56 | body .rotion-text-h2, 57 | body .rotion-text-h3 { 58 | color: var(--primary-color); 59 | } 60 | body .rotion-richtext-link { 61 | background-color: var(--accent-color); 62 | } 63 | 64 | @media (prefers-color-scheme: dark) { 65 | html { 66 | color-scheme: dark; 67 | } 68 | body .rotion-richtext-link { 69 | color: var(--accent-color); 70 | border-color: var(--accent-color); 71 | background: none; 72 | } 73 | body .rotion-code-area { 74 | border: 1px solid rgba(100,100,100,0.4); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "paths": { 16 | "@/*": ["./*"] 17 | } 18 | }, 19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 20 | "exclude": ["node_modules"] 21 | } 22 | --------------------------------------------------------------------------------