Thread overview
October 03

I want to try to make access via D to PAM. I'm trying to write basic things. I would like to know how to best transform access to callback functions? For example, so that there is no need to cast to a type and use binding to extern, move all this to a library?

extern(C):

struct pam_message {
    int msg_style;
    const(char) *msg;
}

struct pam_response {
    char *resp;
    int	resp_retcode;
}

alias conversation = int function(int num_msg, const pam_message **msg, pam_response **resp, void *appdata_ptr);

struct pam_conv {
    conversation *conv;
    void *appdata_ptr;
}

struct pam_handle;
alias pam_handle_t = pam_handle;

const (char) *pam_strerror(pam_handle_t *pamh, int errnum);

int pam_start(const(char) *service_name, const(char) *user, const pam_conv *pam_conversation, pam_handle_t **pamh);
int pam_authenticate(pam_handle_t *pamh, int flags);
int pam_end(pam_handle_t *pamh, int pam_status);

Below is the code that implements basic simple authentication. Is it possible to move conversation_func into a shell so as not to use type casting? What is the best way to implement this?

struct pam_data {
    string password;
}

extern(C) {
int conversation_func(int num_msg, const pam_message **msg, pam_response **resp, void *appdata_ptr) {
    pam_data *data = cast(pam_data*)appdata_ptr;

    *resp = cast(pam_response *)malloc(num_msg * pam_response.sizeof);
    if (*resp == null) {
        return PAM_BUF_ERR;
    }

    for (int i = 0; i < num_msg; i++) {
        switch (msg[i].msg_style) {
            case PAM_PROMPT_ECHO_ON:
            case PAM_PROMPT_ECHO_OFF:
                (*resp)[i].resp = strdup(data.password.toStringz);
                (*resp)[i].resp_retcode = 0;
                break;
            default:
                (*resp)[i].resp = null;
                (*resp)[i].resp_retcode = 0;
                break;
        }
    }

    return PAM_SUCCESS;
}
}

int authenticate_user(string username, string password) {
    pam_handle_t *pamh = null;
    int retval = 0;

    pam_data data = { password };
    void *appdata_ptr = &data;

    pam_conv conv = { cast(conversation*)&conversation_func, appdata_ptr };

    retval = pam_start("login", username.toStringz, &conv, &pamh);
    if (retval != PAM_SUCCESS) {
        writefln("pam_start: %s", pam_strerror(pamh, retval).to!string);
        return 1;
    }

    retval = pam_authenticate(pamh, 0);
    if (retval != PAM_SUCCESS) {
        writefln("Authentication failure: %s", pam_strerror(pamh, retval).to!string);
        pam_end(pamh, retval);
        return 2;
    }

    retval = pam_end(pamh, PAM_SUCCESS);
    if (retval != PAM_SUCCESS) {
        writefln("pam_end: %s", pam_strerror(pamh, retval).to!string);
        return 3;
    }

    return 0;
}

int main(string[] args) {
    string username = args[1];
    string password = args[2];

    int result = authenticate_user(username, password);
    if (result == 0) {
        writeln("Authentication succeeded!");
    } else {
        writefln("Authentication failed with code: %d", result);
    }

    return EXIT_SUCCESS;
}
October 03

On Thursday, 3 October 2024 at 22:54:53 UTC, Alexander Zhirov wrote:

>

I want to try to make access via D to PAM.

/// I added it here:
import pam_wrapper;
int main(string[] args)
{
   if (args.length < 3)
    {
        writeln("Usage: ", args[0], "<username> <password>");
        return 1;
    }
/// I added it here.
    string username = args[1];
    string password = args[2];

    int result = authenticate_user(username, password);
    if (result == 0) {
        writeln("Authentication succeeded!");
    } else {
        writefln("Authentication failed with code: %d", result);
    }

    return EXIT_SUCCESS;
}

It is possible to implement PAM (Pluggable Authentication Modules) support in the D programming language using the standard library. The D standard library provides extern(C) support to access C language APIs and a strong FFI (Foreign Function Interface) support to adapt to C data structures. However, D itself does not include a special module for PAM, so it is necessary to work with C-based PAM libraries in the D language.

I think you should use 2 separate modules! This can make type conversions and memory management safer. Here is the pam_wrapper.d file:

module pam_wrapper;

import pam; // You have this module.
import std.string, std.conv : to;
import core.stdc.string : strdup;
import core.stdc.stdlib : malloc, free;
public import std.stdio;

struct pam_data { string password; }

extern(C)
{
    int conversation_func(int num_msg, const pam_message **msg, pam_response **resp, void *appdata_ptr)
    {
        pam_data *data = cast(pam_data*)appdata_ptr;

        *resp = cast(pam_response *)malloc(num_msg * pam_response.sizeof);
        if (resp == null) return PAM_BUF_ERR;

        for (int i = 0; i < num_msg; i++)
        {
            switch (msg[i].msg_style)
            {
                case PAM_PROMPT_ECHO_ON:
                    goto case;
                case PAM_PROMPT_ECHO_OFF:
                    resp[i].resp = strdup(data.password.toStringz);
                    resp[i].resp_retcode = 0;
                    break;
                default:
                    resp[i].resp = null;
                    resp[i].resp_retcode = 0;
                    break;
            }
        }

        return PAM_SUCCESS;
    }
}

int authenticate_user(string username, string password)
{
    pam_handle_t *pamh = null;
    int retval = 0;

    pam_data data = { password };
    void *appdata_ptr = &data;

    pam_conv conv = { cast(conversation*)&conversation_func, appdata_ptr };

    retval = pam_start("login", username.toStringz, &conv, &pamh);
    if (retval != PAM_SUCCESS)
    {
        pam_strerror(pamh, retval).to!string.writefln!"pam_start: %s";
        return 1;
    }

    retval = pam_authenticate(pamh, 0);
    if (retval != PAM_SUCCESS)
    {
        pam_strerror(pamh, retval).to!string.writefln!"Authentication failure: %s";
        pam_end(pamh, retval);
        return 2;
    }

    retval = pam_end(pamh, PAM_SUCCESS);
    if (retval != PAM_SUCCESS)
    {
        pam_strerror(pamh, retval).to!string.writefln!"pam_end: %s";
        return 3;
    }

    return 0;
}

SDB@79

October 04

On Thursday, 3 October 2024 at 23:56:37 UTC, Salih Dincer wrote

I meant taking the function to D for the elements of the C syntax. To get only an API to which we can pass our callback, and hide everything else inside the wrapper. I have no experience porting from C to D, but I would like to try to learn the basics to make a small library for PAM.

October 04

On Friday, 4 October 2024 at 12:39:45 UTC, Alexander Zhirov wrote:

>

On Thursday, 3 October 2024 at 23:56:37 UTC, Salih Dincer wrote

I meant taking the function to D for the elements of the C syntax. To get only an API to which we can pass our callback, and hide everything else inside the wrapper. I have no experience porting from C to D, but I would like to try to learn the basics to make a small library for PAM.

https://dlang.org/spec/importc.html

October 07

I tried to build a class with a private function conversation_func to call it inside the public authentication function. When compiling I get this message:

source/login/auth.d(87,46): Deprecation: casting from extern (C) int delegate(int num_msg, const(pam_message**) msg, pam_response** resp, void* appdata_ptr) to extern (C) int function(int, const(pam_message**), pam_response**, void*)* is deprecated

And when I start up, at the moment of calling the authentication function, I get Segmentation fault.

Is the function identified differently in the class? How can this be fixed?

October 07

On Monday, 7 October 2024 at 08:23:08 UTC, Alexander Zhirov wrote:

module login.auth;

import libpam;

enum {
    AUTH_SUCCESS = 0,
    AUTH_ERR_USER = 1,
    AUTH_ERR_PASS = 2,
    AUTH_ERR_NPASS = 3,
    AUTH_ERR_START = 4,
    AUTH_ERR_AUTH = 5,
    AUTH_ERR_ACCT = 6,
    AUTH_ERR_CHTOK = 7,
    AUTH_ERR_END = 8
}

class Auth {
private:
    struct PAMdata {
        string password;
        string newPassword;
    }

    extern(C) {
        int conversation_func(int num_msg, const pam_message **msg, pam_response **resp, void *appdata_ptr) {
            PAMdata *data = cast(PAMdata*)appdata_ptr;

            pam_response *responses = cast(pam_response *)calloc(num_msg, pam_response.sizeof);

            if (responses == null) {
                return PAM_BUF_ERR;
            }

            for (int count = 0; count < num_msg; ++count) {
                responses[count].resp_retcode = 0;
                switch (msg[count].msg_style) {
                    case PAM_PROMPT_ECHO_ON:
                    case PAM_PROMPT_ECHO_OFF:
                        switch (msg[count].msg.to!string) {
                            case "New password: ":
                            case "Retype new password: ":
                                responses[count].resp = strdup(data.newPassword.toStringz);
                                break;
                            case "Password: ":
                            case "Current password: ":
                                responses[count].resp = strdup(data.password.toStringz);
                                break;
                            default:
                                responses[count].resp = null;
                                break;
                        }
                        break;
                    default:
                        responses[count].resp = null;
                        break;
                }
            }

            *resp = responses;

            return PAM_SUCCESS;
        }
    }
public:
    int authenticate(string username, string password) {
        if (!username.length) {
            return AUTH_ERR_USER;
        }

        if (!password.length) {
            return AUTH_ERR_PASS;
        }

        pam_handle_t *pamh = null;
        int retval = 0;

        PAMdata data = { password };
        void *appdata_ptr = &data;

        pam_conv conv = { cast(conversation*)&this.conversation_func, appdata_ptr };

        retval = pam_start("login", username.toStringz, &conv, &pamh);
        if (retval != PAM_SUCCESS) {
            return AUTH_ERR_START;
        }

        retval = pam_authenticate(pamh, 0);
        if (retval != PAM_SUCCESS) {
            pam_end(pamh, retval);
            return AUTH_ERR_AUTH;
        }

        retval = pam_end(pamh, PAM_SUCCESS);
        if (retval != PAM_SUCCESS) {
            return AUTH_ERR_END;
        }

        return AUTH_SUCCESS;
    }
}

October 07

On Monday, 7 October 2024 at 08:23:08 UTC, Alexander Zhirov wrote:

>

I tried to build a class with a private function conversation_func to call it inside the public authentication function. When compiling I get this message:

source/login/auth.d(87,46): Deprecation: casting from extern (C) int delegate(int num_msg, const(pam_message**) msg, pam_response** resp, void* appdata_ptr) to extern (C) int function(int, const(pam_message**), pam_response**, void*)* is deprecated

And when I start up, at the moment of calling the authentication function, I get Segmentation fault.

Is the function identified differently in the class? How can this be fixed?

Of course it segfaults, you are trying to give delegate pretending it is to be a standalone function.

For example you can fix it by using a trampoline - Make a global class instance somewhere, make conversation_func static (if you absolutely want it to be in class) and then in that function use instance.actual_conversation_func, that will do actual work.

Maybe other solutions exists that is not variation of this method by I can't come up with anything else.

October 10

On Monday, 7 October 2024 at 08:23:08 UTC, Alexander Zhirov wrote:

>

I tried to build a class with a private function conversation_func to call it inside the public authentication function. When compiling I get this message:

source/login/auth.d(87,46): Deprecation: casting from extern (C) int delegate(int num_msg, const(pam_message**) msg, pam_response** resp, void* appdata_ptr) to extern (C) int function(int, const(pam_message**), pam_response**, void*)* is deprecated

And when I start up, at the moment of calling the authentication function, I get Segmentation fault.

Is the function identified differently in the class? How can this be fixed?

Would you try static this?

// 1. Salih changed it:
extern(C) static int conversation_func(int num_msg, const pam_message **msg, pam_response **resp, void *appdata_ptr)
{ /* ... */}

There is no need for this.

// 2. Salih changed it:
pam_conv conv = {
  cast(conversation*)&/*this.*/conversation_func,
  appdata_ptr
};

SDB@79

October 10

On Thursday, 10 October 2024 at 12:58:22 UTC, Salih Dincer wrote:

class Auth
{
  private:
    struct PAMdata
    {
      string password;
      string newPassword;
    }

    extern(C)
    { // 1. Salih changed it:
      static int conversation_func(int num_msg,
                       const pam_message **msg,
                           pam_response **resp,
                             void *appdata_ptr)
      {
        auto data = cast(PAMdata*)appdata_ptr;
        auto responses = cast(pam_response*)
          calloc(num_msg, pam_response.sizeof);

        if (responses == null)
        {
          return PAM_BUF_ERR;
        }

        for (int i = 0; i < num_msg; ++i)
        {
          responses[i].resp_retcode = 0;
          switch (msg[i].msg_style)
          {
            case PAM_PROMPT_ECHO_ON: goto case;
            case PAM_PROMPT_ECHO_OFF:
              switch (msg[i].msg.to!string)
              {
                case "New password: ": goto case;
                case "Retype new password: ":
                  responses[i].resp = strdup(data.newPassword.toStringz);
                  break;
                case "Password: ": goto case;
                case "Current password: ":
                  responses[i].resp = strdup(data.password.toStringz);
                  break;
                default:
                  responses[i].resp = null;
                  break;
              }
              break;
            default:
              responses[i].resp = null;
              break;
          }
        }
        *resp = responses;
        return PAM_SUCCESS;
      }
    }

  public:
    int authenticate(string username, string password)
    {
      if (!username.length)
      {
        return AUTH_ERR_USER;
      }

      if (!password.length)
      {
        return AUTH_ERR_PASS;
      }

      pam_handle_t *pamh = null;
      PAMdata data = {
        password
      };

      void *appdata_ptr = &data;
      // 2. Salih changed it:
      pam_conv conv = {
        cast(conversation*)&conversation_func,
        appdata_ptr
      };
      // 3. Salih changed it:
      auto retval = pam_start("login", username.toStringz, &conv, &pamh);
      if (retval != PAM_SUCCESS)
      {
        return AUTH_ERR_START;
      }

      retval = pam_authenticate(pamh, 0);
      if (retval != PAM_SUCCESS)
      {
        pam_end(pamh, retval);
        return AUTH_ERR_AUTH;
      }

      retval = pam_end(pamh, PAM_SUCCESS);
      if (retval != PAM_SUCCESS)
      {
        return AUTH_ERR_END;
      }
      return AUTH_SUCCESS;
    }
}

SDB@79

October 12

On Thursday, 10 October 2024 at 13:10:58 UTC, Salih Dincer wrote:

>

On Thursday, 10 October 2024 at 12:58:22 UTC, Salih Dincer wrote:

class Auth
{
  private:
    struct PAMdata
    {
      string password;
      string newPassword;
    }

    extern(C)
    { // 1. Salih changed it:
      static int conversation_func(int num_msg,
                       const pam_message **msg,
                           pam_response **resp,
                             void *appdata_ptr)
      {
        auto data = cast(PAMdata*)appdata_ptr;
        auto responses = cast(pam_response*)
          calloc(num_msg, pam_response.sizeof);

        if (responses == null)
        {
          return PAM_BUF_ERR;
        }

        for (int i = 0; i < num_msg; ++i)
        {
          responses[i].resp_retcode = 0;
          switch (msg[i].msg_style)
          {
            case PAM_PROMPT_ECHO_ON: goto case;
            case PAM_PROMPT_ECHO_OFF:
              switch (msg[i].msg.to!string)
              {
                case "New password: ": goto case;
                case "Retype new password: ":
                  responses[i].resp = strdup(data.newPassword.toStringz);
                  break;
                case "Password: ": goto case;
                case "Current password: ":
                  responses[i].resp = strdup(data.password.toStringz);
                  break;
                default:
                  responses[i].resp = null;
                  break;
              }
              break;
            default:
              responses[i].resp = null;
              break;
          }
        }
        *resp = responses;
        return PAM_SUCCESS;
      }
    }

  public:
    int authenticate(string username, string password)
    {
      if (!username.length)
      {
        return AUTH_ERR_USER;
      }

      if (!password.length)
      {
        return AUTH_ERR_PASS;
      }

      pam_handle_t *pamh = null;
      PAMdata data = {
        password
      };

      void *appdata_ptr = &data;
      // 2. Salih changed it:
      pam_conv conv = {
        cast(conversation*)&conversation_func,
        appdata_ptr
      };
      // 3. Salih changed it:
      auto retval = pam_start("login", username.toStringz, &conv, &pamh);
      if (retval != PAM_SUCCESS)
      {
        return AUTH_ERR_START;
      }

      retval = pam_authenticate(pamh, 0);
      if (retval != PAM_SUCCESS)
      {
        pam_end(pamh, retval);
        return AUTH_ERR_AUTH;
      }

      retval = pam_end(pamh, PAM_SUCCESS);
      if (retval != PAM_SUCCESS)
      {
        return AUTH_ERR_END;
      }
      return AUTH_SUCCESS;
    }
}

SDB@79

Thanks for the example. I'll try to test it.