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.
Related
I'm writing a program that continuously and recursively checks an FTP server for new files. When a file is detected, it is downloaded.
I wrote the all thing using the curl easy interface, since blocking calls to curl_easy_perform() are great for the control channel and listing operations. But when it comes to download files, the multi interface seems a lot more appropriate. I thought about switching the entire thing to multi, but it gets very complicated for directory listing.
So here's my question, can I use both interfaces, easy and multi inside the same thread ? If so, can they share the same connection to the server ?
EDIT 1
Instead of using curl_easy_perform(), is there a way to check for a single transfer status ? So I could use the curl_multi_* interface for all my transfers, and only check my LIST command status right after I perform it. This would allow me to simulate a blocking behavior, without interfering with my file transfers that would be handled and checked elsewhere.
From what I saw, the curl_multi_info_read() doesn't allow to do so :
When you fetch a message using this function, it is removed from the internal queue so calling this function again will not return the same message again.
Does this answer your question:
When an easy handle is setup and ready for transfer, then instead of using curl_easy_perform like when using the easy interface for transfers, you should add the easy handle to the multi handle with curl_multi_add_handle. You can add more easy handles to a multi handle at any point, even if other transfers are already running.
From libcurl - multi interface overview (ONE MULTI HANDLE MANY EASY HANDLES)
can I use both interfaces, easy and multi inside the same thread ?
yes absolutely, but note that the easy api is mostly blocking, and the multi api is mostly non-blocking, so if you combine them wrong, you might end up in a situation where your multi transfers are hanging/slow because your thread is blocking on a curl_easy~ call.
If so, can they share the same connection to the server ?
strictly speaking, yes, at least in some situations, but you really should let libcurl worry about connection-reuse details, unless you're in a micro-optimization phase (and given your questions, you're absolutely not)
is there a way to check for a single transfer status
check status of a single transfer from a curl_multi list of transfers?
idk to be honest, when i use curl_multi, i usually only check up on them when they're no longer active as reported by curl_multi_info_read() & co.. you could wrap each transfer in its own object with its own dedicated download thread, and keep track of each transfer with CURLOPT_WRITEFUNCTION & co,
this program will output
transfer #1 is 4.70178% downloaded. running: true
transfer #2 is 6.51742% downloaded. running: true
transfer #3 is 6.14288% downloaded. running: true
transfer #4 is 6.01199% downloaded. running: true
transfer #0 is 12.3027% downloaded. running: true
transfer #1 is 8.73407% downloaded. running: true
transfer #2 is 14.0515% downloaded. running: true
transfer #3 is 12.8638% downloaded. running: true
transfer #4 is 11.8516% downloaded. running: true
(...)
transfer #0 is 94.8156% downloaded. running: true
transfer #1 is 88.5291% downloaded. running: true
transfer #2 is 98.8117% downloaded. running: true
transfer #3 is 92.01% downloaded. running: true
transfer #4 is 100% downloaded. running: false
transfer #0 is 100% downloaded. running: false
transfer #1 is 100% downloaded. running: false
transfer #2 is 100% downloaded. running: false
transfer #3 is 100% downloaded. running: false
it keeps track of each individual transfer in its own thread, and the main thread can easily check up on any individual transfers by doing transfers[x]->status ~
#include <iostream>
#include <thread>
#include <string>
#include <string_view>
#include <atomic>
#include <vector>
#include <memory>
#include <curl/curl.h>
class Curl_Transfer
{
public:
std::string url;
std::string response_headers;
std::string response_body;
CURL *ch = nullptr;
CURLcode curl_easy_perform_code = CURLcode(0);
bool running = true;
std::thread dedicated_thread;
int64_t expected_size = 0; // << content-length reported size
Curl_Transfer(std::string url) : url(url)
{
this->dedicated_thread = std::thread([&]() -> void
{
this->ch = curl_easy_init();
curl_easy_setopt(this->ch, CURLOPT_URL, this->url.c_str());
curl_easy_setopt(this->ch, CURLOPT_WRITEDATA,
this);
curl_easy_setopt(this->ch, CURLOPT_HEADERDATA,
this);
curl_easy_setopt(this->ch, CURLOPT_WRITEFUNCTION, this->WRITEFUNCTION_cb);
curl_easy_setopt(this->ch, CURLOPT_HEADERFUNCTION, this->HEADERFUNCTION_cb);
CURLcode code=curl_easy_perform(this->ch);
//std::cout << "code: " << code << std::endl;
this->curl_easy_perform_code = code;
this->running = false;
});
}
~Curl_Transfer()
{
std::cout << "DESTRUCTING!" << std::endl;
this->dedicated_thread.join();
curl_easy_cleanup(this->ch);
}
private:
// this function need to be static to be compatible with some C->C++ calling stuff... idk, but it also need access to this, so fthis=this...
static size_t WRITEFUNCTION_cb(const char *data, size_t size, size_t nmemb, Curl_Transfer *fthis)
{
CURL *ch = fthis->ch;
fthis->response_body.append(data, size * nmemb);
//std::cout << "got body data! " << size*nmemb << "\n";
return size * nmemb;
};
// this function need to be static to be compatible with some C->C++ calling stuff... idk, but it also need access to this, so fthis=this...
static size_t HEADERFUNCTION_cb(const char *data, size_t size, size_t nmemb, Curl_Transfer *fthis)
{
CURL *ch = fthis->ch;
//std::cout << "got headers! " << size*nmemb << "\n";
fthis->response_headers.append(data, size * nmemb);
std::string_view svd(data, size * nmemb);
const std::string_view needle = "Content-Length: ";
auto clp = svd.find(needle);
if (clp != std::string::npos)
{
svd = svd.substr(needle.size());
std::string fck(svd);
fthis->expected_size = std::stoll(fck, nullptr, 0);
}
return size * nmemb;
};
};
int main()
{
curl_global_init(~0); // << todo get proper constant
std::vector<Curl_Transfer *> transfers;
for (int i = 0; i < 5; ++i)
{
auto fck = new Curl_Transfer("http://speedtest.tele2.net/100MB.zip");
transfers.push_back((fck));
}
for (;;)
{
std::this_thread::sleep_for(std::chrono::seconds(5));
for (size_t i = 0; i < transfers.size(); ++i)
{
std::cout << "transfer #" << i << " is " << (double((transfers[i]->response_body.size()) / double(transfers[i]->expected_size))*100) << "% downloaded. running: " << (transfers[i]->running ? "true" : "false") << "\n";
}
}
}
there's probably a better way to do this though.. there has to be. but until someone smarter comes along, *this works at least...
apparently i did all the threading shit to avoid using the curl_multi api.. dafuq
you're using C, not C++... sorry, ofc you can do all of the above in C as well, but i'm not comfortable enough with C to enjoy re-writing that in C (anyone is free to re-write the code in C if they want to)
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.
I need to secure a section in my application making sure only the owner of the account can change settings. Is there any way to ask for users credentials like on the loginscreen to unlock the page in the application? Is it possible with PAM?
#include <security/pam_appl.h>
#include <security/pam_misc.h>
#include <stdio.h>
int main()
{
pam_handle_t *pamh;
struct pam_conv pamc;
pamc.conv = &misc_conv;
pamc.appdata_ptr = NULL;
pam_start("su", getenv("USER"), &pamc, &pamh);
if (pam_authenticate (pamh, 0) != PAM_SUCCESS)
{
printf("Nope!\n");
}
else
{
printf("Good job!\n");
}
pam_end(pamh, 0);
return 0;
}
I managed to put this together real quick. Is there a way to give pam the credentials myself? I want the passwort to be asked by GTK in a dialog and then pass it along to PAM if possible...
I have some code I'm writing under cygwin (using GCC) that successfully uses gethostbyname(); however when I try to use the resolver directly to retrieve the IP address of the DNS server it fails (all entries in nsaddr_list[] are null and nscount is -1).
If gethostbyname() is working, then obviously it is able to connect to the DNS server.
This code...
if (res_init() == -1) {
fprintf(stderr,"res_init() failed\n");
exit(1);
}
if (_res.nscount <= 0) {
fprintf(stderr,"nscount = %d\n",_res.nscount);
}
else {
for(i=0;i<_res.nscount;i++) {
fprintf(stderr, "dnssrvr: %d.%d.%d.%d\n",
(_res.nsaddr_list[i].sin_addr.s_addr & 0xff) >> 0,
(_res.nsaddr_list[i].sin_addr.s_addr & 0xff00) >> 8,
(_res.nsaddr_list[i].sin_addr.s_addr & 0xff0000) >> 16,
(_res.nsaddr_list[i].sin_addr.s_addr & 0xff000000) >> 24);
}
}
works on unix/linux, but returns nscount=-1 on cygwin.
Is there some trick to getting the DNS server when using cygwin/gcc?
res_init does not necessarily populate _res.nsaddr_list. Instead, on Windows it directs the resolver to use DnsQuery_A, unless you have the resolv.conf file, in which case DNS servers from that file are used.
See the source here, files minires.c and minires-os-if.c.
If you need to know your DNS servers, you probably have to use DnsQueryConfig or GetNetworkParams.
NB: _res is undocumented and should not be used.
UPDATE Apparently the "newer" (ca 2010 and later) versions of cygwin do populate _res.nsaddr_list, via a call to get_dns_info and then get_registry_dns. You may want to make sure that you have the newest cygwin, and if the problem persists, try to use a debug version and trace calls to the mentioned functions.
UPDATE 2 No, _res is not populated, my mistake.
As n.m. says, on Cygwin res_init() does not populate _res.nsaddr_list if it is using the Windows resolver. It uses the Windows resolver if either /etc/resolv.conf does not exist, or /etc/resolv.conf contains options osquery.
In my opinion this is a Cygwin bug - returning a negative nscount is bogus - but nonetheless we are stuck with working around it.
The solution is to call GetNetworkParams() just as Cygwin does itself - here's what I'm doing as a fallback:
#include <windows.h>
#include <iphlpapi.h>
#include <netinet/in.h>
#include <arpa/inet.h>
if (_res.nscount < 0)
{
ULONG buflen = 0;
FIXED_INFO *buf = NULL;
if (GetNetworkParams(NULL, &buflen) == ERROR_BUFFER_OVERFLOW)
buf = malloc(buflen);
if (buf && GetNetworkParams(buf, &buflen) == NO_ERROR)
{
_res.nscount = 1;
_res.nsaddr_list[0].sin_family = AF_INET;
_res.nsaddr_list[0].sin_addr.s_addr = inet_addr(buf->DnsServerList.IpAddress.String);
_res.nsaddr_list[0].sin_port = htons(53);
}
free(buf);
}
You need to link against -liphlpapi for the GetNetworkParams() function.
This only takes the first Windows DNS address, but if you want the rest of them you can follow the linked list that GetNetworkParams() returns. GetNetworkParams() only returns IPv4 addresses, I'm not sure what you're supposed to do if the machine has an IPv6 DNS server address configured.
I'm trying to develop simple RESTful api using FastCGI (and restcgi). When I tried to implement POST method I noticed that the input stream (representing request body) is wrong. I did a little test and looks like when I try to read the stream only every other character is received.
Body sent: name=john&surname=smith
Received: aejh&unm=mt
I've tried more clients just to make sure it's not the client messing with the data.
My code is:
int main(int argc, char* argv[]) {
// FastCGI initialization.
FCGX_Init();
FCGX_Request request;
FCGX_InitRequest(&request, 0, 0);
while (FCGX_Accept_r(&request) >= 0) {
// FastCGI request setup.
fcgi_streambuf fisbuf(request.in);
std::istream is(&fisbuf);
fcgi_streambuf fosbuf(request.out);
std::ostream os(&fosbuf);
std::string str;
is >> str;
std::cerr << str; // this way I can see it in apache error log
// restcgi code here
}
return 0;
}
I'm using fast_cgi module with apache (not sure if that makes any difference).
Any idea what am I doing wrong?
The problem is in fcgio.cpp
The fcgi_steambuf class is defined using char_type, but the int underflow() method downcasts its return value to (unsigned char), it should cast to (char_type).
I encountered this problem as well, on an unmodified Debian install.
I found that the problem went away if I supplied a buffer to the fcgi_streambuf constructor:
const size_t LEN = ... // whatever, it doesn't have to be big.
vector<char> v (LEN);
fcgi_streambuf buf (request.in, &v[0], v.size());
iostream in (&buf);
string s;
getline(in, s); // s now holds the correct data.
After finding no answer anywhere (not even FastCGI mailing list) I dumped the original fastcgi libraries and tried using fastcgi++ libraries instead. The problem disappeared. There are also other benefits - c++, more features, easier to use.
Use is.read() not is >> ...
Sample from restcgi documentation:
clen = strtol(clenstr, &clenstr, 10);
if (*clenstr)
{
cerr << "can't parse \"CONTENT_LENGTH="
<< FCGX_GetParam("CONTENT_LENGTH", request->envp)
<< "\"\n";
clen = STDIN_MAX;
}
// *always* put a cap on the amount of data that will be read
if (clen > STDIN_MAX) clen = STDIN_MAX;
*content = new char[clen];
is.read(*content, clen);
clen = is.gcount();