scram_rs/c_api/
client.rs

1/*-
2 * Scram-rs - a SCRAM authentification authorization library
3 * 
4 * Copyright (C) 2021  Aleksandr Morozov
5 * Copyright (C) 2025 Aleksandr Morozov
6 * 
7 * The syslog-rs crate can be redistributed and/or modified
8 * under the terms of either of the following licenses:
9 *
10 *   1. the Mozilla Public License Version 2.0 (the “MPL”) OR
11 *
12 *   2. The MIT License (MIT)
13 *                     
14 *   3. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
15 */
16
17 
18#[cfg(feature = "std")]
19use std::ffi::CString;
20#[cfg(not(feature = "std"))]
21use alloc::ffi::CString;
22use base64::{engine::general_purpose, Engine};
23
24#[cfg(feature = "std")]
25use std::ffi::c_char;
26#[cfg(not(feature = "std"))]
27use core::ffi::c_char;
28
29
30
31#[cfg(feature = "std")]
32use std::str;
33#[cfg(not(feature = "std"))]
34use core::str;
35
36
37#[cfg(not(feature = "std"))]
38use alloc::string::String;
39
40#[cfg(not(feature = "std"))]
41use alloc::boxed::Box;
42
43#[cfg(feature = "use_ring")]
44use crate::{ScramSha1Ring, ScramSha256Ring, ScramSha512Ring};
45
46use crate::
47{
48    capi_scram_err_map,
49    scram_sync::SyncScramClient, 
50    ChannelBindType, 
51    ScramAuthClient, 
52    ScramCbHelper, 
53    ScramClientDyn, 
54    ScramKey, 
55    ScramNonce, 
56    ScramResultClient, 
57    ScramSha1RustNative, 
58    ScramSha256RustNative, 
59    ScramSha512RustNative, 
60    ScramTypeAlias, 
61    SCRAM_TYPES
62};
63
64use super::
65{
66    common, 
67    error::{CApiScramResult, CApiScramRuntimeError}, 
68    nonce::CApiNonce, 
69    CApiScramHasherProvider
70};
71
72const CLIENT_MARKER: u64 = 0xc0FFEEAA_12345678;
73
74#[derive(Debug)]
75pub struct CApiAuthClient
76{
77    username: String,
78    authzid: Option<String>,
79    password: String,
80    key: Option<Box<ScramKey>>,
81}
82
83impl ScramAuthClient for CApiAuthClient
84{
85    fn get_username(&self) -> &str
86    {
87        return &self.username;
88    }
89
90    fn get_authzid(&self) -> Option<&str> 
91    {
92        return self.authzid.as_ref().map(|v| v.as_str());
93    }
94
95    fn get_password(&self) -> &str
96    {
97        return &self.password;
98    }
99
100    fn get_scram_keys(&self) -> Option<&ScramKey> 
101    {
102        return self.key.as_ref().map(|v| v.as_ref());
103    }
104}
105
106
107impl CApiAuthClient
108{ 
109    fn new(
110        plain_username: *const c_char,
111        plain_authzid: *const c_char,
112        plain_password: *const c_char,
113        scram_key: *mut ScramKey,
114    ) -> CApiScramResult<Self>
115    {
116        let p_username = 
117            common::cstr_to_string(plain_username, "plain text username")?;
118
119        let p_authzid = 
120            if plain_authzid.is_null() == false
121            {
122                Some(common::cstr_to_string(plain_authzid, "plain text username")?)
123            }
124            else
125            {
126                None            
127            };
128            
129        let p_password = 
130            common::cstr_to_string(plain_password, "plain text password")?;
131
132        let p_key = 
133            if scram_key.is_null() == false
134            {
135                Some(unsafe{ Box::from_raw(scram_key) })
136            }
137            else
138            {
139                None
140            };
141
142        let ret =  
143            Self
144            {
145                username: p_username,
146                authzid: p_authzid,
147                password: p_password,
148                key: p_key
149            };
150
151        return Ok(ret);
152    }
153}
154
155#[derive(Debug)]
156struct CApiCbClient {}
157
158impl ScramCbHelper for CApiCbClient
159{
160    
161}
162
163impl CApiCbClient
164{
165    fn new() -> Self
166    {
167        return Self{};
168    }
169}
170
171#[repr(C)]
172pub struct CApiScramClient
173{
174    dyn_client: Box<dyn ScramClientDyn>,
175    marker: u64,
176}
177
178impl CApiScramClient
179{
180    pub 
181    fn new(client: Box<dyn ScramClientDyn>) -> *mut Self
182    {
183        let api_client = 
184            CApiScramClient
185            {
186                dyn_client: client,
187                marker: CLIENT_MARKER,
188            };
189
190        return Box::into_raw(Box::new(api_client));
191    }
192}
193
194
195#[unsafe(no_mangle)]
196pub unsafe extern "C" 
197fn capi_scram_client_init(
198    hash_provider: CApiScramHasherProvider,
199    scram_type: *const c_char,
200    plain_username: *const c_char,
201    plain_authzid: *const c_char,
202    plain_password: *const c_char,
203    scram_key: *mut ScramKey,
204    client_nonce: *mut CApiNonce,
205    ignore_exts: bool,
206    client: *mut *mut CApiScramClient,
207    error: *mut *mut CApiScramRuntimeError
208) -> i32
209{
210    if client.is_null() == true
211    {
212        return -7;
213    }
214    else if hash_provider.validate() == false
215    {
216        return -1;
217    }
218
219    let scram_type_res  =
220        match common::cstr_to_string(scram_type, "scram_type is not valid")
221        {
222            Ok(r) => r,
223            Err(e) =>
224            {
225                unsafe 
226                { 
227                    if let Some(err) = error.as_mut()
228                    {
229                        *err = e.into_capi();
230                    }
231                }
232
233                return 1;
234            }
235        };
236
237    let scramtype = 
238        match SCRAM_TYPES.get_scramtype(scram_type_res).map_err(|e|capi_scram_err_map!(e))
239        {
240            Ok(r) => r,
241            Err(e) =>
242            {
243                unsafe 
244                { 
245                    if let Some(err) = error.as_mut()
246                    {
247                        *err = e.into_capi();
248                    }
249                }
250
251                return 1;
252            }
253        };
254
255    let nonce = 
256        if client_nonce.is_null() == true
257        {
258            match ScramNonce::none().map_err(|e| capi_scram_err_map!(e))
259            {
260                Ok(r) => r,
261                Err(e) =>
262                {
263                    unsafe 
264                    { 
265                        if let Some(err) = error.as_mut()
266                        {
267                            *err = e.into_capi();
268                        }
269                    }
270
271                    return 1;
272                }
273            }
274        }
275        else
276        {
277            unsafe 
278            { 
279                match client_nonce.as_mut().unwrap().take()
280                {
281                    Ok(r) => r,
282                    Err(e) =>
283                    {
284                        if let Some(err) = error.as_mut()
285                        {
286                            *err = e.into_capi();
287                        }
288
289                        return 1;
290                    }
291                }
292            }
293        };
294
295    let auth = 
296        match CApiAuthClient::new(plain_username, plain_authzid, plain_password, scram_key)
297        {
298            Ok(r) => r,
299            Err(e) =>
300            {
301                unsafe 
302                { 
303                    if let Some(err) = error.as_mut()
304                    {
305                        *err = e.into_capi();
306                    }
307                }
308
309                return 1;
310            }
311        };
312
313    let clientcb = CApiCbClient::new();
314    
315    let res = 
316        match (hash_provider, scramtype.scram_alias)
317        {
318            #[cfg(not(feature = "exclude_sha1"))]
319            (CApiScramHasherProvider::RustNative, ScramTypeAlias::Sha1) =>
320            {
321                SyncScramClient
322                    ::<ScramSha1RustNative, CApiAuthClient, CApiCbClient>
323                    ::new(auth, nonce, ChannelBindType::None, clientcb, ignore_exts)
324                        .map_err(|e|
325                            capi_scram_err_map!(e)
326                        )
327                        .map(|c| c.make_dyn())
328            },
329            (CApiScramHasherProvider::RustNative, ScramTypeAlias::Sha256 | ScramTypeAlias::Sha256Plus) => 
330            {
331                SyncScramClient
332                    ::<ScramSha256RustNative, CApiAuthClient, CApiCbClient>
333                    ::new(auth, nonce, ChannelBindType::None, clientcb, ignore_exts)
334                        .map_err(|e|
335                            capi_scram_err_map!(e)
336                        )
337                        .map(|c| c.make_dyn())
338            },
339            (CApiScramHasherProvider::RustNative, ScramTypeAlias::Sha512 | ScramTypeAlias::Sha512Plus) =>
340            {
341                SyncScramClient
342                    ::<ScramSha512RustNative, CApiAuthClient, CApiCbClient>
343                    ::new(auth, nonce, ChannelBindType::None, clientcb, ignore_exts)
344                        .map_err(|e|
345                            capi_scram_err_map!(e)
346                        )
347                        .map(|c| c.make_dyn())
348            },
349
350            #[cfg(all(feature = "use_ring", not(feature = "exclude_sha1")))]
351            (CApiScramHasherProvider::RustRing, ScramTypeAlias::Sha1) =>
352            {
353                SyncScramClient
354                    ::<ScramSha1Ring, CApiAuthClient, CApiCbClient>
355                    ::new(auth, nonce, ChannelBindType::None, clientcb, ignore_exts)
356                        .map_err(|e|
357                            capi_scram_err_map!(e)
358                        )
359                        .map(|c| c.make_dyn())
360            },
361
362            #[cfg(all(not(feature = "use_ring"), not(feature = "exclude_sha1")))]
363            (CApiScramHasherProvider::RustRing, ScramTypeAlias::Sha1) =>
364            {
365                crate::capi_scram_err_res!(crate::ScramErrorCode::FeatureNotSupported, crate::ScramServerError::None, "Sha1Ring not available")
366            },
367
368            #[cfg(feature = "use_ring")]
369            (CApiScramHasherProvider::RustRing, ScramTypeAlias::Sha256 | ScramTypeAlias::Sha256Plus) =>
370            {
371                SyncScramClient
372                    ::<ScramSha256Ring, CApiAuthClient, CApiCbClient>
373                    ::new(auth, nonce, ChannelBindType::None, clientcb, ignore_exts)
374                        .map_err(|e|
375                            capi_scram_err_map!(e)
376                        )
377                        .map(|c| c.make_dyn())
378            },
379
380            #[cfg(not(feature = "use_ring"))]
381            (CApiScramHasherProvider::RustRing, ScramTypeAlias::Sha256 | ScramTypeAlias::Sha256Plus) =>
382            {
383                crate::capi_scram_err_res!(crate::ScramErrorCode::FeatureNotSupported, crate::ScramServerError::None, "Sha256Ring not available")
384            },
385
386            #[cfg(feature = "use_ring")]
387            (CApiScramHasherProvider::RustRing, ScramTypeAlias::Sha512 | ScramTypeAlias::Sha512Plus) =>
388            {
389                SyncScramClient
390                    ::<ScramSha512Ring, CApiAuthClient, CApiCbClient>
391                    ::new(auth, nonce, ChannelBindType::None, clientcb, ignore_exts)
392                        .map_err(|e|
393                            capi_scram_err_map!(e)
394                        )
395                        .map(|c| c.make_dyn())
396            },
397
398            #[cfg(not(feature = "use_ring"))]
399            (CApiScramHasherProvider::RustRing, ScramTypeAlias::Sha512 | ScramTypeAlias::Sha512Plus) =>
400            {
401                crate::capi_scram_err_res!(crate::ScramErrorCode::FeatureNotSupported, crate::ScramServerError::None, "Sha512Ring not available")
402            }
403        };
404
405    if res.is_ok() == true
406    {
407        unsafe { *client.as_mut().unwrap() = CApiScramClient::new(res.ok().unwrap()) };
408        return 0;
409    }
410    else
411    {
412        unsafe 
413        { 
414            if let Some(err) = error.as_mut()
415            {
416                *err = res.err().unwrap().into_capi();
417            }
418        }
419
420        return 1;
421    };
422}   
423
424
425
426
427#[unsafe(no_mangle)]
428pub unsafe extern "C" 
429fn capi_scram_client_free(client: *mut CApiScramClient)
430{
431    if client.is_null() == true
432    {
433        return;
434    }
435
436    let marker = unsafe { client.as_ref().unwrap().marker };
437    if marker != CLIENT_MARKER
438    {
439        panic!("Assertion trap: invalid client marker: {:X} {:X}", marker, CLIENT_MARKER);
440    }
441
442    let boxed_client = unsafe { Box::from_raw(client)};
443
444
445    drop(boxed_client);
446
447    return;
448}
449
450#[derive(PartialEq, Eq)]
451pub struct CApiScramResultClient
452{
453    res: ScramResultClient,
454    plain: Option<CString>,
455    base64: Option<CString>,
456}
457
458impl From<ScramResultClient> for CApiScramResultClient
459{
460    fn from(value: ScramResultClient) -> Self 
461    {
462        return Self{ res: value, plain: None, base64: None };
463    }
464}
465
466#[unsafe(no_mangle)]
467pub unsafe extern "C" 
468fn capi_scram_client_result_is_final(msg_res: *mut CApiScramResultClient) -> bool
469{
470    if msg_res.is_null() == true
471    {
472        return false;
473    }
474
475    return ScramResultClient::Completed == unsafe { msg_res.as_ref().unwrap()}.res;
476}
477
478#[unsafe(no_mangle)]
479pub unsafe extern "C" 
480fn capi_scram_client_result_plain(msg_res: *mut CApiScramResultClient, msg_raw_out: *mut *const c_char) -> i32
481{
482    if msg_res.is_null() == true
483    {
484        return -1;
485    }
486    else if msg_raw_out.is_null() == true
487    {
488        return -1;
489    }
490
491    let this = unsafe { msg_res.as_mut().unwrap()};
492
493    if this.res.is_output() == false
494    {
495        return 1;
496    };
497    
498    
499
500    let plain_msg = 
501        if let Some(plain) = this.plain.as_ref()
502        {
503            plain.as_ptr()
504        }
505        else
506        {
507            let plain = CString::new(this.res.get_output().unwrap().as_bytes()).unwrap();
508            let ret_plain = plain.as_ptr();
509            this.plain.replace(plain);
510
511            ret_plain
512        };
513
514    unsafe { *(msg_raw_out.as_mut().unwrap()) = plain_msg };
515
516    return 0;
517}
518
519#[unsafe(no_mangle)]
520pub unsafe extern "C" 
521fn capi_scram_client_result_base64(msg_res: *mut CApiScramResultClient, msg_base64_out: *mut *const c_char) -> i32
522{
523    if msg_res.is_null() == true
524    {
525        return -1;
526    }
527    else if msg_base64_out.is_null() == true
528    {
529        return -2;
530    }
531
532    let this = unsafe { msg_res.as_mut().unwrap() };
533
534    if this.res.is_output() == false
535    {
536        return 1;
537    };
538
539    let base_msg = 
540        if let Some(base) = this.base64.as_ref()
541        {
542            base.as_ptr()
543        }
544        else
545        {
546            let base = 
547                CString::new(
548                    general_purpose::STANDARD.encode(this.res.get_output().unwrap().as_bytes())
549                )
550                .unwrap();
551
552            let ret_base = base.as_ptr();
553            this.base64.replace(base);
554
555            ret_base
556        };
557
558    unsafe { *msg_base64_out = base_msg };
559    
560    return 0;
561}
562
563#[unsafe(no_mangle)]
564pub unsafe extern "C" 
565fn capi_scram_client_result_free(msg_res: *mut CApiScramResultClient)
566{
567    if msg_res.is_null() == true
568    {
569        return;
570    }
571
572    drop(unsafe { Box::from_raw(msg_res) });
573
574    return;
575}
576
577#[unsafe(no_mangle)]
578pub unsafe extern "C" 
579fn capi_scram_client_init_msg(client: *mut CApiScramClient, init_msg: *mut *mut CApiScramResultClient) -> i32
580{
581    if client.is_null() == true
582    {
583        return -1;
584    }
585    else if init_msg.is_null() == true
586    {
587        return -1;
588    }
589
590    let msg = 
591        unsafe { client.as_mut().unwrap().dyn_client.init_client_msg()};
592
593    unsafe 
594    { 
595        *(init_msg.as_mut().unwrap()) = Box::into_raw(Box::new(CApiScramResultClient::from(msg)));
596    }
597
598    return 0;
599}
600
601#[unsafe(no_mangle)]
602pub unsafe extern "C" 
603fn capi_scram_client_parse_resp_plain(client: *mut CApiScramClient, resp_plain: *const c_char, 
604    res_out: *mut *mut CApiScramResultClient, error_out: *mut *mut CApiScramRuntimeError) -> i32
605{
606    if client.is_null() == true
607    {
608        return -1;
609    }
610    else if res_out.is_null() == true
611    {
612        return -3;
613    }
614
615    let resp_res = 
616        match common::cstr_to_string(resp_plain, "resp_plain is not valid")
617        {
618            Ok(r) => r,
619            Err(e) => 
620            {
621                unsafe 
622                { 
623                    if let Some(err) = error_out.as_mut()
624                    {
625                        *err = e.into_capi();
626                    }
627                }
628
629                return 1;
630            }
631        };
632            
633
634    let msg = 
635        unsafe 
636        { 
637            client
638                .as_mut()
639                .unwrap()
640                .dyn_client
641                .parse_response(&resp_res)
642                .map_err(|e|
643                    capi_scram_err_map!(e)
644                )
645                .map(|res| 
646                    Box::into_raw(Box::new(CApiScramResultClient::from(res)))
647                )
648        };
649
650    if msg.is_ok() == true
651    {
652        unsafe { *res_out.as_mut().unwrap() = msg.ok().unwrap() };
653        
654        return 0;
655    }
656    else
657    {
658        unsafe 
659        { 
660            if let Some(err) = error_out.as_mut()
661            {
662                *err = msg.err().unwrap().into_capi();
663            }
664        }
665
666        return 1;
667    }
668}
669
670
671#[unsafe(no_mangle)]
672pub unsafe extern "C" 
673fn capi_scram_client_parse_resp_base64(client: *mut CApiScramClient, resp_b64: *const u8, resp_len: usize,
674    res_out: *mut *mut CApiScramResultClient, error_out: *mut *mut CApiScramRuntimeError) -> i32
675{
676    if client.is_null() == true
677    {
678        return -1;
679    }
680    else if res_out.is_null() == true
681    {
682        return -4;
683    }
684
685
686    let resp = 
687        match common::carr_to_slice(resp_b64, resp_len, "resp is not valid")
688        {
689            Ok(r) => r,
690            Err(e) => 
691            {
692                unsafe 
693                { 
694                    if let Some(err) = error_out.as_mut()
695                    {
696                        *err = e.into_capi();
697                    }
698                }
699
700                return 1;
701            }
702        };      
703
704    let msg = 
705        unsafe 
706        { 
707            client
708                .as_mut()
709                .unwrap()
710                .dyn_client
711                .parse_response_base64(resp)
712                .map_err(|e: crate::ScramRuntimeError|
713                    capi_scram_err_map!(e)
714                )
715                .map(|res| Box::into_raw(Box::new(CApiScramResultClient::from(res))))
716        };
717
718    if msg.is_ok() == true
719    {
720        unsafe { *res_out.as_mut().unwrap() = msg.ok().unwrap() };
721        
722        return 0;
723    }
724    else
725    {
726        unsafe 
727        { 
728            if let Some(err) = error_out.as_mut()
729            {
730                *err = msg.err().unwrap().into_capi();
731            }
732        }
733
734        return 1;
735    }
736}
737