From 34b8d30c50ff74f4680625e94bcdb53d6b335fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AB=8F=E8=A8=AA=E5=AD=90?= Date: Wed, 21 Jan 2026 04:43:42 +0900 Subject: [PATCH] =?UTF-8?q?SVN=E3=81=8B=E3=82=89=E3=81=AE=E3=83=9F?= =?UTF-8?q?=E3=83=A9=E3=83=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 61 ++++++++++ LICENSE.txt | 13 +++ Makefile | 121 +++++++++++++++++++ README.md | 151 ++++++++++++++++++++++++ main.c | 170 +++++++++++++++++++++++++++ man/sp-en.1 | 191 ++++++++++++++++++++++++++++++ man/sp-jp.1 | 191 ++++++++++++++++++++++++++++++ sp-completion.zsh | 70 +++++++++++ src/addpass.c | 288 ++++++++++++++++++++++++++++++++++++++++++++++ src/addpass.h | 13 +++ src/base32.c | 65 +++++++++++ src/base32.h | 8 ++ src/chkpass.c | 184 +++++++++++++++++++++++++++++ src/chkpass.h | 18 +++ src/common.c | 273 +++++++++++++++++++++++++++++++++++++++++++ src/common.h | 40 +++++++ src/delpass.c | 162 ++++++++++++++++++++++++++ src/delpass.h | 6 + src/findpass.c | 25 ++++ src/findpass.h | 8 ++ src/genpass.c | 32 ++++++ src/genpass.h | 8 ++ src/initpass.c | 48 ++++++++ src/initpass.h | 6 + src/listpass.c | 56 +++++++++ src/listpass.h | 6 + src/otppass.c | 246 +++++++++++++++++++++++++++++++++++++++ src/otppass.h | 6 + src/showpass.c | 148 ++++++++++++++++++++++++ src/showpass.h | 6 + src/vulnpass.c | 141 +++++++++++++++++++++++ src/vulnpass.h | 6 + src/yankpass.c | 154 +++++++++++++++++++++++++ src/yankpass.h | 6 + 34 files changed, 2927 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 LICENSE.txt create mode 100644 Makefile create mode 100644 README.md create mode 100644 main.c create mode 100644 man/sp-en.1 create mode 100644 man/sp-jp.1 create mode 100644 sp-completion.zsh create mode 100644 src/addpass.c create mode 100644 src/addpass.h create mode 100644 src/base32.c create mode 100644 src/base32.h create mode 100644 src/chkpass.c create mode 100644 src/chkpass.h create mode 100644 src/common.c create mode 100644 src/common.h create mode 100644 src/delpass.c create mode 100644 src/delpass.h create mode 100644 src/findpass.c create mode 100644 src/findpass.h create mode 100644 src/genpass.c create mode 100644 src/genpass.h create mode 100644 src/initpass.c create mode 100644 src/initpass.h create mode 100644 src/listpass.c create mode 100644 src/listpass.h create mode 100644 src/otppass.c create mode 100644 src/otppass.h create mode 100644 src/showpass.c create mode 100644 src/showpass.h create mode 100644 src/vulnpass.c create mode 100644 src/vulnpass.h create mode 100644 src/yankpass.c create mode 100644 src/yankpass.h diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ee5ea1d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,61 @@ +# 1.5.1 (2025年03月13日) +* OTPで、秘密鍵を自動的に大文字にする様に + +# 1.5.0 (2024年12月17日) +* パスワード表示で、「OpenPGP」かどうかの確認の追加 +* 侵害されたパスワードの確認の追加 +* 複数サイトで同じパスワードを利用かどうか、パスワードの長さ、又はパスワードの強さの確認 +* パスワードコピーの期間を設定出来る様に +* ワンタイムパスワード(OTP)を表示せずにコピー機能性の追加 +* Wayland対応の追加 +* コピータイムアウトの間にCTRL+Cを押したら、クリップボードから取り消す様に +* コンフィグファイルの追加 + +# 1.4.0 (2024年09月24日) +* Haiku対応 +* macOS対応 +* パスワードの検索の追加 +* 静的リンクはデフォルトに +* simpas情報の表示 +* ベースディレクトリを関数化 +* パスワードの編集する前に、バックアップを作成する様に + +# 1.3.0 (2024年06月17日) +* 英訳の追加 (レミリア・スカーレットさん) +* GNU Make → BSD Make +* GPLv2 → ISC +* OpenBSD向けのリリースコマンドの追加 +* FreeBSD向けのリリースコマンドの追加 +* Linux向けのリリースコマンドの追加 +* 最新ルールに従い +* NetBSD向けのリリースコマンドの追加 +* OpenBSD 7.5でTOTPの修正 (ヌル終端文字列のバグ) +* ヘルプの表示の削除 (manpageをご利用下さい) +* パスワード作成関数のデフォルトな長さは64に +* manpageを細かくに +* パスワードがなくなるまで削除したら、ディレクトリも削除する様に + +# 1.2.0 (2024年02月01日) +* やっとTOTP機能性を修正した +* makeを実行したら、バイナリがもっと小さくなる +* パスワードの長さの延長 +* パスワード追加機能性で、パスワードが既に存在するかどうか確認 +* パスワード削除機能性で、パスワードが存在ないかどうか確認 +* パスワード変更機能性の追加 +* zsh対応の修正 + +# 1.1.2 (2023年12月01日) +* OpenBSDでのコンパイラーが発生された問題を修正した + +# 1.1.1 (2023年12月01日) +* make install-zsh部分を修正 + +# 1.1.0 (2023年12月01日) +* TOTP対応 +* READMEファイルで使い方を詳しく説明する +* zshキャプチャー +* パスワード追加関数を安定化 +* パスワード表示とパスワードのコピー関数で、GNU Passで保存したパスワードの場合は改行を追加しない様に + +# 1.0.0 (2023年11月30日) +* 最初リリース diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..777a97f --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,13 @@ +Copyright © 2018-2024 by 076.moe + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD +TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR +CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4fb370e --- /dev/null +++ b/Makefile @@ -0,0 +1,121 @@ +UNAME_S != uname -s +UNAME_M != uname -m +OS = ${UNAME_S} + +.if ${UNAME_S} == "OpenBSD" +OS = openbsd +.elif ${UNAME_S} == "NetBSD" +OS = netbsd +.elif ${UNAME_S} == "FreeBSD" +OS = freebsd +.elif ${UNAME_S} == "Linux" +OS = linux +.endif + +.if ${UNAME_M} == "x86_64" +UNAME_M = amd64 +.endif + +NAME != cat main.c | grep "const char \*sofname" | awk '{print $$5}' | \ + sed "s/\"//g" | sed "s/;//" +VERSION != cat main.c | grep "const char \*version" | awk '{print $$5}' | \ + sed "s/\"//g" | sed "s/;//" +PREFIX = /usr/local +.if ${UNAME_S} == "Haiku" +PREFIX = /boot/home/config/non-packaged +.elif ${UNAME_S} == "Linux" +PREFIX = /usr +.endif + +MANPREFIX = ${PREFIX}/share/man +.if ${UNAME_S} == "OpenBSD" +MANPREFIX = ${PREFIX}/man +.elif ${UNAME_S} == "Haiku" +MANPREFIX = ${PREFIX}/documentation/man +.endif + +DATAPREFIX = ${PREFIX}/share +.if ${UNAME_S} == "Haiku" +DATAPREFIX = ${PREFIX}/data +.endif + +CC = cc +FILES = main.c src/*.c + +CFLAGS = -Wall -Wextra -I/usr/include -L/usr/lib +.if ${UNAME_S} == "NetBSD" +CFLAGS += -I/usr/pkg/include -L/usr/pkg/lib -I/usr/local/include -L/usr/local/lib +.elif ${UNAME_S} == "OpenBSD" || ${UNAME_S} == "FreeBSD" +CFLAGS += -I/usr/local/include -L/usr/local/lib +.endif + +LDFLAGS = -lgpgme -lcrypto + +.if ${UNAME_S} == "OpenBSD" +LDFLAGS += -lc -lassuan -lgpg-error -lintl -liconv +.elif ${UNAME_S} == "FreeBSD" +LDFLAGS += -lc -lassuan -lgpg-error -lthr -lintl +.elif ${UNAME_S} == "NetBSD" +LDFLAGS += -lcrypt -lc -lassuan -lgpg-error -lintl +.elif ${UNAME_S} == "Linux" +LDFLAGS += -lc -lassuan -lgpg-error +.endif + +all: + ${CC} -O3 ${CFLAGS} -o ${NAME} ${FILES} -static ${LDFLAGS} + strip ${NAME} + +debug: + ${CC} -g ${CFLAGS} -o ${NAME} ${FILES} ${LDFLAGS} + +clean: + rm -f ${NAME} + +dist: clean + mkdir -p release/src ${NAME}-${VERSION} + cp -R LICENSE.txt Makefile README.md CHANGELOG.md \ + ${NAME}-completion.zsh man main.c src ${NAME}-${VERSION} + tar zcfv release/src/${NAME}-${VERSION}.tar.gz ${NAME}-${VERSION} + rm -rf ${NAME}-${VERSION} + +man: + mkdir -p release/man/${VERSION} + sed "s/VERSION/${VERSION}/g" < man/${NAME}-en.1 > \ + release/man/${VERSION}/${NAME}-en.1 + sed "s/VERSION/${VERSION}/g" < man/${NAME}-jp.1 > \ + release/man/${VERSION}/${NAME}-jp.1 + +release: + mkdir -p release/bin/${VERSION}/${OS}/${UNAME_M} + ${CC} -O3 ${CFLAGS} -o release/bin/${VERSION}/${OS}/${UNAME_M}/${NAME} ${FILES} \ + -static ${LDFLAGS} + strip release/bin/${VERSION}/${OS}/${UNAME_M}/${NAME} + +install: + mkdir -p ${DESTDIR}${PREFIX}/bin ${DESTDIR}${MANPREFIX}/man1 + cp -f ${NAME} ${DESTDIR}${PREFIX}/bin + chmod 755 ${DESTDIR}${PREFIX}/bin/${NAME} + mkdir -p ${DESTDIR}${MANPREFIX}/man1 + sed "s/VERSION/${VERSION}/g" < man/${NAME}-en.1 > \ + ${DESTDIR}${MANPREFIX}/man1/${NAME}-en.1 + sed "s/VERSION/${VERSION}/g" < man/${NAME}-jp.1 > \ + ${DESTDIR}${MANPREFIX}/man1/${NAME}-jp.1 + chmod 644 ${DESTDIR}${MANPREFIX}/man1/${NAME}-en.1 + chmod 644 ${DESTDIR}${MANPREFIX}/man1/${NAME}-jp.1 + +install-zsh: + mkdir -p ${DESTDIR}${DATAPREFIX}/zsh/site-functions + cp ${NAME}-completion.zsh ${DESTDIR}${DATAPREFIX}/zsh/site-functions/_${NAME} + +publish: + rsync -rtvzP release/bin/${VERSION} 192.168.0.143:/zroot/repo/bin/${NAME} + rsync -rtvzP release/src/* 192.168.0.143:/zroot/repo/src/${NAME} + rsync -rtvzP release/man/* 192.168.0.143:/zroot/repo/man/${NAME} + +uninstall: + rm -rf ${DESTDIR}${PREFIX}/bin/${NAME} + rm -rf ${DESTDIR}${MANPREFIX}/man1/${NAME}-en.1 + rm -rf ${DESTDIR}${MANPREFIX}/man1/${NAME}-jp.1 + rm -rf ${DESTDIR}${DATAPREFIX}/zsh/site-functions/_${NAME} + +.PHONY: all clean dist man release install install-zsh uninstall diff --git a/README.md b/README.md new file mode 100644 index 0000000..8e9f00c --- /dev/null +++ b/README.md @@ -0,0 +1,151 @@ +# sp +シンプルなパスワードマネージャー +Simple Password Manager + +## インストールする方法 | Installation +### CRUX +```sh +doas prt-get depinst gpgme gnupg pinentry bmake +bmake +doas bmake install PREFIX=/usr +doas bmake install-zsh PREFIX=/usr +``` + +### Artix +```sh +doas pacman -S base-devel gpgme gnupg pinentry bmake +bmake +doas bmake install PREFIX=/usr +doas bmake install-zsh PREFIX=/usr +``` + +### OpenBSD +```sh +doas pkg_add gpgme gnupg pinentry +make +doas make install +doas make install-zsh +``` + +### NetBSD +```sh +doas pkgin install gpgme gnupg pinentry +make +doas make install +doas make install-zsh +``` + +### FreeBSD +```sh +doas pkg install gpgme gnupg pinentry +make +doas make install +doas make install-zsh +``` + +### Haiku +```sh +pkgman install bmake libassuan libassuan_devel gpgme gpgme_devel gnupg llvm18_clang +bmake +bmake install +bmake install-zsh +``` + +### macOS +```sh +brew install bmake libassuan gpgme gnupg pinentry pinentry-mac +bmake +doas bmake install +doas bmake install-zsh +``` + +## 初期設定 | Initial setup +「gpg -k」でGPG鍵IDを確認して、「sp -i [GPG ID]」を実行して下さい。 +Confirm your GPG key ID with "gpg -k", and run "sp -i [GPG ID]". + +## 使い方 | Usage +### パスワードの作成 | Password generation +#### 強いパスワードの場合 | Strong password +```sh +$ sp -g 64 +nSYGSF2lWGCUsqKCRB_~mZm+spaU + +const char *sofname = "sp"; +const char *version = "1.5.1"; +const char *avalopt = "abcdefgiloOsvy"; +const char *madefor = "simpas 1.1.1"; + +void usage() { + printf("%s-%s (%s)\nusage: %s [-%s]\n", + sofname, version, madefor, sofname, avalopt); +} + +char *getfullpath(char *arg) { + char *lang = getlang(); + + char *basedir = getbasedir(1); + size_t fullPathLen; + char *fullPath; + fullPathLen = strlen(basedir) + (arg != NULL ? strlen(arg) + 5 : 0); + + fullPath = (char *)malloc(fullPathLen); + if (fullPath == NULL) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to allocating memory"); + else perror("メモリの役割に失敗"); + free(basedir); + return NULL; + } + + if (arg != NULL) { + snprintf(fullPath, fullPathLen, "%s%s.gpg", basedir, arg); + } else { + snprintf(fullPath, fullPathLen, "%s", basedir); + } + + return fullPath; +} + +void editpass(char *file) { + char *lang = getlang(); + +#if defined(__HAIKU__) + const char *tmpfile = "/boot/system/cache/sp-tmp.gpg"; +#else + const char *tmpfile = "/tmp/sp-tmp.gpg"; +#endif + tmpcopy(file, tmpfile); + if (delpass(file, 1) != 0) { + if (strncmp(lang, "en", 2) == 0) + perror("Editing failed: Failed to delete."); + else perror("編集に失敗:削除に失敗"); + return; + } + if (addpass(file) != 0) { + tmpcopy(tmpfile, file); + unlink(tmpfile); + if (strncmp(lang, "en", 2) == 0) + perror("Editing failed: Failed to add."); + else perror("編集に失敗:追加に失敗"); + return; + } + + unlink(tmpfile); +} + +int main(int argc, char *argv[]) { + if (argc < 2) { + usage(); + return 0; + } + + if (strcmp(argv[1], "-g") == 0) { + if (argc > 4) { + usage(); + return 1; + } + + if (argc == 2) genpass(64, true); + else if (argc == 3) genpass(atoi(argv[2]), true); + else if (argc == 4 && strcmp(argv[3], "risk") == 0) + genpass(atoi(argv[2]), false); + else if (argc == 4 && strcmp(argv[3], "secure") == 0) + genpass(atoi(argv[2]), true); + + return 0; + } + + if (argc == 3) { + if (strcmp(argv[1], "-i") == 0) initpass(argv[2]); + else if (strcmp(argv[1], "-s") == 0) { + const char *pass = showpass(argv[2]); + if (pass == NULL) return -1; + printf("%s\n", pass); + } + else if (strcmp(argv[1], "-y") == 0) yankpass(argv[2], 45); + else if (strcmp(argv[1], "-a") == 0) addpass(argv[2]); + else if (strcmp(argv[1], "-d") == 0) delpass(argv[2], 0); + else if (strcmp(argv[1], "-e") == 0) { + editpass(argv[2]); + } else if (strcmp(argv[1], "-o") == 0) { + char *fullPath = getfullpath(argv[2]); + if (fullPath == NULL) return -1; + otppass(fullPath, 0, 0); + free(fullPath); + } else if (strcmp(argv[1], "-O") == 0) { + char *fullPath = getfullpath(argv[2]); + if (fullPath == NULL) return -1; + otppass(fullPath, 1, 30); + free(fullPath); + } else if (strcmp(argv[1], "-f") == 0) { + char *fullPath = getfullpath(NULL); + if (fullPath == NULL) return -1; + findpass(fullPath, argv[2]); + free(fullPath); + } else if (strcmp(argv[1], "-c") == 0) { + char *fullPath = getfullpath(NULL); + if (fullPath == NULL) return -1; + chkpass(fullPath, argv[2]); + free(fullPath); + } else { + usage(); + return 1; + } + } else if (argc == 4) { + if (strcmp(argv[1], "-y") == 0) { + int i; + if (sscanf(argv[3], "%d", &i) == 0) yankpass(argv[2], 45); + else yankpass(argv[2], atoi(argv[3])); + } else if (strcmp(argv[1], "-O") == 0) { + char *fullPath = getfullpath(argv[2]); + if (fullPath == NULL) return -1; + int i; + if (sscanf(argv[3], "%d", &i) == 0) otppass(fullPath, 1, 30); + else otppass(fullPath, 1, atoi(argv[3])); + free(fullPath); + } + } else if (argc == 2) { + char *basePath = getfullpath(NULL); + if (basePath == NULL) return -1; + + if (strcmp(argv[1], "-l") == 0) listpass(basePath, 0); + else if (strcmp(argv[1], "-b") == 0) vulnpass(basePath); + else if (strcmp(argv[1], "-v") == 0) { + printf("%s-%s (%s)\n", sofname, version, madefor); + } else { + usage(); + free(basePath); + return 1; + } + free(basePath); + } else { + usage(); + return 1; + } + + return 0; +} diff --git a/man/sp-en.1 b/man/sp-en.1 new file mode 100644 index 0000000..a942e49 --- /dev/null +++ b/man/sp-en.1 @@ -0,0 +1,191 @@ +.TH SP 1 VERSION +.SH NAME +sp - Simple Password Manager +.br +.B sp +[-bfi] [-i \fI\,gpg-id\fR] [-adeos \fI\,password\fR] [-c \fI,all\fR|\fI,length\fR|\fI,strength\fR|\fI,duplicate\fR] [-g [\fI\,length\fR] [\fI\,secure\fR|\fI\,risk\fR]] [-Oy \fI\,password\fR [\fI,seconds\fR]] +.SH DESCRIPTION +.PP +Simple Password Manager +.TP +\fB\,a\fR \fI\,password\fR +Add password +.TP +\fB\,b\fR +Check for breached passwords (internet connection is required) +.TP +\fB\,c\fR \fI,all|length|strength|duplicate\fR +Check for password length, strength, or duplicates. +.br +length = Check for password length +strength = Check for password strength +duplicate = Check for duplicate passwords +all = Check all of the above +.TP +\fB\,d\fR \fI\,password\fR +Delete password +.TP +\fB\,e\fR \fI\,password\fR +Edit password +.TP +\fB\,g\fR \fI,length\fR \fI,risk|secure\fR +Generate a random password using the preferred amount of digits. +.br +The default is 64 characters. +.br +secure = Lower and uppercase + digits + special characters (default) +.br +risk = only lowercase and uppercase + digits +.TP +\fB\,i\fR \fI\,gpg-id\fR +Initialize password storage using GPG +.TP +\fB\,l\fR +Show a list of passwords +.TP +\fB\,o\fR \fI\,password\fR +Show one time password (TOTP) +.TP +\fB\,O\fR \fI\,password\fR \fI\,seconds\fR +Copy one time password (TOTP) +.br +The default is 30 seconds. +.TP +\fB\,s\fR \fI\,password\fR +Show password +.TP +\fB\,v\fR +Show version +.TP +\fB\,y\fR \fI\,password\fR \fI,seconds\fR +Copy password to the clipboard without showing the password (Xorg-only) +.br +The default is 45 seconds. +.SH LANGUAGE +The default language is Japanese. In order to use English, +.br +please add "SP_LANG=en" to the `.zshrc` or `.bashrc` file. +.SH EXAMPLES +\&... + +$ sp -i 12345678901234567890ABCDEFABCDEF1234ABCD +.br +初期設定に完了しました。 + +$ cat ~/.local/share/sp/.gpg-id +.br +12345678901234567890ABCDEFABCDEF1234ABCD +.ED + +\&... + +$ sp -a 076.moe/nitori +.br +Password: +.br +Password (for verification): +.br +The password got saved. + +$ sp -s 076.moe/nitori +.br +kyuuri + +$ sp -d 076.moe/nitori +.br +Are you sure you want to delete the password '076.moe/nitori' (y/N): y +.br +The password got deleted +.br + +$ sp -s 076.moe/nitori +.br +Failed to open file: No such file or directory +.ED + +\&... + +$ sp -a 076.moe/nitori +.br +Password: +.br +Password (for verification): +.br +The password got saved. + +$ sp -s 076.moe/nitori +.br +kyuuri + +$ sp -a 076.moe/nitori +.br +パスワードが既に存在しています。 +.br +変更するには、「 sp -e 076.moe/nitori 」を実行して下さい。 + +$ SP_LANG=en sp -a 076.moe/nitori +.br +Password already exist. +.br +For edit, please run ' sp -e 076.moe/nitori '. + +$ sp -e 076.moe/nitori +.br +Password: +.br +Password (for verification): +.br +The password got saved. + +$ sp -s 076.moe/nitori +.br +kappa +.ED + +\&... + +$ sp -g +.br +\5C'F6=8r&:OO=P?{Ry-3d4%z#7Hki}965l`j2xJSRoHQkvj^nz+YPx4g74yu_OT + +$ sp -g 12 +.br +{%#upiPiayqZ + +$ sp -g 12 risk +.br +iwxoumJC9wZH +.ED + +\&... + +$ doas pkg_add zbar +.br +quirks-7.14 signed on 2024-05-21T22:52:07Z +.br +zbar-0.23.90p2: ok + +$ zbarimg -q --raw Untitled.png +.br +otpauth://totp/GitHub:TechnicalSuwako?secret=ABCDEFGHIJKLMNOP&issuer=GitHub + +$ sp -a github.com/2fa +.br +Password: +.br +Password (for verification): +.br +The password got saved. + +$ sp -s github.com/2fa +.br +otpauth://totp/GitHub:TechnicalSuwako?secret=ABCDEFGHIJKLMNOP&issuer=GitHub + +$ sp -o github.com/2fa +.br +123456 +.Ed +.SH AUTHORS +.PP +Technical Suwako (developer) +Remilia Scarlet (English translation) diff --git a/man/sp-jp.1 b/man/sp-jp.1 new file mode 100644 index 0000000..bde1b92 --- /dev/null +++ b/man/sp-jp.1 @@ -0,0 +1,191 @@ +.TH SP 1 VERSION +.SH NAME +sp - Simple Password Manager +.br +.B sp +[-bfi] [-i \fI\,gpg-id\fR] [-adeos \fI\,password\fR] [-c \fI,all\fR|\fI,length\fR|\fI,strength\fR|\fI,duplicate\fR] [-g [\fI\,length\fR] [\fI\,secure\fR|\fI\,risk\fR]] [-Oy \fI\,password\fR [\fI,seconds\fR]] +.SH 説明 +.PP +シンプルなパスワードマネージャー。 +.TP +\fB\,a\fR \fI\,password\fR +パスワードを追加 +.TP +\fB\,b\fR +漏洩されたパスワードを確認(インターネット接続が必須) +.TP +\fB\,c\fR \fI,all|length|strength|duplicate\fR +パスワードの長さ、強さ、や服数度を確認 +.br +length=パスワードの長さの確認 +strength=パスワードの強さの確認 +duplicate=服数パスワードが同じかどうか確認 +all=上記の全部の確認 +.TP +\fB\,d\fR \fI\,password\fR +パスワードを削除 +.TP +\fB\,e\fR \fI\,password\fR +パスワードを変更 +.TP +\fB\,g\fR \fI,length\fR \fI,risk|secure\fR +希望文字数でパスワードをランダムに作成する。 +.br +デフォルトな長さは64文字です。 +.br +secure=英数字+記号文字(デフォルト)を使用 +.br +risk=英数字のみ(不安)を使用 +.TP +\fB\,i\fR \fI\,gpg-id\fR +GPGと使ってパスワードストレージを初期設定 +.TP +\fB\,l\fR +パスワード一覧を表示 +.TP +\fB\,o\fR \fI\,password\fR +ワンタイムパスワード(TOTP)を表示 +.TP +\fB\,O\fR \fI\,password\fR \fI,seconds\fR +ワンタイムパスワード(TOTP)をコピー +.br +デフォルトは30秒です。 +.TP +\fB\,s\fR \fI\,password\fR +パスワードを表示 +.TP +\fB\,v\fR +バージョンを表示 +.TP +\fB\,y\fR \fI\,password\fR \fI,seconds\fR +パスワードを表示せずクリップボードにコピーする(Xorgのみ) +.br +デフォルトは45秒です。 +.SH LANGUAGE +デフォルトの言語は日本語ですが、英語で利用するには、 +.br +「.zshrc」、「.bashrc」等ファイルで「SP_LANG=en」を追加して下さい。 +.SH EXAMPLES +\&... + +$ sp -i 12345678901234567890ABCDEFABCDEF1234ABCD +.br +初期設定に完了しました。 + +$ cat ~/.local/share/sp/.gpg-id +.br +12345678901234567890ABCDEFABCDEF1234ABCD +.ED + +\&... + +$ sp -a 076.moe/nitori +.br +パスワード: +.br +パスワード(確認用): +.br +パスワードを保存出来ました + +$ sp -s 076.moe/nitori +.br +kyuuri + +$ sp -d 076.moe/nitori +.br +パスワード「076.moe/nitori」を本当に削除する事が宜しいでしょうか? (y/N): y +.br +パスワードを削除しました +.br + +$ sp -s 076.moe/nitori +.br +ファイルを開くに失敗: No such file or directory +.ED + +\&... + +$ sp -a 076.moe/nitori +.br +パスワード: +.br +パスワード(確認用): +.br +パスワードを保存出来ました + +$ sp -s 076.moe/nitori +.br +kyuuri + +$ sp -a 076.moe/nitori +.br +パスワードが既に存在しています。 +.br +変更するには、「 sp -e 076.moe/nitori 」を実行して下さい。 + +$ SP_LANG=en sp -a 076.moe/nitori +.br +Password already exist. +.br +For edit, please run ' sp -e 076.moe/nitori '. + +$ sp -e 076.moe/nitori +.br +パスワード: +.br +パスワード(確認用): +.br +パスワードを保存出来ました + +$ sp -s 076.moe/nitori +.br +kappa +.ED + +\&... + +$ sp -g +.br +\5C'F6=8r&:OO=P?{Ry-3d4%z#7Hki}965l`j2xJSRoHQkvj^nz+YPx4g74yu_OT + +$ sp -g 12 +.br +{%#upiPiayqZ + +$ sp -g 12 risk +.br +iwxoumJC9wZH +.ED + +\&... + +$ doas pkg_add zbar +.br +quirks-7.14 signed on 2024-05-21T22:52:07Z +.br +zbar-0.23.90p2: ok + +$ zbarimg -q --raw Untitled.png +.br +otpauth://totp/GitHub:TechnicalSuwako?secret=ABCDEFGHIJKLMNOP&issuer=GitHub + +$ sp -a github.com/2fa +.br +パスワード: +.br +パスワード(確認用): +.br +パスワードを保存出来ました + +$ sp -s github.com/2fa +.br +otpauth://totp/GitHub:TechnicalSuwako?secret=ABCDEFGHIJKLMNOP&issuer=GitHub + +$ sp -o github.com/2fa +.br +123456 +.Ed +.SH AUTHORS +.PP +テクニカル諏訪子(開発者) +レミリア・スカーレット(英訳) diff --git a/sp-completion.zsh b/sp-completion.zsh new file mode 100644 index 0000000..08574fa --- /dev/null +++ b/sp-completion.zsh @@ -0,0 +1,70 @@ +#compdef sp +#autoload + +_sp () { + local cmd + if (( CURRENT > 2)); then + cmd=${words[2]} + curcontext="${curcontext%:*:*}:pass-$cmd" + (( CURRENT-- )) + shift words + case "${cmd}" in + -i) + _sp_complete_keys + ;; + -i|-y|-d|-a|-e|-o|-O) + _sp_complete_entries_helper + ;; + -g) + local -a subcommands + subcommands=( + "secure:英数字+記号文字(デフォルト)" + "risk:英数字のみ(不安)" + ) + ;; + -s) + _sp_cmd_show + ;; + esac + else + local -a subcommands + subcommands=( + "-a:パスワードを追加" + "-b:漏洩されたパスワードを確認(インターネット接続が必須)" + "-c:パスワードの長さ、強さ、や服数度を確認する。length=パスワードの長さの確認、strength=パスワードの強さの確認、duplicate=服数パスワードが同じかどうか確認、all=上記の全部の確認" + "-d:パスワードを削除" + "-e:パスワードを変更" + "-g:希望文字数でパスワードをランダムに作成する。デフォルトな長さは64文字です。secure=英数字+記号文字(デフォルト)を使用、risk=英数字のみ(不安)を使用" + "-i:GPGと使ってパスワードストレージを初期設定" + "-l:パスワード一覧を表示" + "-o:ワンタイムパスワード(TOTP)を表示" + "-O:ワンタイムパスワード(TOTP)をコピー。デフォルトは30秒です。" + "-s:パスワードを表示" + "-v:バージョンを表示" + "-y:パスワードを表示せずクリップボードにコピーする(Xorgのみ)。デフォルトは45秒です。" + ) + _sp_cmd_show + fi +} + +_sp_cmd_show () { + _sp_complete_entries +} + +_sp_complete_entries() { + _sp_complete_entries_helper -type f +} + +_sp_complete_entries_helper () { + local IFS=$'\n' + local prefix + zstyle -s ":completion:${curcontext}:" prefix prefix || prefix="${SP_DIR:-$HOME/.local/share/sp}" + _values -C 'passwords' ${$(find -L "$prefix" \( -name .git -o -name .gpg-id \) -prune -o $@ -print 2>/dev/null | sed -e "s#${prefix}/\{0,1\}##" -e 's#\.gpg##' -e 's#\\#\\\\#g' -e 's#:#\\:#g' | sort):-""} +} + +_sp_complete_keys () { + local IFS=$'\n' + _values 'gpg keys' $(gpg2 --list-secret-keys --with-colons | cut -d : -f 10 | sort -u | sed '/^$/d') +} + +_sp diff --git a/src/addpass.c b/src/addpass.c new file mode 100644 index 0000000..4998275 --- /dev/null +++ b/src/addpass.c @@ -0,0 +1,288 @@ +#include "addpass.h" + +void cleanup(gpgme_ctx_t ctx, gpgme_key_t key, gpgme_data_t in, gpgme_data_t out) { + gpgme_data_release(in); + gpgme_data_release(out); + gpgme_release(ctx); + gpgme_key_release(key); +} + +void getpasswd(char *prompt, char *pw, size_t pwlen) { + struct termios old, new; + printf("%s", prompt); + + // 端末設定を受け取る + tcgetattr(fileno(stdin), &old); + new = old; + + // echoを無効にして + new.c_lflag &= ~ECHO; + tcsetattr(fileno(stdin), TCSANOW, &new); + + // パスワードを読み込む + if (fgets(pw, pwlen, stdin) != NULL) { + // あれば、改行を取り消す + size_t len = strlen(pw); + if (len > 0 && pw[len - 1] == '\n') { + pw[len - 1] = '\0'; + } else { + // 掃除 + int c; + while ((c = getchar()) != '\n' && c != EOF); + } + } + + // 端末設定をもとに戻す + tcsetattr(fileno(stdin), TCSANOW, &old); +} + +int addpass(char *file) { + char *lang = getlang(); + + // パスを準備 + char *basedir = getbasedir(1); + char *ext = ".gpg"; + + char pass[256]; + char knin[256]; + + int alllen = snprintf(NULL, 0, "%s%s%s", basedir, file, ext) + 1; + char *gpgpathchk = malloc(alllen); + if (gpgpathchk == NULL) { + const char *ero = (strncmp(lang, "en", 2) == 0 ? + "Failed to allocate memory" : "メモリを割当に失敗"); + fprintf(stderr, "%s\n", ero); + return -1; + } + + // ファイルが既に存在するかどうか確認 + snprintf(gpgpathchk, alllen, "%s%s%s", basedir, file, ext); + + if (access(gpgpathchk, F_OK) != -1) { + if (strncmp(lang, "en", 2) == 0) + fprintf( + stderr, + "Password already exist.\nTo edit, please run ' sp -e %s '.\n", + file + ); + else + fprintf( + stderr, + "%s\n変更するには、「 sp -e %s 」を実行して下さい。\n", + "パスワードが既に存在しています。", + file + ); + free(gpgpathchk); + return -1; + } + free(gpgpathchk); + + // パスワードを受け取る + if (strncmp(lang, "en", 2) == 0) + getpasswd("Password: ", pass, sizeof(pass)); + else getpasswd("パスワード: ", pass, sizeof(pass)); + puts(""); + if (strncmp(lang, "en", 2) == 0) + getpasswd("Password (for verification): ", knin, sizeof(knin)); + else getpasswd("パスワード(確認用): ", knin, sizeof(knin)); + puts(""); + + // パスワードが一致するかどうか確認 + if (strcmp(pass, knin) != 0) { + const char *ero = (strncmp(lang, "en", 2) == 0 ? + "Password does not match. Terminating..." : + "パスワードが一致していません。終了…"); + fprintf(stderr, "%s\n", ero); + return -1; + } + + // パスワードを保存する + gpgme_ctx_t ctx; + gpgme_error_t err; + gpgme_key_t key[2] = {NULL, NULL}; + gpgme_data_t in, out; + FILE *gpgfile; + + // GPGMEライブラリを設置 + setlocale(LC_ALL, ""); + gpgme_check_version(NULL); + gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL)); + + // GPGMEを創作 + err = gpgme_new(&ctx); + if (err) { + const char *ero = (strncmp(lang, "en", 2) == 0 ? + "Failed to generate GPGME" : "GPGMEを創作に失敗"); + fprintf(stderr, "%s: %s\n", ero, gpgme_strerror(err)); + return -1; + } + + // GPGMEは非対話的モードに設定 + err = gpgme_set_pinentry_mode(ctx, GPGME_PINENTRY_MODE_LOOPBACK); + if (err) { + const char *ero = (strncmp(lang, "en", 2) == 0 ? + "Failed to set pinentry mode" : "pinentryモードを設定に失敗"); + fprintf(stderr, "%s: %s\n", ero, gpgme_strerror(err)); + gpgme_release(ctx); + return -1; + } + + // パスワードからデータオブジェクトを創作 + err = gpgme_data_new_from_mem(&in, pass, strlen(pass), 0); + if (err) { + const char *ero = (strncmp(lang, "en", 2) == 0 ? + "Failed to make data object" : "データオブジェクトを創作に失敗"); + fprintf(stderr, "%s: %s\n", ero, gpgme_strerror(err)); + gpgme_release(ctx); + return -1; + } + + gpgme_data_new(&out); + + // 鍵を受け取る + char keypath[256]; + snprintf(keypath, sizeof(keypath), "%s%s", basedir, ".gpg-id"); + + keypath[sizeof(keypath) - 1] = '\0'; + + FILE* keyfile = fopen(keypath, "rb"); + if (keyfile == NULL) { + const char *ero = (strncmp(lang, "en", 2) == 0 ? + "Failed to open .gpg-id file" : ".gpg-idファイルを開くに失敗"); + fprintf(stderr, "%s\n", ero); + return -1; + } + + char *keyid = malloc(256); + if (!fgets(keyid, 256, keyfile)) { + const char *ero = (strncmp(lang, "en", 2) == 0 ? + "Failed to reading key ID" : "鍵IDを読込に失敗"); + fprintf(stderr, "%s\n", ero); + fclose(keyfile); + free(keyid); + return -1; + } + + keyid[strcspn(keyid, "\n")] = 0; + fclose(keyfile); + + err = gpgme_get_key(ctx, keyid, &key[0], 0); + if (err) { + const char *ero = (strncmp(lang, "en", 2) == 0 ? + "Failed to get key" : "鍵Dを受取に失敗"); + fprintf(stderr, "%s: %s\n", ero, gpgme_strerror(err)); + free(keyid); + return -1; + } + + if (key[0] == NULL) { + const char *ero = (strncmp(lang, "en", 2) == 0 ? + "Error: Key is NULL" : "エラー:鍵はNULLです"); + fprintf(stderr, "%s\n", ero); + free(keyid); + return -1; + } + + free(keyid); + + // 暗号化 + err = gpgme_op_encrypt(ctx, &key[0], GPGME_ENCRYPT_ALWAYS_TRUST, in, out); + if (err) { + const char *ero = (strncmp(lang, "en", 2) == 0 ? + "Failed to encrypt" : "暗号化に失敗"); + fprintf(stderr, "%s: %s\n", ero, gpgme_strerror(err)); + cleanup(ctx, key[0], in, out); + return -1; + } + + // 暗号化したファイルを開く + char *gpgpath = malloc(alllen); + if (gpgpath == NULL) { + const char *ero = (strncmp(lang, "en", 2) == 0 ? + "Failed to allocate memory" : "メモリを割当に失敗"); + fprintf(stderr, "%s\n", ero); + cleanup(ctx, key[0], in, out); + return -1; + } + + // ディレクトリを創作 + char dirpath[512]; + snprintf(dirpath, sizeof(dirpath), "%s%s", basedir, file); + + dirpath[sizeof(dirpath) - 1] = '\0'; + + char *lastsla = strrchr(dirpath, '/'); + if (lastsla != NULL) { + *lastsla = '\0'; + if (mkdir_r(dirpath, 0755) != 0) { + const char *ero = (strncmp(lang, "en", 2) == 0 ? + "Failed to create directory" : "ディレクトリを創作に失敗"); + fprintf(stderr, "%s\n", ero); + free(gpgpath); + cleanup(ctx, key[0], in, out); + return -1; + } + } + + snprintf(gpgpath, alllen, "%s%s%s", basedir, file, ext); + + struct stat statbuf; + if (stat(gpgpath, &statbuf) == 0) { + const char *ero = (strncmp(lang, "en", 2) == 0 ? + "Password already exists" : "パスワードは既に存在しています"); + fprintf(stderr, "%s\n", ero); + free(gpgpath); + cleanup(ctx, key[0], in, out); + return -1; + } + + gpgfile = fopen(gpgpath, "wb"); + if (gpgfile == NULL) { + const char *ero = (strncmp(lang, "en", 2) == 0 ? + "Failed to open file." : "ファイルを開くに失敗。"); + fprintf(stderr, "%s\n", ero); + free(gpgpath); + cleanup(ctx, key[0], in, out); + return -1; + } + + // データが保存したかどうか確認 + ssize_t encrypted_data_size = gpgme_data_seek(out, 0, SEEK_END); + if (encrypted_data_size <= 0) { + const char *ero = (strncmp(lang, "en", 2) == 0 ? + "Failed to store the data" : "データを保存に失敗"); + fprintf(stderr, "%s\n", ero); + fclose(gpgfile); + free(gpgpath); + cleanup(ctx, key[0], in, out); + return -1; + } + + // 復号化したパスワードを表示する + gpgme_data_seek(out, 0, SEEK_SET); + + char buffer[512]; + ssize_t read_bytes; + + while ((read_bytes = gpgme_data_read(out, buffer, sizeof(buffer))) > 0) { + if (fwrite(buffer, 1, (size_t)read_bytes, gpgfile) != (size_t)read_bytes) { + const char *ero = (strncmp(lang, "en", 2) == 0 ? + "Failed to write password" : "パスワードを書き込みに失敗"); + fprintf(stderr, "%s\n", ero); + free(gpgpath); + cleanup(ctx, key[0], in, out); + return -1; + } + } + + // 掃除 + fclose(gpgfile); + free(gpgpath); + cleanup(ctx, key[0], in, out); + + const char *msg = (strncmp(lang, "en", 2) == 0 ? + "The password got saved." : "パスワードを保存出来ました。"); + printf("%s\n", msg); + + return 0; +} diff --git a/src/addpass.h b/src/addpass.h new file mode 100644 index 0000000..5bf0b57 --- /dev/null +++ b/src/addpass.h @@ -0,0 +1,13 @@ +#ifndef ADDPASS_H +#define ADDPASS_H + +#include +#include + +#include + +#include "common.h" + +int addpass(char *file); + +#endif diff --git a/src/base32.c b/src/base32.c new file mode 100644 index 0000000..faef952 --- /dev/null +++ b/src/base32.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include + +#include "base32.h" + +int char_to_val(char c) { + const char *base32_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + char *ptr = strchr(base32_alphabet, c); + return ptr ? ptr - base32_alphabet : -1; +} + +char *to_upper(const char *str) { + if (str == NULL) return NULL; + + size_t len = strlen(str); + char *res = (char *)malloc(len + 1); + + if (res == NULL) return NULL; + + for (size_t i = 0; i < len; i++) { + res[i] = toupper(str[i]); + } + + res[len] = '\0'; + + return res; +} + +unsigned char *base32_decode(const char *encoded, size_t *out_len) { + char *encoded_up = to_upper(encoded); + size_t encoded_len = strlen(encoded_up); + size_t padding = 0; + for (int i = encoded_len - 1; i >= 0 && encoded_up[i] == '='; --i) { + ++padding; + } + + *out_len = (encoded_len - padding) * 5 / 8; + if (*out_len == 0) return NULL; + + unsigned char *decoded = malloc(*out_len); + if (!decoded) return NULL; + + int buffer = 0, bits_left = 0, count = 0; + for (size_t i = 0; i < encoded_len - padding; ++i) { + int val = char_to_val(encoded_up[i]); + if (val < 0) { + free(decoded); + return NULL; + } + + buffer <<= 5; + buffer |= val; + bits_left += 5; + + if (bits_left >= 8) { + decoded[count++] = (unsigned char) (buffer >> (bits_left - 8)); + bits_left -= 8; + } + } + + *out_len = count; + return decoded; +} diff --git a/src/base32.h b/src/base32.h new file mode 100644 index 0000000..4023054 --- /dev/null +++ b/src/base32.h @@ -0,0 +1,8 @@ +#ifndef BASE32_H +#define BASE32_H + +#include + +unsigned char *base32_decode(const char *encoded, size_t *out_len); + +#endif diff --git a/src/chkpass.c b/src/chkpass.c new file mode 100644 index 0000000..cc5f93f --- /dev/null +++ b/src/chkpass.c @@ -0,0 +1,184 @@ +#include +#include +#include +#include +#include +#include + +#include "chkpass.h" +#include "showpass.h" +#include "common.h" + +List chkFullpaths; +List chkDispaths; +List dupPasswd; +List dupPath; + +int vulncount = 0; +int weaklencount = 0; +int weakcharcount = 0; +int duppasscount = 0; + +size_t minimumlen = 12; +size_t recommendlen = 64; + +const char *spchar = "!@#$%^&*()-_=+[]{}|;:'\",.<>?/\\`~"; + +void chkLenPass(const char *path, const char *pass, char *lang) { + if (strlen(pass) > 0 && strlen(pass) < minimumlen) { + printf("【E】"); + if (strncmp(lang, "en", 2) == 0) { + printf("The password \"%s\" is too short, minimum length should be " + "%ld characters, recommended is %ld characters.\n", + path, minimumlen, recommendlen); + } else { + printf("パスワード「%s」は短すぎます。最短パスワードの長さは%ld文字ですが、" + "勧めが%ld文字です。\n", + path, minimumlen, recommendlen); + } + + weaklencount++; + vulncount++; + } else if (strlen(pass) >= minimumlen && strlen(pass) < recommendlen) { + printf("【W】"); + if (strncmp(lang, "en", 2) == 0) { + printf("The password \"%s\" is long enough, but for optimal security, " + "%ld characters is recommended.\n", + path, recommendlen); + } else { + printf("パスワード「%s」の長さは十分ですが、最強のセキュリティには%ld文字が" + "勧めします。\n", + path, recommendlen); + } + } +} + +void chkCharPass(const char *path, const char *pass, char *lang) { + int isUpper = 0; + int isLower = 0; + int isDigit = 0; + int isSpecial = 0; + + for (size_t j = 0; j < strlen(pass); j++) { + if (isupper(pass[j])) isUpper = 1; + else if (islower(pass[j])) isLower = 1; + else if (isdigit(pass[j])) isDigit = 1; + else if (strchr(spchar, pass[j])) isSpecial = 1; + } + + if (isUpper == 0 || isLower == 0 || isDigit == 0 || isSpecial == 0) { + printf("【E】"); + if (strncmp(lang, "en", 2) == 0) { + printf("The password \"%s\" is too weak! A strong password contains at least " + "1 uppercase, 1 lowercase, 1 digit, and 1 special character.\n", + path); + } else { + printf("パスワード「%s」は弱すぎます!強いパスワードは最大1大文字、1小文字、" + "1数字、及び1記号の文字が含みます。\n", + path); + } + + weakcharcount++; + vulncount++; + } +} + +void chkDupPass(const char *path, const char *pass, char *lang) { + for (size_t k = 0; k < dupPasswd.size; k++) { + if (strncmp(getElement(&dupPasswd, k), pass, strlen(pass)) == 0) { + printf("【E】"); + if (strncmp(lang, "en", 2) == 0) { + printf("The password \"%s\" is the same as \"%s\". " + "For security, please keep passwords unique!\n", + path, getElement(&dupPath, k)); + } else { + printf("パスワード「%s」は「%s」と一致しています。" + "セキュリティの為、各パスワードはユニークにする様にして下さい!\n", + path, getElement(&dupPath, k)); + } + + duppasscount++; + vulncount++; + } + } + + addElement(&dupPath, path); + addElement(&dupPasswd, pass); +} + +void chkpass(const char *dpath, const char *mode) { + char *lang = getlang(); + + if (strncmp(mode, "all", 3) != 0 && strncmp(mode, "length", 6) != 0 && + strncmp(mode, "strength", 8) != 0 && strncmp(mode, "duplicate", 9) != 0) { + if (strncmp(lang, "en", 2) == 0) + printf("Invalid mode. Possible modes: all, length, strength, duplicate\n"); + else + printf("不正なモード。可能なモード: all(全部)、length(長さ)、" + "strength(強さ)、duplicate(同じパスワードの確認)\n"); + return; + } + + // パスワードをスキャンして + List fpaths; + initList(&fpaths); + initList(&chkFullpaths); + initList(&chkDispaths); + initList(&dupPasswd); + initList(&dupPath); + scanDir(dpath, dpath, &fpaths, &chkFullpaths, &chkDispaths); + + puts(strncmp(lang, "en", 2) == 0 ? + "Checking, please wait for a while..." : "確認中。暫くお待ち下さい・・・"); + + for (size_t i = 0; i < chkDispaths.size; i++) { + const char *pass = showpass(getElement(&chkDispaths, i)); + if (!pass) continue; + if (strncmp(pass, "otpauth://totp/", strlen(pass)) == 0) continue; + + if (strncmp(mode, "all", 3) == 0) { + // 全部を確認する + chkLenPass(getElement(&chkDispaths, i), pass, lang); + chkCharPass(getElement(&chkDispaths, i), pass, lang); + chkDupPass(getElement(&chkDispaths, i), pass, lang); + } else if (strncmp(mode, "length", 6) == 0) { + // 長さの確認 + chkLenPass(getElement(&chkDispaths, i), pass, lang); + } else if (strncmp(mode, "strength", 8) == 0) { + // 強さの確認 + chkCharPass(getElement(&chkDispaths, i), pass, lang); + } else if (strncmp(mode, "duplicate", 9) == 0) { + // 複数回同じパスワードの確認 + chkDupPass(getElement(&chkDispaths, i), pass, lang); + } + } + + if (strncmp(lang, "en", 2) == 0) { + if (strncmp(mode, "all", 3) == 0 || strncmp(mode, "length", 6) == 0) + printf("\nShort password count: %d\n", weaklencount); + if (strncmp(mode, "all", 3) == 0 || strncmp(mode, "strength", 8) == 0) + printf("\nWeak password count: %d\n", weakcharcount); + if (strncmp(mode, "all", 3) == 0 || strncmp(mode, "duplicate", 9) == 0) + printf("\nDuplicate password count: %d\n", duppasscount); + + printf("\nTotal weak passwords: %d\n", vulncount); + if (vulncount > 0) + printf("It's advised to change any of the weak " + "passwords as soon as possible!\n"); + } else { + if (strncmp(mode, "all", 3) == 0 || strncmp(mode, "length", 6) == 0) + printf("\n短いパスワード数: %d\n", weaklencount); + if (strncmp(mode, "all", 3) == 0 || strncmp(mode, "strength", 8) == 0) + printf("\n弱いパスワード数: %d\n", weakcharcount); + if (strncmp(mode, "all", 3) == 0 || strncmp(mode, "duplicate", 9) == 0) + printf("\n同じパスワード数: %d\n", duppasscount); + + printf("\n不安のパスワードの合計: %d\n", vulncount); + if (vulncount > 0) + printf("不安的なパスワードは出来るだけ早く変更する事をお勧めします!\n"); + } + + freeList(&fpaths); + freeList(&dupPath); + freeList(&dupPasswd); +} diff --git a/src/chkpass.h b/src/chkpass.h new file mode 100644 index 0000000..06ba74a --- /dev/null +++ b/src/chkpass.h @@ -0,0 +1,18 @@ +#ifndef CHKPASS_H +#define CHKPASS_H + +#include + +void chkpass(const char *dpath, const char *mode); + +extern int vulncount; +extern int weaklencount; +extern int weakcharcount; +extern int duppasscount; + +extern size_t minimumlen; +extern size_t recommendlen; + +extern const char *spchar; + +#endif diff --git a/src/common.c b/src/common.c new file mode 100644 index 0000000..4253a9d --- /dev/null +++ b/src/common.c @@ -0,0 +1,273 @@ +#include "common.h" + +#include +#include +#include +#include + +#define MAXFINDLEN 1024 + +char *getbasedir(int trailing) { + char *lang = getlang(); + + char *homedir = getenv("HOME"); + if (homedir == NULL) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to get home directory"); + else perror("ホームディレクトリを受取に失敗"); + return NULL; + } + +#if defined(__HAIKU__) + char *basedir = "/config/settings/sp"; + char *slash = "/"; +#elif defined(_WIN32) + char *basedir = "\\AppData\\Local\\076\\sp"; + char *slash = "\\"; +#else + char *basedir = "/.local/share/sp"; + char *slash = "/"; +#endif + + size_t len = strlen(homedir) + strlen(basedir) + strlen(slash) + 4; + char *res = malloc(len); + if (res == NULL) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to allocate memory"); + else perror("メモリの役割に失敗"); + return NULL; + } + if (trailing == 1) + snprintf(res, len, "%s%s%s", homedir, basedir, slash); + else + snprintf(res, len, "%s%s", homedir, basedir); + + return res; +} + +char *getlang() { + char *lang = NULL; + + lang = getenv("SP_LANG"); + if (lang == NULL) lang = "ja"; + + return lang; +} + +int mkdir_r(const char *path, mode_t mode) { + char tmp[256]; + char *p = NULL; + size_t len; + + snprintf(tmp, sizeof(tmp), "%s", path); + + len = strlen(tmp); + if (tmp[len - 1] == '/') { + tmp[len - 1] = 0; // 最後の「/」文字を取り消す + } + + for (p = tmp + 1; *p; p++) { + if (*p == '/') { + *p = 0; // 最後の「/」文字を取り消す + if (mkdir(tmp, mode) != 0 && errno != EEXIST) return -1; + *p = '/'; // また追加 + } + } + + if (mkdir(tmp, mode) != 0 && errno != EEXIST) { + return -1; + } + + return 0; +} + +int tmpcopy(const char *inpath, const char *outpath) { + FILE *src = fopen(inpath, "rb"); + if (!src) { + return -1; + } + + FILE *dst = fopen(outpath, "wb"); + if (!dst) { + fclose(src); + return -1; + } + + size_t n, m; + unsigned char buf[8192]; + do { + n = fread(buf, 1, sizeof buf, src); + if (n) m = fwrite(buf, 1, n, dst); + else m = 0; + } while ((n > 0) && (n == m)); + if (m) return -1; + + fclose(src); + fclose(dst); + + return 0; +} + +#if defined(__linux__) +char *strcasestr(const char *haystack, const char *needle) { + size_t needle_len = strlen(needle); + if (needle_len == 0) { + return (char *)haystack; + } + + while (*haystack) { + if (strncasecmp(haystack, needle, needle_len) == 0) { + return (char *)haystack; + } + haystack++; + } + return NULL; +} +#endif + +void rmext(char *filename) { + char *ext = strrchr(filename, '.'); + if (ext != NULL && strcmp(ext, ".gpg") == 0) { + *ext = '\0'; + } +} + +void scanDir(const char *dpath, const char *rpath, List *fpaths, + List *fullpaths, List *dispaths) { + char *lang = getlang(); + + DIR *dir = opendir(dpath); + if (!dir) { + if (strncmp(lang, "en", 2) == 0) + perror("Could not open directory"); + else perror("ディレクトリを開けられません"); + exit(EXIT_FAILURE); + } + + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + const char *name = entry->d_name; + if (strncmp(name, ".", 1) == 0 || strncmp(name, "..", 2) == 0) continue; + + char fpath[MAXFINDLEN]; + snprintf(fpath, sizeof(fpath), "%s/%s", dpath, name); + + struct stat s; + if (stat(fpath, &s) != 0) { + closedir(dir); + return; + } + + if (S_ISDIR(s.st_mode)) { + scanDir(fpath, rpath, fpaths, fullpaths, dispaths); + } else if (strstr(name, ".gpg") != NULL) { + const char *rel = fpath + strlen(rpath) + 1; + addElement(fpaths, rel); + addElement(fullpaths, fpath); + char *disname = strdup(rel); + rmext(disname); + addElement(dispaths, disname); + + free(disname); + } + } + + closedir(dir); +} + +void initList(List *list) { + list->head = NULL; + list->tail = NULL; + list->size = 0; +} + +void addElement(List *list, const char *data) { + Node *newNode = (Node *)malloc(sizeof(Node)); + if (!newNode) { + return; + } + + newNode->data = strdup(data); + if (!newNode->data) { + free(newNode); + return; + } + newNode->next = NULL; + + if (list->tail) { + list->tail->next = newNode; + } else { + list->head = newNode; + } + + list->tail = newNode; + list->size++; +} + +char *getElement(List *list, size_t idx) { + if (idx >= list->size) return NULL; + + Node *current = list->head; + for (size_t i = 0; i < idx; i++) + current = current->next; + + return current->data; +} + +void rmElement(List *list, size_t idx) { + if (idx >= list->size) return; + + Node *current = list->head; + Node *previous = NULL; + + if (idx == 0) { + list->head = current->next; + if (list->size == 1) list->tail = NULL; + } else { + for (size_t i = 0; i < idx; i++) { + previous = current; + current = current->next; + } + previous->next = current->next; + if (idx == list->size - 1) { + list->tail = previous; + } + } + + free(current->data); + free(current); + list->size--; +} + +void freeList(List *list) { + Node *current = list->head; + + while (current) { + Node *next = current->next; + free(current->data); + free(current); + current = next; + } + + list->head = NULL; + list->tail = NULL; + list->size = 0; +} + +void handle_sigint(int sig) { + (void)sig; + + if (getenv("WAYLAND_DISPLAY") != NULL) { + system("echo -n \"\" | wl-copy"); + } else { + system("echo -n \"\" | xclip -selection clipboard"); + } + + if (strncmp(getlang(), "en", 2) == 0) { + printf("\nClipboard cleared and program aborted.\n"); + } else { + printf("\nクリップボードをクリアし、プログラムが中止されました。\n"); + } + + exit(0); +} diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..2e06fd5 --- /dev/null +++ b/src/common.h @@ -0,0 +1,40 @@ +#ifndef COMMON_H +#define COMMON_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +typedef struct Node { + char *data; + struct Node *next; +} Node; + +typedef struct { + Node *head; + Node *tail; + size_t size; +} List; + +char *getbasedir(int trailing); +char *getlang(); +int mkdir_r(const char *path, mode_t mode); +int tmpcopy(const char *inpath, const char *outpath); +void scanDir(const char *dpath, const char *rpath, List *fpaths, + List *fullpaths, List *dispaths); +void handle_sigint(int sig); + +// C言語のvector +void initList(List *list); +void addElement(List *list, const char *data); +char *getElement(List *list, size_t idx); +void rmElement(List *list, size_t idx); +void freeList(List *list); + +#endif diff --git a/src/delpass.c b/src/delpass.c new file mode 100644 index 0000000..91f5d69 --- /dev/null +++ b/src/delpass.c @@ -0,0 +1,162 @@ +#include +#include + +#include "common.h" +#include "delpass.h" + +int delcnt(const char *str, char delimiter) { + int count = 0; + while (*str) { + if (*str == delimiter) { + count++; + } + str++; + } + + return count; +} + +char **explode(const char *str, char delimiter, int *numtokens) { + int count = delcnt(str, delimiter) + 1; + *numtokens = count; + + char **tokens = malloc(count * sizeof(char *)); + if (tokens == NULL) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + char *strcopy = strdup(str); + if (strcopy == NULL) { + perror("strdup"); + exit(EXIT_FAILURE); + } + + char *token = strtok(strcopy, &delimiter); + int i = 0; + while (token != NULL) { + tokens[i] = strdup(token); + if (tokens[i] == NULL) { + perror("strdup"); + exit(EXIT_FAILURE); + } + i++; + token = strtok(NULL, &delimiter); + } + + free(strcopy); + + return tokens; +} + +void freetokens(char **tokens, int numtokens) { + for (int i = 0; i < numtokens; i++) { + free(tokens[i]); + } + + free(tokens); +} + +int delpass(char *file, int force) { + char *lang = getlang(); + + // パスを準備 + char pwfile[512]; + char *basedir = getbasedir(1); + char *ext = ".gpg"; + + int alllen = snprintf(NULL, 0, "%s%s%s", basedir, file, ext) + 1; + char *gpgpathchk = malloc(alllen); + if (gpgpathchk == NULL) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to allocate memory"); + else perror("メモリを割当に失敗"); + return -1; + } + + // ファイルが既に存在するかどうか確認 + snprintf(gpgpathchk, alllen, "%s%s%s", basedir, file, ext); + if (access(gpgpathchk, F_OK) != 0) { + if (strncmp(lang, "en", 2) == 0) + perror("Password does not exist"); + else perror("パスワードが存在しません"); + free(gpgpathchk); + return -1; + } + free(gpgpathchk); + + int needed = snprintf( pwfile, sizeof(pwfile), "%s%s%s", basedir, file, ext); + if (needed >= (int)sizeof(pwfile)) { + if (strncmp(lang, "en", 2) == 0) + perror("Error: Path is too long"); + else perror("エラー:パスが長すぎる"); + return -1; + } + + // 削除を確認する + if (force == 0) { // パスワードの変更の場合、確認は不要 + if (strncmp(lang, "en", 2) == 0) + printf("Are you sure you want to delete the password '%s'? (y/N): ", file); + printf("パスワード「%s」を本当に削除する事が宜しいでしょうか? (y/N): ", file); + int confirm = getchar(); + if (confirm != 'y' && confirm != 'Y') { + if (strncmp(lang, "en", 2) == 0) puts("Not deleted"); + else puts("削除しませんでした"); + return -1; + } + + int ch; + while ((ch = getchar()) != '\n' && ch != EOF); + } + + if (unlink(pwfile) == -1) { + if (strncmp(lang, "en", 2) == 0) + perror("Password cannot be deleted"); + else perror("パスワードを削除出来ませんでした"); + return -1; + } + + // 空のディレクトリの場合 + int numt; + char **tokens = explode(file, '/', &numt); + + char passpath[1024]; + snprintf(passpath, sizeof(passpath), "%s%s", basedir, tokens[0]); + char *ls = strrchr(basedir, '/'); + if (ls != NULL) { + *ls = '\0'; + } + + for (int i = 1; i < numt; i++) { + if (i == (numt-1)) continue; + strncat(passpath, "/", sizeof(passpath) - strlen(passpath) - 1); + strncat(passpath, tokens[i], sizeof(passpath) - strlen(passpath) - 1); + } + + for (int i = numt - 1; i >= 0; i--) { + // ~/.local/share/sp を削除したら危険 + if (strncmp(passpath, basedir, sizeof(passpath)) == 0) { + break; + } + + // ディレクトリが空じゃない場合、削除を止める + if (rmdir(passpath) == -1) { + break; + } + + char *last_slash = strrchr(passpath, '/'); + if (last_slash != NULL) { + *last_slash = '\0'; + } + } + + freetokens(tokens, numt); + + // sp -e の場合、「パスワードを削除しました」って要らない + if (force == 1) return 0; + + if (strncmp(lang, "en", 2) == 0) puts("The password got deleted"); + else puts("パスワードを削除しました"); + + return 0; +} diff --git a/src/delpass.h b/src/delpass.h new file mode 100644 index 0000000..8189816 --- /dev/null +++ b/src/delpass.h @@ -0,0 +1,6 @@ +#ifndef DELPASS_H +#define DELPASS_H + +int delpass(char *file, int force); + +#endif diff --git a/src/findpass.c b/src/findpass.c new file mode 100644 index 0000000..698f6c0 --- /dev/null +++ b/src/findpass.c @@ -0,0 +1,25 @@ +#include "common.h" +#include "findpass.h" + +#include +#include +#include + +List findFullpaths; +List findDispaths; + +void findpass(const char *dpath, const char *txt) { + List fpaths; + initList(&fpaths); + initList(&findFullpaths); + initList(&findDispaths); + scanDir(dpath, dpath, &fpaths, &findFullpaths, &findDispaths); + + for (size_t i = 0; i < findDispaths.size; i++) { + if (strcasestr(getElement(&findDispaths, i), txt) != NULL) { + printf("%s\n", getElement(&findDispaths, i)); + } + } + + freeList(&fpaths); +} diff --git a/src/findpass.h b/src/findpass.h new file mode 100644 index 0000000..338cc10 --- /dev/null +++ b/src/findpass.h @@ -0,0 +1,8 @@ +#ifndef FINDPASS_H +#define FINDPASS_H + +#include "common.h" + +void findpass(const char *dpath, const char *txt); + +#endif diff --git a/src/genpass.c b/src/genpass.c new file mode 100644 index 0000000..bbd4cab --- /dev/null +++ b/src/genpass.c @@ -0,0 +1,32 @@ +#include "common.h" +#include "genpass.h" + +void genpass(int count, bool issecure) { + char *lang = getlang(); + + const char *charset_risky = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + const char *charset_secure = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!\"#$%&'()=~-^\\|_@`[{]};:+*<>,./?"; + const char *charset = issecure ? charset_secure : charset_risky; + + FILE *fp = fopen("/dev/random", "rb"); + if (fp == NULL) { + if (strncmp(lang, "en", 2) == 0) + perror("Could not open /dev/random"); + else perror("/dev/randomを開けられませんでした"); + exit(EXIT_FAILURE); + } + + char password[count + 1]; + for (int i = 0; i < count; i++) { + unsigned char key; + fread(&key, sizeof(key), 1, fp); + password[i] = charset[key % strlen(charset)]; + } + + password[count] = '\0'; + fclose(fp); + + printf("%s\n", password); +} diff --git a/src/genpass.h b/src/genpass.h new file mode 100644 index 0000000..d7f1d0d --- /dev/null +++ b/src/genpass.h @@ -0,0 +1,8 @@ +#ifndef GENPASS_H +#define GENPASS_H + +#include + +void genpass(int count, bool issecure); + +#endif diff --git a/src/initpass.c b/src/initpass.c new file mode 100644 index 0000000..c73cfb7 --- /dev/null +++ b/src/initpass.c @@ -0,0 +1,48 @@ +#include "common.h" +#include "initpass.h" + +void initpass(char *gpgid) { + char *lang = getlang(); + + char *basedir = getbasedir(1); + + if (mkdir_r(basedir, 0755) != 0 && errno != EEXIST) { + if (strncmp(lang, "en", 2) == 0) + fprintf(stderr, "Failed to create directory.\n"); + else fprintf(stderr, "ディレクトリを作成に失敗。\n"); + return; + } + + char gpgidpath[512]; + snprintf(gpgidpath, sizeof(gpgidpath), "%s/.gpg-id", basedir); + + struct stat statbuf; + if (stat(gpgidpath, &statbuf) == 0) { + if (strncmp(lang, "en", 2) == 0) + fprintf(stderr, ".gpg-id file already exists.\n"); + else fprintf(stderr, ".gpg-idファイルは既に存在します。\n"); + return; + } + + FILE *gpgidfile = fopen(gpgidpath, "w"); + if (gpgidfile == NULL) { + if (strncmp(lang, "en", 2) == 0) + fprintf(stderr, "Failed to write .gpg-id file.\n"); + else fprintf(stderr, ".gpg-idファイルを書き込めません。\n"); + return; + } + + if (fprintf(gpgidfile, "%s\n", gpgid) < 0) { + if (strncmp(lang, "en", 2) == 0) + fprintf(stderr, "Failed to write .gpg-id file.\n"); + else fprintf(stderr, ".gpg-idファイルへの書き込みに失敗しました。\n"); + fclose(gpgidfile); + return; + } + + fclose(gpgidfile); + + if (strncmp(lang, "en", 2) == 0) + puts("Initialization completed."); + else puts("初期設定に完了しました。"); +} diff --git a/src/initpass.h b/src/initpass.h new file mode 100644 index 0000000..38cd2d5 --- /dev/null +++ b/src/initpass.h @@ -0,0 +1,6 @@ +#ifndef INITPASS_H +#define INITPASS_H + +void initpass(char* gpgid); + +#endif diff --git a/src/listpass.c b/src/listpass.c new file mode 100644 index 0000000..cc2e08f --- /dev/null +++ b/src/listpass.c @@ -0,0 +1,56 @@ +#include + +#include "common.h" +#include "listpass.h" + +void listpass(char *basePath, int level) { + char *lang = getlang(); + + struct dirent *entry; + DIR* dir = opendir(basePath); + if (!dir) { + if (strncmp(lang, "en", 2) == 0) + perror("Could not open directory"); + else perror("ディレクトリを開けられません"); + return; + } + + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + char path[1000]; + int needed = snprintf(path, sizeof(path), "%s/%s", basePath, entry->d_name); + if (needed >= (int)sizeof(path) || needed < 0) { + if (strncmp(lang, "en", 2) == 0) + perror("Error: Path is too long, or failed to get lenth"); + else perror("エラー:パスが長すぎる、又は長さを受取に失敗"); + continue; + } + + struct stat statbuf; + if (stat(path, &statbuf) == -1) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to read file status"); + else perror("ファイル状況を読込に失敗"); + continue; + } + + for (int i = 0; i < level; i++) { + printf(" "); + } + + if (S_ISDIR(statbuf.st_mode)) { + printf("|-- %s\n", entry->d_name); + listpass(path, level + 1); + } else if (S_ISREG(statbuf.st_mode)) { + char *filename = entry->d_name; + char *ext = strstr(filename, ".gpg"); + if (ext) *ext = '\0'; + printf("|-- %s\n", filename); + } + } + + closedir(dir); +} diff --git a/src/listpass.h b/src/listpass.h new file mode 100644 index 0000000..8215396 --- /dev/null +++ b/src/listpass.h @@ -0,0 +1,6 @@ +#ifndef LISTPASS_H +#define LISTPASS_H + +void listpass(char *basePath, int level); + +#endif diff --git a/src/otppass.c b/src/otppass.c new file mode 100644 index 0000000..1124fc1 --- /dev/null +++ b/src/otppass.c @@ -0,0 +1,246 @@ +#include +#include + +#include + +#if defined(__APPLE) +#include +#define htobe64(x) OSSwapHostToBigInt64(x) +#endif + +#include "base32.h" +#include "common.h" +#include "otppass.h" + +unsigned char *extract_secret(const char *otpauth_url, size_t *decoded_len) { + char *lang = getlang(); + + const char *secret_start = strstr(otpauth_url, "secret="); + if (!secret_start) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to find secret in the OTPAuth URL"); + else perror("OTPAuth URLの中に、シークレットを見つけられませんでした"); + return NULL; + } + secret_start += 7; + + const char *secret_end = strchr(secret_start, '&'); + if (!secret_end) { + if (secret_start[0] != '\0') { + secret_end = secret_start + strlen(secret_start); + } else { + secret_end = secret_start; + } + } + + if (secret_end < secret_start) { + if (strncmp(lang, "en", 2) == 0) + perror("Incorrect secret range"); + else perror("不正なシークレットの距離"); + return NULL; + } + + size_t secret_len = secret_end - secret_start; + char *secret_encoded = (char *)malloc(secret_len + 1); + + if (secret_encoded == NULL) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to allocate memory"); + else perror("メモリの役割に失敗"); + return NULL; + } + + strncpy(secret_encoded, secret_start, secret_len); + secret_encoded[secret_len] = '\0'; + + unsigned char *secret_decoded = base32_decode(secret_encoded, decoded_len); + free(secret_encoded); + + if (!secret_decoded) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to decrypt BASE32"); + else perror("BASE32の復号化に失敗"); + return NULL; + } + + return secret_decoded; +} + +#if defined(__HAIKU__) +uint64_t htobe64(uint64_t counter) { + uint64_t res = 0; + uint8_t *dest = (uint8_t *)&res; + uint8_t *src = (uint8_t *)&counter; + + dest[0] = src[7]; + dest[1] = src[6]; + dest[2] = src[5]; + dest[3] = src[4]; + dest[4] = src[3]; + dest[5] = src[2]; + dest[6] = src[1]; + dest[7] = src[0]; + + return res; +} +#endif + +uint32_t generate_totp(const char *secret, uint64_t counter) { + counter = htobe64(counter); + + unsigned char hash[SHA_DIGEST_LENGTH]; + HMAC( + EVP_sha1(), + secret, + strlen(secret), + (unsigned char *)&counter, + sizeof(counter), + hash, + NULL + ); + + int offset = hash[SHA_DIGEST_LENGTH - 1] & 0x0F; + uint32_t truncated_hash = + (hash[offset] & 0x7F) << 24 | + (hash[offset + 1] & 0xFF) << 16 | + (hash[offset + 2] & 0xFF) << 8 | + (hash[offset + 3] & 0xFF); + + return truncated_hash % 1000000; +} + +void otppass(char *file, int isCopy, int copyTimeout) { + if (isCopy == 1 && copyTimeout > 300) copyTimeout = 300; + char *lang = getlang(); + + int isGay = (getenv("WAYLAND_DISPLAY") != NULL); + + // Xセッションではない場合(例えば、SSH、TTY等)、otppass()を実行して + if (isCopy == 1 && getenv("DISPLAY") == NULL && getenv("WAYLAND_DISPLAY") == NULL) { + if (strncmp(lang, "en", 2) == 0) + puts("There is no X or Wayland session, so running 'sp -o'."); + else puts("X又はWaylandセッションではありませんので、「sp -o」を実行します。"); + otppass(file, 0, 0); + return; + } + + // CTRL + Cを押す場合 + if (isCopy) { + signal(SIGINT, handle_sigint); + } + + gpgme_ctx_t ctx; + gpgme_error_t err; + gpgme_data_t in, out; + size_t secret_len; + + gpgme_check_version(NULL); + err = gpgme_new(&ctx); + if (err) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to generate the GPG"); + else perror("GPGを創作に失敗"); + exit(1); + } + + err = gpgme_data_new_from_file(&in, file, 1); + if (err) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to read the GPG file"); + else perror("GPGファイルを読込に失敗"); + gpgme_release(ctx); + exit(1); + } + + err = gpgme_data_new(&out); + if (err) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to read the GPG data"); + else perror("GPGデータを読込に失敗"); + gpgme_release(ctx); + gpgme_data_release(in); + exit(1); + } + + err = gpgme_op_decrypt(ctx, in, out); + if (err) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to decrypt the GPG"); + else perror("GPGを復号化に失敗"); + gpgme_release(ctx); + gpgme_data_release(in); + exit(1); + } + + char *secret = gpgme_data_release_and_get_mem(out, &secret_len); + if (!secret) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to get the GPG"); + else perror("GPGを受取に失敗"); + gpgme_data_release(in); + gpgme_release(ctx); + exit(1); + } + + secret[secret_len] = '\0'; + + size_t decoded_len; + unsigned char *secret_decoded = extract_secret(secret, &decoded_len); + if (!secret_decoded) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to decode or export secret"); + else perror("シークレットの抽出又はデコードに失敗しました"); + gpgme_data_release(in); + gpgme_release(ctx); + gpgme_free(secret); + exit(1); + } + + secret_decoded[decoded_len] = '\0'; + + time_t current_time = time(NULL); + uint64_t counter = current_time / 30; + uint32_t otp = generate_totp((const char *)secret_decoded, counter); + + gpgme_data_release(in); + gpgme_release(ctx); + gpgme_free(secret); + free(secret_decoded); + + if (isCopy) { + char cmd[64]; + if (isGay) snprintf(cmd, sizeof(cmd), "echo -n %06d | wl-copy", otp); + else snprintf(cmd, sizeof(cmd), "echo -n %06d | xclip -selection clipboard", otp); + + int ret = system(cmd); + if (ret != 0) { + char *ero = (strncmp(lang, "en", 2) == 0 ? + "Failed to copy OTP." : "ワンタイムパスワードをコピーに失敗。"); + fprintf(stderr, "%s\n", ero); + } + + // 何(デフォルトは30)秒後、クリップボードから削除する + if (strncmp(lang, "en", 2) == 0) + printf( + "%s\n%s%d%s\n", + "Added the one time password to the clipboard.", + "After ", + copyTimeout, + " seconds it'll be deleted from the clipboard." + ); + else + printf( + "%s\n%d%s\n", + "ワンタイムパスワードをクリップボードに追加しました。", + copyTimeout, + "秒後はクリップボードから取り消されます。" + ); + + sleep(copyTimeout); + + if (isGay) system("echo -n \"\" | wl-copy"); + else system("echo -n \"\" | xclip -selection clipboard"); + } else { + printf("%06d\n", otp); + } +} diff --git a/src/otppass.h b/src/otppass.h new file mode 100644 index 0000000..db96da9 --- /dev/null +++ b/src/otppass.h @@ -0,0 +1,6 @@ +#ifndef OTPPASS_H +#define OTPPASS_H + +void otppass(char* file, int isCopy, int copyTimeout); + +#endif diff --git a/src/showpass.c b/src/showpass.c new file mode 100644 index 0000000..89a4173 --- /dev/null +++ b/src/showpass.c @@ -0,0 +1,148 @@ +#include + +#include "common.h" +#include "showpass.h" + +void clean_up( + gpgme_ctx_t ctx, + gpgme_data_t in, + gpgme_data_t out, + FILE *gpgfile, + char *gpgpath +) { + if (gpgfile) fclose(gpgfile); + if (gpgpath) free(gpgpath); + gpgme_data_release(in); + gpgme_data_release(out); + gpgme_release(ctx); +} + +const char *showpass(char *file) { + char *lang = getlang(); + + gpgme_ctx_t ctx; + gpgme_error_t err; + gpgme_data_t in = NULL, out = NULL; + char *gpgpath = NULL; + FILE *gpgfile = NULL; + + // GPGMEライブラリを設置 + setlocale(LC_ALL, ""); + gpgme_check_version(NULL); + gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL)); + + // GPGMEを創作 + err = gpgme_new(&ctx); + if (err) { + if (strncmp(lang, "en", 2) == 0) + fprintf(stderr, "Failed to generate GPGME: %s\n", gpgme_strerror(err)); + else fprintf(stderr, "GPGMEを創作に失敗:%s\n", gpgme_strerror(err)); + return NULL; + } + + // OpenPGPプロトコールを設定 + gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP); + if (err) { + if (strncmp(lang, "en", 2) == 0) + fprintf(stderr, "Failed to set OpenPGP protocol: %s\n", gpgme_strerror(err)); + else fprintf(stderr, "OpenPGP付トロコールの設置に失敗:%s\n", gpgme_strerror(err)); + clean_up(ctx, in, out, gpgfile, gpgpath); + return NULL; + } + + // 暗号化したタイルを開く + char *basedir = getbasedir(1); + char *ext = ".gpg"; + int alllen = snprintf(NULL, 0, "%s%s%s", basedir, file, ext) + 1; + gpgpath = malloc(alllen); + if (gpgpath == NULL) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to allocate memeory"); + else perror("メモリを割当に失敗"); + return NULL; + } + + snprintf(gpgpath, alllen, "%s%s%s", basedir, file, ext); + + gpgfile = fopen(gpgpath, "rb"); + if (gpgfile == NULL) { + if (strncmp(lang, "en", 2) == 0) { + perror("Failed to open file"); + } else { + perror("ファイルを開くに失敗"); + } + free(gpgpath); + return NULL; + } + + // ファイルからinデータオブジェクトを創作 + if (gpgme_data_new_from_stream(&in, gpgfile) != GPG_ERR_NO_ERROR) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to generate the GPGME data object"); + else perror("GPGMEデータオブジェクトを創作に失敗"); + clean_up(ctx, in, out, gpgfile, gpgpath); + return NULL; + } + + // outデータオブジェクトを創作 + if (gpgme_data_new(&out) != GPG_ERR_NO_ERROR) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to generate the GPGME data object"); + else perror("GPGMEデータオブジェクトを創作に失敗"); + clean_up(ctx, in, out, gpgfile, gpgpath); + return NULL; + } + + // データオブジェクトを創作 + gpgme_data_new(&out); + + // 復号化して + err = gpgme_op_decrypt(ctx, in, out); + if (err) { + if (strncmp(lang, "en", 2) == 0) + fprintf(stderr, "Failed to decrypt: %s (source: %s)\n", + gpgme_strerror(err), gpgme_strsource(err)); + else fprintf(stderr, "復号化に失敗: %s (元: %s)\n", + gpgme_strerror(err), gpgme_strsource(err)); + + // 掃除 + clean_up(ctx, in, out, gpgfile, gpgpath); + return NULL; + } + + // 復号化したパスワードを表示する + gpgme_data_seek(out, 0, SEEK_SET); + size_t bufsize = 512; + size_t totsize = 0; + char buffer[512]; + char *res = malloc(bufsize); + if (res == NULL) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to allocate memory"); + else perror("メモリを役割に失敗"); + clean_up(ctx, in, out, gpgfile, gpgpath); + return NULL; + } + + ssize_t read_bytes; + + while ((read_bytes = gpgme_data_read(out, buffer, sizeof(buffer) - 1)) > 0) { + if (totsize + read_bytes >= bufsize) { + bufsize *= 2; + res = realloc(res, bufsize); + if (res == NULL) { + perror("Failed to reallocate memory"); + clean_up(ctx, in, out, gpgfile, gpgpath); + return NULL; + } + } + memcpy(res + totsize, buffer, read_bytes); + totsize += read_bytes; + } + + res[totsize] = '\0'; + + // 掃除 + clean_up(ctx, in, out, gpgfile, gpgpath); + return res; +} diff --git a/src/showpass.h b/src/showpass.h new file mode 100644 index 0000000..40f317f --- /dev/null +++ b/src/showpass.h @@ -0,0 +1,6 @@ +#ifndef SHOWPASS_H +#define SHOWPASS_H + +const char *showpass(char *file); + +#endif diff --git a/src/vulnpass.c b/src/vulnpass.c new file mode 100644 index 0000000..0478092 --- /dev/null +++ b/src/vulnpass.c @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vulnpass.h" +#include "showpass.h" +#include "common.h" + +List vulnFullpaths; +List vulnDispaths; + +void vulnpass(const char *dpath) { + char *lang = getlang(); + + // pwndサーバに接続 + int sock; + struct sockaddr_in srv; + struct addrinfo hints, *addr; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + int status = getaddrinfo("076.moe", NULL, &hints, &addr); + if (status != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status)); + return; + } + + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == -1) { + const char *ero = strncmp(lang, "en", 2) == 0 ? + "Failed to create socket" : "ソケットを作成に失敗"; + perror(ero); + return; + } + + srv.sin_addr = ((struct sockaddr_in *)(addr->ai_addr))->sin_addr; + srv.sin_family = AF_INET; + srv.sin_port = htons(9951); + + freeaddrinfo(addr); + + if (connect(sock, (struct sockaddr *)&srv, sizeof(srv)) < 0) { + const char *ero = strncmp(lang, "en", 2) == 0 ? "Failed to connect" : "接続に失敗"; + perror(ero); + close(sock); + return; + } + + // パスワードをスキャンして + List fpaths; + initList(&fpaths); + initList(&vulnFullpaths); + initList(&vulnDispaths); + scanDir(dpath, dpath, &fpaths, &vulnFullpaths, &vulnDispaths); + int vulncount = 0; + + puts(strncmp(lang, "en", 2) == 0 ? + "Checking, please wait for a while..." : "確認中。暫くお待ち下さい・・・"); + + for (size_t i = 0; i < vulnDispaths.size; i++) { + const char *pass = showpass(getElement(&vulnDispaths, i)); + if (!pass) continue; + char res[256]; + int reslen = 0; + + if (send(sock, pass, strlen(pass), 0) < 0) { + const char *ero = strncmp(lang, "en", 2) == 0 ? + "Failed to send" : "送信に失敗"; + perror(ero); + close(sock); + freeList(&fpaths); + return; + } + + reslen = recv(sock, res, sizeof(res) -1, 0); + if (reslen < 0) { + const char *ero = strncmp(lang, "en", 2) == 0 ? + "Failed to retreive" : "受取に失敗"; + perror(ero); + close(sock); + freeList(&fpaths); + return; + } + + res[reslen] = '\0'; + + if (strncmp(res, "0", 1) != 0) { + printf("【!】"); + if (strncmp(lang, "en", 2) == 0) { + printf("The password \"%s\" has been breached %d times.\n", + getElement(&vulnDispaths, i), atoi(res)); + } else { + printf("パスワード「%s」は%d回に漏洩されました。\n", + getElement(&vulnDispaths, i), atoi(res)); + } + + vulncount++; + } + + close(sock); + + // 再接続 + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == -1) { + const char *ero = strncmp(lang, "en", 2) == 0 ? + "Failed to create socket" : "ソケットを作成に失敗"; + perror(ero); + return; + } + + if (connect(sock, (struct sockaddr *)&srv, sizeof(srv)) < 0) { + const char *ero = strncmp(lang, "en", 2) == 0 ? + "Failed to reconnect" : "再接続に失敗"; + perror(ero); + close(sock); + return; + } + } + + if (strncmp(lang, "en", 2) == 0) { + printf("\nTotal breached: %d\n", vulncount); + if (vulncount > 0) + printf("It's advised to change any of the breached " + "passwords as soon as possible!\n"); + } else { + printf("\n漏洩したパスワードの合計: %d\n", vulncount); + if (vulncount > 0) + printf("漏洩したパスワードは出来るだけ早く変更する事をお勧めします!\n"); + } + + freeList(&fpaths); +} diff --git a/src/vulnpass.h b/src/vulnpass.h new file mode 100644 index 0000000..f87f2e6 --- /dev/null +++ b/src/vulnpass.h @@ -0,0 +1,6 @@ +#ifndef VULNPASS_H +#define VULNPASS_H + +void vulnpass(const char *dpath); + +#endif diff --git a/src/yankpass.c b/src/yankpass.c new file mode 100644 index 0000000..c1b5435 --- /dev/null +++ b/src/yankpass.c @@ -0,0 +1,154 @@ +#include +#include + +#include "common.h" +#include "yankpass.h" +#include "showpass.h" + +void yankpass(char *file, int copyTimeout) { + if (copyTimeout > 300) copyTimeout = 300; + char *lang = getlang(); + + int isGay = (getenv("WAYLAND_DISPLAY") != NULL); + + // Xセッションではない場合(例えば、SSH、TTY等)、showpass()を実行して + if (getenv("DISPLAY") == NULL && getenv("WAYLAND_DISPLAY") == NULL) { + if (strncmp(lang, "en", 2) == 0) + puts("There is no X or Wayland session, so running 'sp -s'."); + else puts("X又はWaylandセッションではありませんので、「sp -s」を実行します。"); + showpass(file); + return; + } + + gpgme_ctx_t ctx; + gpgme_error_t err; + gpgme_data_t in, out; + FILE *gpgfile; + + // GPGMEライブラリを設置 + setlocale(LC_ALL, ""); + gpgme_check_version(NULL); + gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL)); + + // GPGMEを創作 + err = gpgme_new(&ctx); + if (err) { + if (strncmp(lang, "en", 2) == 0) + fprintf(stderr, "Failed to generate GPGME: %s\n", gpgme_strerror(err)); + else fprintf(stderr, "GPGMEを創作に失敗:%s\n", gpgme_strerror(err)); + return; + } + + // CTRL + Cを押す場合 + signal(SIGINT, handle_sigint); + + // OpenPGPプロトコールを設定 + gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP); + + // 暗号化したタイルを開く + char *basedir = getbasedir(1); + char* ext = ".gpg"; + int alllen = snprintf(NULL, 0, "%s%s%s", basedir, file, ext) + 1; + char* gpgpath = malloc(alllen); + if (gpgpath == NULL) { + if (strncmp(lang, "en", 2) == 0) + perror("Failed to allocate memory"); + else perror("メモリを割当に失敗"); + return; + } + + snprintf(gpgpath, alllen, "%s%s%s", basedir, file, ext); + gpgfile = fopen(gpgpath, "rb"); + if (gpgfile == NULL) { + if (strncmp(lang, "en", 2) == 0) { + perror("Failed to open the file"); + fprintf(stderr, "Failed path: %s\n", gpgpath); + } else { + perror("ファイルを開くに失敗"); + fprintf(stderr, "失敗したパス: %s\n", gpgpath); + } + free(gpgpath); + return; + } + gpgme_data_new_from_stream(&in, gpgfile); + + // データオブジェクトを創作 + gpgme_data_new(&out); + + // 復号化して + err = gpgme_op_decrypt(ctx, in, out); + if (err) { + if (strncmp(lang, "en", 2) == 0) + fprintf(stderr, "Failed to decrypt: %s\n", gpgme_strerror(err)); + else fprintf(stderr, "復号化に失敗: %s\n", gpgme_strerror(err)); + + // 掃除 + fclose(gpgfile); + free(gpgpath); + gpgme_data_release(in); + gpgme_data_release(out); + gpgme_release(ctx); + return; + } + + // xclip又はwl-copyを準備して + char *cmd; + if (isGay) cmd = "wl-copy"; + else cmd = "xclip -selection clipboard"; + + FILE *pipe = popen(cmd, "w"); + if (pipe == NULL) { + // 掃除 + fclose(gpgfile); + free(gpgpath); + gpgme_data_release(in); + gpgme_data_release(out); + gpgme_release(ctx); + if (strncmp(lang, "en", 2) == 0) + perror("Could not find a clipboard"); + else perror("クリップボードを見つけられませんでした"); + return; + } + + // 復号化したパスワードを表示する + gpgme_data_seek(out, 0, SEEK_SET); + + char buffer[512]; + ssize_t read_bytes; + + while ((read_bytes = gpgme_data_read(out, buffer, sizeof(buffer))) > 0) { + if (buffer[read_bytes - 1] == '\n') { + read_bytes--; + } + fwrite(buffer, 1, read_bytes, pipe); + } + + pclose(pipe); + + // 何(デフォルトは45)秒後、クリップボードから削除する + if (strncmp(lang, "en", 2) == 0) + printf( + "%s\n%s%d%s\n", + "Added password to the clipboard.", + "After ", + copyTimeout, + " seconds it'll be deleted from the clipboard." + ); + else + printf( + "%s\n%d%s\n", + "パスワードをクリップボードに追加しました。", + copyTimeout, + "秒後はクリップボードから取り消されます。" + ); + sleep(copyTimeout); + if (isGay) system("echo -n \"\" | wl-copy"); + else system("echo -n \"\" | xclip -selection clipboard"); + + // 掃除 + fclose(gpgfile); + free(gpgpath); + gpgme_data_release(in); + gpgme_data_release(out); + gpgme_release(ctx); +} diff --git a/src/yankpass.h b/src/yankpass.h new file mode 100644 index 0000000..58e447f --- /dev/null +++ b/src/yankpass.h @@ -0,0 +1,6 @@ +#ifndef YANKPASS_H +#define YANKPASS_H + +void yankpass(char *file, int copyTimeout); + +#endif