use base64 instead of hex
[pam_pcsc_cr.git] / authfile.c
1 /*
2 Copyright (c) 2013 Eugene Crosser
3
4 This software is provided 'as-is', without any express or implied
5 warranty. In no event will the authors be held liable for any damages
6 arising from the use of this software.
7
8 Permission is granted to anyone to use this software for any purpose,
9 including commercial applications, and to alter it and redistribute it
10 freely, subject to the following restrictions:
11
12     1. The origin of this software must not be misrepresented; you must
13     not claim that you wrote the original software. If you use this
14     software in a product, an acknowledgment in the product documentation
15     would be appreciated but is not required.
16
17     2. Altered source versions must be plainly marked as such, and must
18     not be misrepresented as being the original software.
19
20     3. This notice may not be removed or altered from any source
21     distribution.
22 */
23
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <pwd.h>
30 #include <time.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <errno.h>
36 #include <alloca.h>
37 #include "base64.h"
38 #include "authobj.h"
39 #include "authfile.h"
40
41 /*
42  * Template string may contain zero or one '~' and zero or one '?'.
43  * '~' at the beginning of the template string is substituted with
44  * the home directory of the userid. In any other position it is
45  * substituted with the userid itself. '?' is substituted with the
46  * tokenid. There is no way to make the resulting path contain '~'
47  * or '?'. If there is more than one '~' or '?', or if the '~' is
48  * at the beginning but userid does not resolve via getpwnam, or
49  * the character to substitute is present but the argument is NULL,
50  * NULL is returned. Otherwise, malloc()'ed area containg the path
51  * string.
52  */
53
54 static const char *template = "~/.pam_cr/auth";
55
56 void authfile_template(const char *str)
57 {
58         template = str;
59 }
60
61 static int path_size(const char *tokenid, const char *userid)
62 {
63         const char *usub;
64         const char *p, *q;
65         struct passwd *pw;
66
67         if ((p = strchr(template, '~')) != strrchr(template, '~')) return 0;
68         if ((q = strchr(template, '?')) != strrchr(template, '?')) return 0;
69         if (p && !userid) return 0;
70         if (q && !tokenid) return 0;
71         if (p == template) {
72                 pw = getpwnam(userid);
73                 if (!pw) return 0;
74                 usub = pw->pw_dir;
75         } else {
76                 usub = userid;
77         }
78         return strlen(template)+(p?strlen(usub):0)+(q?strlen(tokenid):0)+1;
79 }
80
81 static void
82 make_path(char * const path, const char *tokenid, const char *userid)
83 {
84         const char *usub;
85         const char *p;
86         char *q;
87         struct passwd *pw;
88
89         path[0] = '\0';
90         if (template[0] == '~') {
91                 pw = getpwnam(userid);
92                 if (!pw) return;
93                 usub = pw->pw_dir;
94         } else {
95                 usub = userid;
96         }
97         q = path;
98         for (p = template; *p; p++) switch (*p) {
99         case '~':
100                 strcpy(q, usub);
101                 while (*q) q++;
102                 break;
103         case '?':
104                 strcpy(q, tokenid);
105                 while (*q) q++;
106                 break;
107         default:
108                 *q++ = *p;
109                 break;
110         }
111         *q = '\0';
112 }
113
114 int parse(char * const buf, const int argc, const char *argv[const])
115 {
116         char *p, *q;
117         int i;
118
119         for (i = 0, p = buf; *p; p = q+1, i++) {
120                 for (q = p; *q && *q != ':' && *q != '\r' && *q != '\n'; q++) ;
121                 *q = '\0';
122                 if (*p && i < argc) argv[i] = p;
123         }
124         return i != argc;
125 }
126
127 struct _auth_obj authfile(const char *tokenid,
128                 const char *userid, const char *password,
129                 void (*update_nonce)(char *nonce, const int nonsize),
130                 const unsigned char *secret, const int secsize,
131                 const unsigned char *payload, const int paylsize,
132                 struct _auth_chunk (*fetch_key)(const unsigned char *chal,
133                                                 const int csize))
134 {
135         struct _auth_obj ret = {0};
136         mode_t oldmask;
137         FILE *fp = NULL;
138         char *fn, *nfn;
139         int fnl;
140         struct stat st = {0};
141         char *buf = NULL;
142         struct {
143                 const char *tokenid;
144                 const char *userid;
145                 const char *nonce;
146                 const char *hablob;
147         } w = {"", NULL, NULL, NULL};
148         unsigned char *ablob = NULL;
149         int blobsize = 0;
150         char *newnonce;
151         int nonsize;
152         struct _auth_obj ao;
153
154         if ((fnl = path_size(tokenid, userid)) == 0) {
155                 ret.err = "authfile path impossible to build";
156                 return ret;
157         }
158         fn = alloca(fnl);
159         make_path(fn, tokenid, userid);
160         nfn = alloca(fnl+32);
161         snprintf(nfn, fnl+32, "%s.%d.%ld", fn, (int)getpid(), (long)time(NULL));
162         fp = fopen(fn, "r");
163         if (fp) {
164                 if (fstat(fileno(fp), &st)) st.st_size = 2047;
165                 if (st.st_size > 2047) st.st_size = 2047;
166                 buf = alloca(st.st_size + 1);
167                 if (!fgets(buf, st.st_size + 1, fp)) {
168                         ret.err = strerror(errno);
169                 } else if (parse(buf, sizeof(w)/sizeof(char*),
170                                         (const char ** const)&w)){
171                         ret.err = "error: unparseable auth file";
172                 }
173                 fclose(fp);
174         }
175         if (ret.err) return ret;
176
177         if (w.hablob) {
178                 blobsize = strlen(w.hablob)*3/4;
179                 ablob = alloca(blobsize);
180                 if (b64_decode(w.hablob, ablob, &blobsize))
181                         ret.err = "error: undecodeable auth string";
182         }
183         if (ret.err) return ret;
184
185         nonsize = w.nonce ? strlen(w.nonce)*2 : 32;
186         if (nonsize < 32) nonsize = 32;
187         newnonce = alloca(nonsize);
188         if (w.nonce) strcpy(newnonce, w.nonce);
189         else memset(newnonce, 0, nonsize);
190         update_nonce(newnonce, nonsize);
191
192         ao = authobj(userid?userid:w.userid, password,
193                         w.nonce, newnonce, secret, secsize,
194                         payload, paylsize, ablob, blobsize,
195                         fetch_key);
196
197         if (ao.err) {
198                 ret.err = ao.err;
199                 if (ao.data) memset(ao.data, 0, ao.datasize);
200                 if (ao.payload) memset(ao.payload, 0, ao.paylsize);
201                 if (ao.buffer) free(ao.buffer);
202                 return ret;
203         }
204
205         oldmask = umask(077);
206         if ((fp = fopen(nfn, "w"))) {
207                 int bsize = ((ao.datasize-1)/3+1)*4+1;
208                 char *b64 = alloca(bsize);
209
210                 if (b64_encode(ao.data, ao.datasize, b64, &bsize)) {
211                         ret.err = "error: could not encode auth string";
212                 } else if (fprintf(fp, "%s:%s:%s:%s\n",
213                                 tokenid?tokenid:w.tokenid,
214                                 userid?userid:w.userid, newnonce, b64) < 0) {
215                         ret.err = strerror(errno);
216                 }
217                 if (st.st_uid || st.st_gid) {
218                         if (fchown(fileno(fp), st.st_uid, st.st_gid)) /*ign*/;
219                 }
220                 if (fclose(fp) < 0) {
221                         ret.err = strerror(errno);
222                 }
223         } else {
224                 ret.err = strerror(errno);
225         }
226         (void)umask(oldmask);
227         if (ret.err) {
228                 unlink(nfn); /* may not exist but no matter */
229         } else if (rename(nfn, fn)) {
230                 ret.err = strerror(errno);
231         }
232
233         if (!ret.err) {
234                 int bufsize = (w.userid?strlen(w.userid)+1:0) + ao.paylsize + 1;
235                 if (bufsize) {
236                         if ((ret.buffer = malloc(bufsize)) == NULL) {
237                                 ret.err = "authfile malloc failed";
238                         } else {
239                                 unsigned char *p = ret.buffer;
240                                 if (w.userid) {
241                                         strcpy((char*)p, w.userid);
242                                         ret.data = p;
243                                         ret.datasize = strlen(w.userid)+1;
244                                         p += strlen(w.userid)+1;
245                                 }
246                                 if (ao.payload) {
247                                         memcpy(p, ao.payload, ao.paylsize);
248                                         p[ao.paylsize] = '\0';
249                                         ret.payload = p;
250                                         ret.paylsize = ao.paylsize+1;
251                                 }
252                         }
253                 }
254         }
255
256         if (ao.data) memset(ao.data, 0, ao.datasize);
257         if (ao.payload) memset(ao.payload, 0, ao.paylsize);
258         if (ao.buffer) free(ao.buffer);
259         return ret;
260 }