summaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAge
...
* modules/pam_wheel: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | * modules/pam_wheel/pam_wheel.c: Include "pam_inline.h". (_pam_parse): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_unix: use pam_str_skip_prefix and pam_str_skip_prefix_lenDmitry V. Levin2020-03-19
| | | | | | | | | | | | | * modules/pam_unix/passverify.c: Include "pam_inline.h". (verify_pwd_hash): Use pam_str_skip_prefix instead of ugly strncmp invocations. * modules/pam_unix/support.c: Include "pam_inline.h". (_set_ctrl): Use pam_str_skip_prefix_len instead of hardcoding string lengths. * modules/pam_unix/md5_crypt.c: Include "pam_inline.h". (crypt_md5): Use pam_str_skip_prefix_len. squash! modules/pam_unix: use pam_str_skip_prefix and pam_str_skip_prefix_len
* modules/pam_tty_audit: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | * modules/pam_tty_audit/pam_tty_audit.c: Include "pam_inline.h". (pam_sm_open_session): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_timestamp: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | * modules/pam_timestamp/pam_timestamp.c: Include "pam_inline.h". (check_tty, get_timestamp_name, pam_sm_authenticate): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_tally: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | * modules/pam_tally/pam_tally.c: Include "pam_inline.h". (tally_parse_args, getopts): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_tally2: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | * modules/pam_tally2/pam_tally2.c: Include "pam_inline.h". (tally_parse_args, getopts): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_selinux: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | * modules/pam_selinux/pam_selinux.c: Include "pam_inline.h". (compute_exec_context, compute_tty_context): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_securetty: use pam_str_skip_prefix and pam_str_skip_prefix_lenDmitry V. Levin2020-03-19
| | | | | | * modules/pam_securetty/pam_securetty.c: Include "pam_inline.h". (securetty_perform_check): Use pam_str_skip_prefix and pam_str_skip_prefix_len instead of ugly strncmp invocations.
* modules/pam_rhosts: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | * modules/pam_rhosts/pam_rhosts.c: Include "pam_inline.h". (pam_sm_authenticate): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_nologin: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | * modules/pam_nologin/pam_nologin.c: Include "pam_inline.h". (parse_args): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_namespace: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | * modules/pam_namespace/pam_namespace.c (root_shared): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_motd: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | * modules/pam_motd/pam_motd.c: Include "pam_inline.h". (pam_sm_open_session): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_mkhomedir: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | * modules/pam_mkhomedir/pam_mkhomedir.c: Include "pam_inline.h". (_pam_parse): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_mail: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | * modules/pam_mail/pam_mail.c: Include "pam_inline.h". (_pam_parse): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_localuser: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | * modules/pam_localuser/pam_localuser.c: Include "pam_inline.h". (pam_sm_authenticate): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_listfile: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | * modules/pam_listfile/pam_listfile.c: Include "pam_inline.h". (pam_sm_authenticate): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_limits: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | * modules/pam_limits/pam_limits.c: Include "pam_inline.h". (_pam_parse, parse_kernel_limits): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_lastlog: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | * modules/pam_lastlog/pam_lastlog.c: Include "pam_inline.h". (_pam_auth_parse, get_tty): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_issue: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | * modules/pam_issue/pam_issue.c: Include "pam_inline.h". (pam_sm_authenticate, read_issue_quoted): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_ftp: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | * modules/pam_ftp/pam_ftp.c: Include "pam_inline.h". (_pam_parse): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_env: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | * modules/pam_env/pam_env.c: Include "pam_inline.h". (_pam_parse, _parse_line): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_echo: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | * modules/pam_echo/pam_echo.c: Include "pam_inline.h". (pam_echo): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_cracklib: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | * modules/pam_cracklib/pam_cracklib.c: Include "pam_inline.h". (_pam_parse): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* modules/pam_access: use pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | * modules/pam_access/pam_access.c: Include "pam_inline.h". (parse_args): Use pam_str_skip_prefix instead of ugly strncmp invocations.
* Introduce pam_str_skip_prefix_len and pam_str_skip_prefixDmitry V. Levin2020-03-19
| | | | | | | | | | | | | | | Every time I see a code like if (!strncmp(*argv,"user_readenv=",13)) *user_readenv = atoi(13+*argv); my eyes are bleeding. Introduce a new helper inline function pam_str_skip_prefix_len() and a new macro pam_str_skip_prefix() on top of it, to be used in subsequent commits to cleanup the ugliness. * libpam/include/pam_inline.h: Include <string.h>. (pam_str_skip_prefix_len): New function. (pam_str_skip_prefix): New macro.
* Use PAM_ARRAY_SIZEDmitry V. Levin2020-03-19
| | | | | | | | | | | | | | | | | | | | | Replace all instances of sizeof(x) / sizeof(*x) with PAM_ARRAY_SIZE(x) which is less error-prone and implements an additional type check. * libpam/pam_handlers.c: Include "pam_inline.h". (_pam_open_config_file): Use PAM_ARRAY_SIZE. * modules/pam_exec/pam_exec.c: Include "pam_inline.h". (call_exec): Use PAM_ARRAY_SIZE. * modules/pam_namespace/pam_namespace.c: Include "pam_inline.h". (filter_mntopts): Use PAM_ARRAY_SIZE. * modules/pam_timestamp/hmacfile.c: Include "pam_inline.h". (testvectors): Use PAM_ARRAY_SIZE. * modules/pam_xauth/pam_xauth.c: Include "pam_inline.h". (run_coprocess, pam_sm_open_session): Use PAM_ARRAY_SIZE. * tests/tst-pam_get_item.c: Include "pam_inline.h". (main): Use PAM_ARRAY_SIZE. * tests/tst-pam_set_item.c: Likewise. * xtests/tst-pam_pwhistory1.c: Likewise. * xtests/tst-pam_time1.c: Likewise.
* Introduce pam_inline.hDmitry V. Levin2020-03-19
| | | | | | | | | | Introduce a new internal header file for definitions of handly inline functions and macros providing some convenient functionality to libpam and its modules. * libpam/include/pam_cc_compat.h (PAM_SAME_TYPE): New macro. * libpam/include/pam_inline.h: New file. * libpam/Makefile.am (noinst_HEADERS): Add include/pam_inline.h.
* modules/pam_cracklib: fix parsing of options without argumentsDmitry V. Levin2020-03-19
| | | | | | | | | Prefix match for options without arguments such as use_first_pass is not correct, there has to be an exact match for these options. * modules/pam_cracklib/pam_cracklib.c (_pam_parse): Fix parsing of reject_username, gecoscheck, enforce_for_root, use_authtok, use_first_pass, and try_first_pass options.
* ci: enable -Werror for all buildsDmitry V. Levin2020-03-19
| | | | | | | | The main purpose of fixing all compilation warnings in the current code base was to enable -Werror in CI builds so that no new warnings would creep in. * ci/run-build-and-tests.sh (DISTCHECK_CONFIGURE_FLAGS): Add --enable-Werror.
* configure: implement --enable-Werror optionDmitry V. Levin2020-03-19
| | | | | | | | | | When configure is invoked with --enable-Werror option, -Werror compiler option is added to WARN_CFLAGS. This new configure option is intended primarily for CI purposes. * configure.ac (AC_ARG_ENABLE): Add Werror. Forward -Werror to JAPHAR_GREP_CFLAGS.
* Fix remaining clang -Wcast-align compilation warningsDmitry V. Levin2020-03-19
| | | | | | | | | | | | Introduce DIAG_PUSH_IGNORE_CAST_ALIGN and DIAG_POP_IGNORE_CAST_ALIGN macros, use them to silence remaining clang -Wcast-align compilation warnings. * libpam/include/pam_cc_compat.h (DIAG_PUSH_IGNORE_CAST_ALIGN, DIAG_POP_IGNORE_CAST_ALIGN): New macros. * modules/pam_access/pam_access.c: Include "pam_cc_compat.h". (from_match, network_netmask_match): Wrap inet_ntop invocations in DIAG_PUSH_IGNORE_CAST_ALIGN and DIAG_POP_IGNORE_CAST_ALIGN.
* Fix most of clang -Wcast-align compilation warningsDmitry V. Levin2020-03-19
| | | | | | | | | | | | | | | | | | | Unlike gcc, clang is not smart enough to infer the alignment of structure fields, so add some alignment hints to the code. * libpam/include/pam_cc_compat.h (PAM_ATTRIBUTE_ALIGNED): New macro. * modules/pam_namespace/md5.h: Include "pam_cc_compat.h". (struct MD5Context): Add PAM_ATTRIBUTE_ALIGNED to "in" field. * modules/pam_namespace/md5.c [!(__i386__ || __x86_64__)] (uint8_aligned): New type. [!(__i386__ || __x86_64__)] (byteReverse): Use it instead of unsigned char. * modules/pam_timestamp/sha1.h: Include "pam_cc_compat.h". (struct sha1_context): Add PAM_ATTRIBUTE_ALIGNED to pending field. * modules/pam_unix/md5.h: Include "pam_cc_compat.h". (struct MD5Context): Add PAM_ATTRIBUTE_ALIGNED to "in" field. * modules/pam_unix/md5.c [!HIGHFIRST] (uint8_aligned): New type. [!HIGHFIRST] (byteReverse): Use it instead of unsigned char.
* modules/pam_tally, modules/pam_tally2: fix compilation warningsDmitry V. Levin2020-03-19
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Fix the following compilation warnings reported by gcc when sizeof(time_t) > sizeof(long), e.g. on x32: modules/pam_tally/pam_tally.c:541:7: warning: format ‘%ld’ expects argument of type ‘long int’, but argument 5 has type ‘time_t’ {aka ‘long long int’} [-Wformat=] 541 | _("The account is temporarily locked (%ld seconds left)."), | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ modules/pam_tally/pam_tally.c:546:40: warning: format ‘%ld’ expects argument of type ‘long int’, but argument 6 has type ‘time_t’ {aka ‘long long int’} [-Wformat=] 546 | "user %s (%lu) has time limit [%lds left]" | ~~^ | | | long int | %lld ...... 549 | oldtime+lock_time-time(NULL)); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | time_t {aka long long int} modules/pam_tally2/pam_tally2.c:592:27: warning: format ‘%ld’ expects argument of type ‘long int’, but argument 5 has type ‘time_t’ {aka ‘long long int’} [-Wformat=] 592 | pam_info(pamh, _("The account is temporarily locked (%ld seconds left)."), | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ modules/pam_tally2/pam_tally2.c:597:50: warning: format ‘%ld’ expects argument of type ‘long int’, but argument 6 has type ‘time_t’ {aka ‘long long int’} [-Wformat=] 597 | "user %s (%lu) has time limit [%lds left]" | ~~^ | | | long int | %lld ...... 600 | oldtime+opts->lock_time-time(NULL)); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | time_t {aka long long int} This change doesn't attempt to fix handling of 64-bit time_t on 32-bit systems in these modules. * modules/pam_tally/pam_tally.c (tally_check): Cast time_t expressions to long int before passing them to pam_info and pam_syslog. * modules/pam_tally2/pam_tally2.c (tally_check): Likewise.
* modules/pam_timestamp: fix compilation warningsDmitry V. Levin2020-03-19
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | Fix the following compilation warnings reported by gcc on ilp32 platforms: modules/pam_timestamp/hmacfile.c: In function ‘testvectors’: modules/pam_timestamp/hmacfile.c:121:44: warning: format ‘%lu’ expects argument of type ‘long unsigned int’, but argument 2 has type ‘size_t’ {aka ‘unsigned int’} [-Wformat=] 121 | printf("Incorrect result for vector %lu\n", i + 1); | ~~^ ~~~~~ | | | | | size_t {aka unsigned int} | long unsigned int | %u modules/pam_timestamp/hmacfile.c:128:30: warning: format ‘%lu’ expects argument of type ‘long unsigned int’, but argument 2 has type ‘size_t’ {aka ‘unsigned int’} [-Wformat=] 128 | printf("Error in vector %lu.\n", i + 1); | ~~^ ~~~~~ | | | | | size_t {aka unsigned int} | long unsigned int | %u In function ‘strncpy’, inlined from ‘pam_sm_open_session’ at modules/pam_timestamp/pam_timestamp.c:584:4: /usr/include/bits/string_fortified.h:106:10: warning: ‘__builtin___strncpy_chk’ output may be truncated copying between 1 and 4095 bytes from a string of length 4095 [-Wstringop-truncation] * modules/pam_timestamp/hmacfile.c (testvectors): Cast the argument of type size_t to unsigned long before passing it to printf. * modules/pam_timestamp/pam_timestamp.c (pam_sm_open_session): Use memcpy instead of strncpy as the source is not NUL-terminated, add an extra check to ensure that iterator stays inside bounds.
* modules/pam_unix: fix gcc compilation warningsDmitry V. Levin2020-03-19
| | | | | | | | | | | | | | | | | | | | | | | | | | | | When setreuid() fails, there is no way to proceed any further: either the process credentials are unchanged but inappropriate, or they are in an inconsistent state and nothing good could be made out of it. This fixes the following compilation warnings: modules/pam_unix/passverify.c:209:5: warning: ignoring return value of 'setreuid', declared with attribute warn_unused_result [-Wunused-result] modules/pam_unix/passverify.c:211:5: warning: ignoring return value of 'setreuid', declared with attribute warn_unused_result [-Wunused-result] modules/pam_unix/passverify.c:213:6: warning: ignoring return value of 'setreuid', declared with attribute warn_unused_result [-Wunused-result] modules/pam_unix/passverify.c:214:6: warning: ignoring return value of 'setreuid', declared with attribute warn_unused_result [-Wunused-result] modules/pam_unix/passverify.c:222:5: warning: ignoring return value of 'setreuid', declared with attribute warn_unused_result [-Wunused-result] modules/pam_unix/passverify.c:224:5: warning: ignoring return value of 'setreuid', declared with attribute warn_unused_result [-Wunused-result] modules/pam_unix/passverify.c:225:5: warning: ignoring return value of 'setreuid', declared with attribute warn_unused_result [-Wunused-result] modules/pam_unix/passverify.c:226:5: warning: ignoring return value of 'setreuid', declared with attribute warn_unused_result [-Wunused-result] modules/pam_unix/passverify.c:209:5: warning: ignoring return value of 'setreuid', declared with attribute warn_unused_result [-Wunused-result] modules/pam_unix/passverify.c:211:5: warning: ignoring return value of 'setreuid', declared with attribute warn_unused_result [-Wunused-result] modules/pam_unix/passverify.c:213:6: warning: ignoring return value of 'setreuid', declared with attribute warn_unused_result [-Wunused-result] modules/pam_unix/passverify.c:214:6: warning: ignoring return value of 'setreuid', declared with attribute warn_unused_result [-Wunused-result] modules/pam_unix/passverify.c:222:5: warning: ignoring return value of 'setreuid', declared with attribute warn_unused_result [-Wunused-result] modules/pam_unix/passverify.c:224:5: warning: ignoring return value of 'setreuid', declared with attribute warn_unused_result [-Wunused-result] modules/pam_unix/passverify.c:225:5: warning: ignoring return value of 'setreuid', declared with attribute warn_unused_result [-Wunused-result] modules/pam_unix/passverify.c:226:5: warning: ignoring return value of 'setreuid', declared with attribute warn_unused_result [-Wunused-result] * modules/pam_unix/passverify.c (get_account_info) [HELPER_COMPILE]: Always check setreuid return code and return PAM_CRED_INSUFFICIENT if setreuid failed.
* modules/pam_access: fix compilation warningDmitry V. Levin2020-03-19
| | | | | | | | | | | | | Fix the following compilation warning reported by gcc when HAVE_LIBAUDIT is not set: modules/pam_access/pam_access.c: In function ‘login_access’: modules/pam_access/pam_access.c:338:13: warning: variable ‘nonall_match’ set but not used [-Wunused-but-set-variable] 338 | int nonall_match = NO; | ^~~~~~~~~~~~ * modules/pam_access/pam_access.c (login_access): Enclose nonall_match variable with HAVE_LIBAUDIT #ifdef's.
* conf/pam_conv1: fix clang compilation warningsDmitry V. Levin2020-03-19
| | | | | | | | | | | | | | | | | Fix the following compilation warnings reported by clang: pam_conv_y.y:12:23: warning: unused variable 'bisonid' [-Wunused-const-variable] static const char bisonid[]= ^ pam_conv_l.l:12:23: warning: unused variable 'lexid' [-Wunused-const-variable] static const char lexid[]= ^ These static variables lost their meaning after repository conversion from cvs to git and can be safely removed. * conf/pam_conv1/pam_conv_l.l (lexid): Remove. * conf/pam_conv1/pam_conv_y.y (bisonid): Remove.
* modules/pam_timestamp: fix clang compilation warningDmitry V. Levin2020-03-18
| | | | | | | | | | | modules/pam_timestamp/pam_timestamp.c:807:17: warning: logical not is only applied to the left hand side of this comparison [-Wlogical-not-parentheses] } else if (!timestamp_good(st.st... ^ * modules/pam_timestamp/pam_timestamp.c (main): Change timestamp_good return code check to a more traditional form.
* github: check for whitespace errors on push and pull requestsDmitry V. Levin2020-03-18
| | | | * .github/workflows/whitespace-errors-check.yml: New file.
* modules/pam_timestamp: fix EXTRA_DISTDmitry V. Levin2020-03-18
| | | | | * modules/pam_timestamp/Makefile.am (EXTRA_DIST): Replace "$(man_MANS)" with "$(MANS)" as the former is conditional on HAVE_DOC.
* modules/pam_namespace: fix EXTRA_DISTDmitry V. Levin2020-03-18
| | | | | | * modules/pam_namespace/Makefile.am (EXTRA_DIST): Replace "$(MAN5) $(MAN8)" with "$(MANS)" as the former is conditional on HAVE_DOC.
* pam_usertype: exclude man-page generation when configured with --disable-docChristian Göttsche2020-03-17
| | | | | | | * modules/pam_usertype/Makefile.am (man_MANS): Make conditional on HAVE_DOC. Resolves: https://github.com/linux-pam/linux-pam/pull/193
* pam_namespace: ignore pam_namespace_helper in gitChristian Göttsche2020-03-17
| | | | | | * modules/pam_namespace/.gitignore: New file. Resolves: https://github.com/linux-pam/linux-pam/pull/192
* Update translation filesWeblate2020-03-13
| | | | | | | Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: linux-pam/master Translate-URL: https://translate.fedoraproject.org/projects/linux-pam/master/
* Translated using Weblate (Slovak)Ondrej Sulek2020-03-13
| | | | | | | Currently translated at 100.0% (117 of 117 strings) Translation: linux-pam/master Translate-URL: https://translate.fedoraproject.org/projects/linux-pam/master/sk/
* Translated using Weblate (Ukrainian)Yuri Chornoivan2020-03-13
| | | | | | | Currently translated at 100.0% (117 of 117 strings) Translation: linux-pam/master Translate-URL: https://translate.fedoraproject.org/projects/linux-pam/master/uk/
* Translated using Weblate (Portuguese (Brazil))Dmitry V. Levin2020-03-13
| | | | | | | | | | | | | | | | | | | | | Currently translated at 100.0% (117 of 117 strings) Translation: linux-pam/master Translate-URL: https://translate.fedoraproject.org/projects/linux-pam/master/pt_BR/ Translated using Weblate (Portuguese) Currently translated at 100.0% (117 of 117 strings) Translation: linux-pam/master Translate-URL: https://translate.fedoraproject.org/projects/linux-pam/master/pt/ Translated using Weblate (German) Currently translated at 91.4% (107 of 117 strings) Translation: linux-pam/master Translate-URL: https://translate.fedoraproject.org/projects/linux-pam/master/de/
* Adjust README with instructions for package prerequsitiesTomas Mraz2020-03-13
| | | | Also remove obsolete static modules instructions
* pam_get_authtok: fix i18n of default promptsDmitry V. Levin2020-03-11
| | | | | | | | | | | | | | | Change formatting of default prompts, making them translatable to those languages that use a different word order. From non-i18n perspective this change is essentially a no-op. * libpam/pam_get_authtok.c (PROMPTCURRENT): Replace with PROMPT_CURRENT_ARG and PROMPT_CURRENT_NOARG. (PROMPT1): Replace with PROMPT_NEW_ARG and PROMPT_NEW_NOARG. (PROMPT2): Replace with PROMPT_RETYPE_ARG and PROMPT_RETYPE_NOARG. (pam_get_authtok_internal, pam_get_authtok_verify): Use new macros. * po/Linux-PAM.pot: Regenerated. Resolves: https://github.com/linux-pam/linux-pam/issues/29
* pam_selinux: check unknown object classes or permissions in current policyikerexxe2020-03-11
| | | | | | Explanation: check whether unknown object classes or permissions are allowed or denied in the current policy Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1680961