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 |
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 |
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 |
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 |
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 |