1--[[
2Copyright (c) 2019, Vsevolod Stakhov <vsevolod@highsecure.ru>
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15]]--
16
17--[[[
18-- @module lua_ffi/dkim
19-- This module contains ffi interfaces to DKIM
20--]]
21
22local ffi = require 'ffi'
23
24ffi.cdef[[
25struct rspamd_dkim_sign_context_s;
26struct rspamd_dkim_key_s;
27struct rspamd_task;
28enum rspamd_dkim_key_format {
29  RSPAMD_DKIM_KEY_FILE = 0,
30  RSPAMD_DKIM_KEY_PEM,
31  RSPAMD_DKIM_KEY_BASE64,
32  RSPAMD_DKIM_KEY_RAW,
33};
34enum rspamd_dkim_type {
35	RSPAMD_DKIM_NORMAL,
36	RSPAMD_DKIM_ARC_SIG,
37	RSPAMD_DKIM_ARC_SEAL
38};
39struct rspamd_dkim_sign_context_s*
40rspamd_create_dkim_sign_context (struct rspamd_task *task,
41    struct rspamd_dkim_key_s *priv_key,
42    int headers_canon,
43    int body_canon,
44    const char *dkim_headers,
45    enum rspamd_dkim_type type,
46    void *unused);
47struct rspamd_dkim_key_s* rspamd_dkim_sign_key_load (const char *what, size_t len,
48    enum rspamd_dkim_key_format,
49    void *err);
50void rspamd_dkim_key_unref (struct rspamd_dkim_key_s *k);
51struct GString *rspamd_dkim_sign (struct rspamd_task *task,
52    const char *selector,
53    const char *domain,
54    unsigned long expire,
55    size_t len,
56    unsigned int idx,
57    const char *arc_cv,
58    struct rspamd_dkim_sign_context_s *ctx);
59]]
60
61local function load_sign_key(what, format)
62  if not format then
63    format = ffi.C.RSPAMD_DKIM_KEY_PEM
64  else
65    if format == 'file' then
66      format = ffi.C.RSPAMD_DKIM_KEY_FILE
67    elseif format == 'base64' then
68      format = ffi.C.RSPAMD_DKIM_KEY_BASE64
69    elseif format == 'raw' then
70      format = ffi.C.RSPAMD_DKIM_KEY_RAW
71    else
72      return nil,'unknown key format'
73    end
74  end
75
76  return ffi.C.rspamd_dkim_sign_key_load(what, #what, format, nil)
77end
78
79local default_dkim_headers =
80"(o)from:(o)sender:(o)reply-to:(o)subject:(o)date:(o)message-id:" ..
81"(o)to:(o)cc:(o)mime-version:(o)content-type:(o)content-transfer-encoding:" ..
82"resent-to:resent-cc:resent-from:resent-sender:resent-message-id:" ..
83"(o)in-reply-to:(o)references:list-id:list-owner:list-unsubscribe:" ..
84"list-subscribe:list-post:(o)openpgp:(o)autocrypt"
85
86local function create_sign_context(task, privkey, dkim_headers, sign_type)
87  if not task or not privkey then
88    return nil,'invalid arguments'
89  end
90
91  if not dkim_headers then
92    dkim_headers = default_dkim_headers
93  end
94
95  if not sign_type then
96    sign_type = 'dkim'
97  end
98
99  if sign_type == 'dkim' then
100    sign_type = ffi.C.RSPAMD_DKIM_NORMAL
101  elseif sign_type == 'arc-sig' then
102    sign_type = ffi.C.RSPAMD_DKIM_ARC_SIG
103  elseif sign_type == 'arc-seal' then
104    sign_type = ffi.C.RSPAMD_DKIM_ARC_SEAL
105  else
106    return nil,'invalid sign type'
107  end
108
109
110  return ffi.C.rspamd_create_dkim_sign_context(task:topointer(), privkey,
111      1, 1, dkim_headers, sign_type, nil)
112end
113
114local function do_sign(task, sign_context, selector, domain,
115                       expire, len, arc_idx)
116  if not task or not sign_context or not selector or not domain then
117    return nil,'invalid arguments'
118  end
119
120  if not expire then expire = 0 end
121  if not len then len = 0 end
122  if not arc_idx then arc_idx = 0 end
123
124  local gstring = ffi.C.rspamd_dkim_sign(task:topointer(), selector, domain, expire, len, arc_idx, nil, sign_context)
125
126  if not gstring then
127    return nil,'cannot sign'
128  end
129
130  local ret = ffi.string(gstring.str, gstring.len)
131  ffi.C.g_string_free(gstring, true)
132
133  return ret
134end
135
136return {
137  load_sign_key = load_sign_key,
138  create_sign_context = create_sign_context,
139  do_sign = do_sign
140}
141