I am trying to authenticate against a git server with libgit2 using SSH keys.
So far, this is working for URLs like ssh://myuser#host.domain:1234/dirs/repo.git, where my application accepts the URL as an argument.
However, if I remove the username from the URL (i.e. ssh://host.domain:1234/dirs/repo.git) the connection fails, even if I set the user name programmatically (see below).
Consider the following MCVE for a program that checks whether a certain repository is reachable (no error checks except for the necessary):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
#include <git2.h>
#include <git2/sys/repository.h>
int my_cred_cb(git_cred **out, const char *url,
const char *username_from_url, unsigned int allowed_types, void *payload)
{
uid_t uid = geteuid(); // Get effective user ID
struct passwd* pw = getpwuid(uid); // Get password file entry
char privkeypath[strlen(pw->pw_dir) + 13];
strcpy(privkeypath, pw->pw_dir);
strcat(privkeypath, "/.ssh/id_rsa"); // "~user/.ssh/id_rsa"
char publkeypath[strlen(privkeypath) + 5];
strcpy(publkeypath, privkeypath);
strcat(publkeypath, ".pub"); // "~user/.ssh/id_rsa.pub"
const char* username = (username_from_url != NULL ? username_from_url : pw->pw_name);
printf("Using username: %s, priv key %s and publ key %s\n", username, privkeypath, publkeypath);
return git_cred_ssh_key_new(out, username, publkeypath, privkeypath, ""); // No passphrase for keys
}
int main(int argc, char** argv)
{
git_remote* remote = NULL;
git_repository* repo = NULL;
git_remote_callbacks cbs;
const git_error* err;
if (argc != 2) return EXIT_FAILURE;
git_libgit2_init();
git_repository_new(&repo);
git_remote_create_anonymous(&remote, repo, argv[1]);
git_remote_init_callbacks(&cbs, GIT_REMOTE_CALLBACKS_VERSION);
cbs.credentials = my_cred_cb;
if (git_remote_connect(remote, GIT_DIRECTION_FETCH, &cbs, NULL, NULL)) {
err = giterr_last();
printf ("Error %d: %s\n", err->klass, err->message);
return EXIT_FAILURE;
}
git_libgit2_shutdown();
printf("git repo exists and is reachable.\n");
}
This can be compiled with gcc -Wall -pedantic -std=c99 -o myapp main.c -lssh2 -lgit2, assuming the include and library paths are set properly.
Now consider the output for different URLs:
$ ./myapp ssh://myuser#host.domain:1234/dirs/repo.git
Using username: myuser, priv key /home/myuser/.ssh/id_rsa and publ key /home/myuser/.ssh/id_rsa.pub
git repo exists and is reachable.
And now the failing case:
$ ./myapp ssh://host.domain:1234/dirs/repo.git # Note the missing user name
Using username: myuser, priv key /home/myuser/.ssh/id_rsa and publ key /home/myuser/.ssh/id_rsa.pub
Error 23: callback returned unsupported credentials type
I do not understand why I receive this error, since I pass the exact same information to the library (why is it "unsupported credentials type" in the first place?).
Even worse, I usually use Host entries in my ~/.ssh/config, such that instead of putting host.domain:1234 I can simply use myhost (e.g. git clone ssh://myhost/dirs/repo.git works just fine). For my application, this is the output:
$ ./myapp ssh://myhost/dirs/repo.git
Error 12: failed to resolve address for myhost: Name or service not known
It looks like libgit2 does not configure libssh2 to read in the ssh-config. That's a related, yet probably a different problem. Still I feel these two problems belong together.
Summarizing my questions:
How do I pass the username to the git credentials programmatically (vs. in the URL)?
How do I tell libgit2 to retrieve information from my ssh-config before attempting to connect?
These really are two separate unrelated questions.
You should be checking the allowed_types parameter in your callback and only return a git_cred_ssh_key_new credential if it contains GIT_CREDTYPE_SSH_KEY. The backend is probably requesting a credential of type GIT_CREDTYPE_USERNAME because the URL doesn't have one. In that case you should be returning a credential created by git_cred_username_new. Your callback will be called twice, once to get the username, and a second time to create the ssh credential.
Reading config settings from your OpenSSH config file at ~/.ssh/config isn't supported by libgit2 because it isn't support by libssh2. If you want to read settings from there, you have to do it yourself.
Related
I want to access the user name in the Windows using C programming and use that name to create the path to the particular file like "c:\users\john\Roaming.....and so on". So for every system user name e.g "john" is different. Help me to find the user name at run time.
#include <stdio.h>
int main(void)
{
printf("%s\n", getenv("USERPROFILE")); // Print user's home directory.
return 0;
}
To get the user name instead of the home path replace USERPROFILE with USERNAME.
What you are looking for, here, is probably more SHGetKnownFolderPath. The function lets you find per-user special folders. This is preferred to querying usernames because the home folder may not have the same name as the user.
WSTR* location;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &location);
if (SUCCEEDED(hr))
{
// location contains the folder path
// call CoTaskMemFree to free up the memory once you're done with it
CoTaskMemFree(location);
}
The list of so-called known folders is available here.
The function to get user name on windows is GetUserName
This answer, probably, will help you too.
you could use the following code to get the Username.
#include <stdlib.h>
void main(void)
{
//following gets the appdata folder
char szAppData[1024];
char * szBufer = 0;
szBufer = getenv ("APPDATA");
if (szBufer != NULL)
{
strcpy(szBufer , szAppData);
}
//following code gets the user name
char szOSUserName[1024];
szBufer = getenv ("USERNAME");
if (szBufer != NULL)
{
strcpy(szBufer , szOSUserName);
}
}
You can get the name of the current user with GetUserName:
#include <Windows.h>
#include <Lmcons.h>
#include <stdio.h>
int main()
{
char name[UNLEN + 1];
DWORD cch = UNLEN + 1;
if (GetUserName(name, &cch))
{
char cmd[100 + UNLEN + 1];
sprintf(cmd, "echo The username is \"%s\"", name); // Silly demo command
system(cmd);
}
return 0;
}
Use GetUserNameEx if you want the name in a specific format.
If you need to get the path to a special folder like "My Documents" or "Desktop" you should use the special folder functions like SHGetFolderPath or SHGetKnownFolderPath.
%USERNAME% will give you the username, but a better solution is to store it on %USERPROFILE%\\Desktop\\key.txt to at least make it OS-independent.
And an even better solution would be not to store private information on the users' desktops. Or anywhere.
I'm trying to write a demo PAM module in C, which uses Embedding Python in C concept to run a script written in python (2.7), inside pam_sm_authenticate() function, which is written in C file (pam_auth.c).
This is the python script: test.py
import math
import numpy
def test_func():
a = "test"
return a
The path for test.py is /usr/lib/Python2.7/ so that I can easily import it.
This is the C file:
#define PAM_SM_AUTH
#define PAM_SM_ACCOUNT
#define PAM_SM_SESSION
#include <security/pam_modules.h>
#include <security/_pam_macros.h>
#include <security/pam_appl.h>
#include<python2.7/Python.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define NOBODY "nobody"
/*PAM Stuffs*/
PAM_EXTERN int pam_sm_authenticate(
pam_handle_t* pamh, int flags, int argc, const char** argv)
{
const char *user;
int retval;
user = NULL;
retval = pam_get_user(pamh, &user, NULL);
if(retval != PAM_SUCCESS)
{
fprintf(stderr, "%s", pam_strerror(pamh, retval));
// return (retval);
}
fprintf(stdout, "retval= %d user=%s\n", retval,user);
if (user == NULL || *user =='\0')
pam_set_item(pamh, PAM_USER, (const char*)NOBODY);
/* Python Wrapper */
// Set PYTHONPATH TO working directory
//int res = setenv("PYTHONPATH",".",1);
//fprintf(stdout, "%d", res);
PyObject *pName, *pModule, *pDict, *pFunc, *pValue, *pResult;
// Initialize the Python Interpreter
Py_Initialize();
// Build the name object
pName = PyString_FromString((char*)"test");
// Load the module object
pModule = PyImport_Import(pName);
// pDict is a borrowed reference
PyErr_Print();
pDict = PyModule_GetDict(pModule);
// pFunc is also a borrowed reference
pFunc = PyDict_GetItemString(pDict, (char*)"test_func");
if (PyCallable_Check(pFunc))
{
pValue=NULL;
PyErr_Print();
pResult=PyObject_CallObject(pFunc,pValue);
PyErr_Print();
}else
{
PyErr_Print();
}
printf("Result is %s\n",PyString_AsString(pResult));
// Clean up
Py_DECREF(pModule);
Py_DECREF(pName);/* */
// Finish the Python Interpreter
Py_Finalize();
return PAM_SUCCESS;
}
PAM_EXTERN int pam_sm_setcred(
pam_handle_t* pamh, int flags, int argc, const char** argv)
{
return PAM_SUCCESS;
}
PAM_EXTERN int pam_sm_acct_mgmt(
pam_handle_t* pamh, int flags, int argc, const char** argv)
{
return PAM_SUCCESS;
}
PAM_EXTERN int pam_sm_open_session(
pam_handle_t* pamh, int flags, int argc, const char** argv)
{
return PAM_SUCCESS;
}
PAM_EXTERN int pam_sm_close_session(
pam_handle_t* pamh, int flags, int argc, const char** argv)
{
return PAM_SUCCESS;
}
PAM_EXTERN int pam_sm_chauthtok(
pam_handle_t* pamh, int flags, int argc, const char** argv)
{
return PAM_SUCCESS;
}
The C-file is just a modification of pam_permit.c. The C file is compiled using gcc ( gcc -shared -o pam_auth.so -fPIC pam_auth.c -I/usr/include/python2.7 -lpython2.7 ) to obtain an .so file (pam_auth.so) and is put inside the folder /lib/security/
I changed the PAM configuration of 'sudo' file in /etc/pam.d as follows:
#%PAM-1.0
auth required pam_env.so readenv=1 user_readenv=0
auth required pam_env.so readenv=1 envfile=/etc/default/locale user_readenv=0
##include common-auth #this line is commented to make it use my pam module
auth required pam_auth.so
#include common-account
#include common-session-noninteractive
The line "auth required pam_auth.so" forces the system to use my module for authentication everytime I use the command "sudo". (for ex- sudo nautilus)
Now the problem is:
This line in C file " pModule = PyImport_Import(pName); " gives an import error, which is printed by PyErr_Print() as follows:
stitches#Andromida:~$ sudo nautilus
retval= 0 user=stitches
Traceback (most recent call last):
File "/usr/lib/python2.7/subho_auth.py", line 8, in <module>
import numpy
File "/usr/lib/python2.7/dist-packages/numpy/__init__.py", line 153, in <module>
from . import add_newdocs
File "/usr/lib/python2.7/dist-packages/numpy/add_newdocs.py", line 13, in <module>
from numpy.lib import add_newdoc
File "/usr/lib/python2.7/dist-packages/numpy/lib/__init__.py", line 8, in <module>
from .type_check import *
File "/usr/lib/python2.7/dist-packages/numpy/lib/type_check.py", line 11, in <module>
import numpy.core.numeric as _nx
File "/usr/lib/python2.7/dist-packages/numpy/core/__init__.py", line 6, in <module>
from . import multiarray
ImportError: /usr/lib/python2.7/dist-packages/numpy/core/multiarray.so: undefined symbol: PyExc_SystemError
Segmentation fault (core dumped)
As per I can understand,it fails to import numpy library as specified in test.py file. How to solve this problem of ImportError & PyExc_SystemError?
The python script works as charm if I run in as follows:
#include <Python.h>
#include <stdlib.h>
#include <string.h>
int main()
{
// Set PYTHONPATH TO working directory
//int res = setenv("PYTHONPATH",".",1);
//fprintf(stdout, "%d", res);
PyObject *pName, *pModule, *pDict, *pFunc, *pValue, *pResult;
// Initialize the Python Interpreter
Py_Initialize();
// Build the name object
pName = PyString_FromString((char*)"test");
// Load the module object
pModule = PyImport_Import(pName);
// pDict is a borrowed reference
PyErr_Print();
pDict = PyModule_GetDict(pModule);
// pFunc is also a borrowed reference
pFunc = PyDict_GetItemString(pDict, (char*)"test_func");
if (PyCallable_Check(pFunc))
{
pValue=NULL;
PyErr_Print();
pResult=PyObject_CallObject(pFunc,pValue);
PyErr_Print();
}else
{
PyErr_Print();
}
printf("Result is %s\n",PyString_AsString(pResult));
// Clean up
Py_DECREF(pModule);
Py_DECREF(pName);/* */
// Finish the Python Interpreter
Py_Finalize();
return 0;
}
If it works under general python embedding examples, why its not working in PAM-based embedding examples (where .so files are used)?
PS: I'm importing numpy for a particular reason. Don't ask why I've not used in anywhere in python script as this is just a demo script of what I'm trying to achieve. Moreover, import math doesn't give any import error. I get import error for SciPY too.
PPS: Numpy and Scipy packages works perfect in python scripts and is installed under /usr/lib/python2.7/dist-packages/. I'm using ubuntu 14.04.
Please help!!!!
I don't know the answer to your question, but I am wondering why it didn't fail earlier. The host application does not know your PAM module will be needed using libpython2.7.so.1, so somehow that must being loaded dynamically otherwise the Py_Initialize() call would fail with the same error.
Given you say it doesn't fail there it must be loaded. However from the error you are getting we can deduce the symbols it contains (such as PyExc_SystemError) are not visible to dynamic libraries subsequently loaded. This is the default when libraries are loaded using dlopen() (see RTLD_LOCAL in man 3 dlopen). To override it, you must pass RTLD_GLOBAL to dlopen(). Maybe that's your problem.
Other comments about your code:
Calling Py_Initialise() for each pm_sm_...() call is going to be expensive and possibly surprising to the python modules. It means all data the python module accumulated within one call (like say voice or the user name) will be discarded when the next call is made. You are better off loading libpython2.7.so.1 and initialising PAM once, then using the cleanup function of pam_set_data() to unload it when you are done.
In a related issue, your PAM module isn't usable from Python programs because you always call Py_Initialise() (and I presume the matching call to Py_Finalize()).
If you program hadn't fallen over where it did, it would have fallen over on the line printf("Result is %s\n",PyString_AsString(pResult)) because pResult isn't initialised.
As I think you know, all the boilerplate you have here to let you wring PAM modules in Python is provided by pam-python - no C required. Since you are evidently writing your PAM module in Python anyway, you are already exposed to the overheads it incurs but are missing out on the features it provides like logging uncaught Python exceptions. And most importantly, using it means you can avoid C entirely. Your PAM module will be loaded into programs that guard the security of the machine - programs like login, sudo, and xdm/gdm3. Avoiding C means also avoiding the legions of security bugs C programs can have that are impossible in Python - buffer overruns, uninitialised pointers and accessing free'ed memory. Since you have one of those bugs in your the C code you posted here, avoiding it sounds like a good idea.
I want to access the user name in the Windows using C programming and use that name to create the path to the particular file like "c:\users\john\Roaming.....and so on". So for every system user name e.g "john" is different. Help me to find the user name at run time.
#include <stdio.h>
int main(void)
{
printf("%s\n", getenv("USERPROFILE")); // Print user's home directory.
return 0;
}
To get the user name instead of the home path replace USERPROFILE with USERNAME.
What you are looking for, here, is probably more SHGetKnownFolderPath. The function lets you find per-user special folders. This is preferred to querying usernames because the home folder may not have the same name as the user.
WSTR* location;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &location);
if (SUCCEEDED(hr))
{
// location contains the folder path
// call CoTaskMemFree to free up the memory once you're done with it
CoTaskMemFree(location);
}
The list of so-called known folders is available here.
The function to get user name on windows is GetUserName
This answer, probably, will help you too.
you could use the following code to get the Username.
#include <stdlib.h>
void main(void)
{
//following gets the appdata folder
char szAppData[1024];
char * szBufer = 0;
szBufer = getenv ("APPDATA");
if (szBufer != NULL)
{
strcpy(szBufer , szAppData);
}
//following code gets the user name
char szOSUserName[1024];
szBufer = getenv ("USERNAME");
if (szBufer != NULL)
{
strcpy(szBufer , szOSUserName);
}
}
You can get the name of the current user with GetUserName:
#include <Windows.h>
#include <Lmcons.h>
#include <stdio.h>
int main()
{
char name[UNLEN + 1];
DWORD cch = UNLEN + 1;
if (GetUserName(name, &cch))
{
char cmd[100 + UNLEN + 1];
sprintf(cmd, "echo The username is \"%s\"", name); // Silly demo command
system(cmd);
}
return 0;
}
Use GetUserNameEx if you want the name in a specific format.
If you need to get the path to a special folder like "My Documents" or "Desktop" you should use the special folder functions like SHGetFolderPath or SHGetKnownFolderPath.
%USERNAME% will give you the username, but a better solution is to store it on %USERPROFILE%\\Desktop\\key.txt to at least make it OS-independent.
And an even better solution would be not to store private information on the users' desktops. Or anywhere.
I have a function that is used to check a list of user either that user is logged in or not.(Roughly say, the user have an active connection to the server).
This is the code:
static int is_login(char *user)
{
int found = 0;
struct utmpx *u;
setutxent();
while ((u = getutxent())) {
if ((strcmp(u->ut_user,user)==0) && (u->ut_type ==USER_PROCESS)) {
found = 1;
break;
} else {
found =0;
}
}
endutxent();
return found;
}
Dont get this wrong. This code work fine. The only issue is when /var/run/utmp permission are not set as readable. eg: chmod /var/run/utmp 600.. Even worse if the server doesnt have utmp. Instead use a utmps. Is there any other function that can do the same thing as getutxent()? So far what I only found is getpwuid(getuid()) and getlogin() which only return the user that logged in on the controlling terminal.
This is what I have tested
#include <unistd.h>
#include <stdio.h>
#include <pwd.h>
int main()
{
char *name;
struct passwd *pass;
while(pass = getpwuid(getuid())) //I've tested also with getlogin(), but without the struct ofcourse.
{
name = pass->pw_name;
printf("user = %s\n",name);
}
return 0;
}
While running this test program, I logged in with 2 more different user into that system, but those 2 user's name doesnt appear on the screen.
I'm working on the project with the same functionality as yours (e.g. login events monitoring), but I have to rely onto the /var/log/secure file. It provides much more information for me. May be you should take it into account.
I would like to authenticate users of my C network application with PAM and I have a found a nice PAM example here on Stack, which I attach at the bottom. The problem is that in my development machine I have a fingerprint reader which PAM is set up to use, as in /etc/pam.d/common-auth:
#%PAM-1.0
#
# This file is autogenerated by pam-config. All changes
# will be overwritten.
#
# Authentication-related modules common to all services
#
# This file is included from other service-specific PAM config files,
# and should contain a list of the authentication modules that define
# the central authentication scheme for use on the system
# (e.g., /etc/shadow, LDAP, Kerberos, etc.). The default is to use the
# traditional Unix authentication mechanisms.
#
auth required pam_env.so
auth sufficient pam_fprint.so
auth optional pam_gnome_keyring.so
auth required pam_unix2.so
pam_fprint.so is the fingerprint reader plugin. When you normally log in, the scan can fail and you are prompted for a password. However, sshd daemon does not initiate the fingerprint at all and I would like to understand how it skips it, because for example /etc/pam.d/sshd references the common-auth module so it must pull it ..
#%PAM-1.0
auth requisite pam_nologin.so
auth include common-auth
account requisite pam_nologin.so
account include common-account
password include common-password
session required pam_loginuid.so
session include common-session
session optional pam_lastlog.so silent noupdate showfailed
I have tried to reference the 'sshd' scheme from the C program but it still initiates the fingerprint reader. I want to skip the fingerprint reader somehow in C and retain my fingerprint reader default config.
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <security/pam_appl.h>
#include <unistd.h>
// To build this:
// g++ test.cpp -lpam -o test
struct pam_response *reply;
//function used to get user input
int function_conversation(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
{
*resp = reply;
return PAM_SUCCESS;
}
int main(int argc, char** argv)
{
if(argc != 2) {
fprintf(stderr, "Usage: check_user <username>\n");
exit(1);
}
const char *username;
username = argv[1];
const struct pam_conv local_conversation = { function_conversation, NULL };
pam_handle_t *local_auth_handle = NULL; // this gets set by pam_start
int retval;
// local_auth_handle gets set based on the service
retval = pam_start("common-auth", username, &local_conversation, &local_auth_handle);
if (retval != PAM_SUCCESS)
{
std::cout << "pam_start returned " << retval << std::endl;
exit(retval);
}
reply = (struct pam_response *)malloc(sizeof(struct pam_response));
// *** Get the password by any method, or maybe it was passed into this function.
reply[0].resp = getpass("Password: ");
reply[0].resp_retcode = 0;
retval = pam_authenticate(local_auth_handle, 0);
if (retval != PAM_SUCCESS)
{
if (retval == PAM_AUTH_ERR)
{
std::cout << "Authentication failure." << std::endl;
}
else
{
std::cout << "pam_authenticate returned " << retval << std::endl;
}
exit(retval);
}
std::cout << "Authenticated." << std::endl;
retval = pam_end(local_auth_handle, retval);
if (retval != PAM_SUCCESS)
{
std::cout << "pam_end returned " << retval << std::endl;
exit(retval);
}
return retval;
}
I doubt that sshd is actually skipping that module. Rather, I suspect that the fingerprint reader authentication module (sensibly) is checking whether the authenticating user appears to be on the local system or is coming over the network (which it can figure out from PAM data like rhost) and just silently does nothing if this is a network authentication. You could try looking at the source code to see if it has such a test, or try setting PAM_RHOST via pam_set_item and see if that changes the behavior.
To answer your actual question, I don't believe there is a way to tell PAM to run a particular PAM group except for one module. The expected way to do what you want to do is to create a new configuration file in /etc/pam.d that matches the application name you pass to pam_start that does not include common-auth but instead contains just the modules that you want to run.