1 /***********************************************************
2     Written by:
3     Fred Gansevles <[email protected]>
4     B&O group,
5     Faculteit der Informatica,
6     Universiteit Twente,
7     Enschede,
8     the Netherlands.
9 ******************************************************************/
10 
11 /* NIS module implementation */
12 
13 #include "Python.h"
14 
15 #include <stdlib.h>               // free()
16 #include <sys/time.h>
17 #include <sys/types.h>
18 #include <rpc/rpc.h>
19 #include <rpcsvc/yp_prot.h>
20 #include <rpcsvc/ypclnt.h>
21 
22 #ifdef __sgi
23 /* This is missing from rpcsvc/ypclnt.h */
24 extern int yp_get_default_domain(char **);
25 #endif
26 
27 PyDoc_STRVAR(get_default_domain__doc__,
28 "get_default_domain() -> str\n\
29 Corresponds to the C library yp_get_default_domain() call, returning\n\
30 the default NIS domain.\n");
31 
32 PyDoc_STRVAR(match__doc__,
33 "match(key, map, domain = defaultdomain)\n\
34 Corresponds to the C library yp_match() call, returning the value of\n\
35 key in the given map. Optionally domain can be specified but it\n\
36 defaults to the system default domain.\n");
37 
38 PyDoc_STRVAR(cat__doc__,
39 "cat(map, domain = defaultdomain)\n\
40 Returns the entire map as a dictionary. Optionally domain can be\n\
41 specified but it defaults to the system default domain.\n");
42 
43 PyDoc_STRVAR(maps__doc__,
44 "maps(domain = defaultdomain)\n\
45 Returns an array of all available NIS maps within a domain. If domain\n\
46 is not specified it defaults to the system default domain.\n");
47 
48 typedef struct {
49     PyObject *nis_error;
50 } nis_state;
51 
52 static inline nis_state*
get_nis_state(PyObject * module)53 get_nis_state(PyObject *module)
54 {
55     void *state = PyModule_GetState(module);
56     assert(state != NULL);
57     return (nis_state *)state;
58 }
59 
60 static int
nis_clear(PyObject * m)61 nis_clear(PyObject *m)
62 {
63     Py_CLEAR(get_nis_state(m)->nis_error);
64     return 0;
65 }
66 
67 static int
nis_traverse(PyObject * m,visitproc visit,void * arg)68 nis_traverse(PyObject *m, visitproc visit, void *arg)
69 {
70     Py_VISIT(get_nis_state(m)->nis_error);
71     return 0;
72 }
73 
74 static void
nis_free(void * m)75 nis_free(void *m)
76 {
77     nis_clear((PyObject *) m);
78 }
79 
80 static PyObject *
nis_error(nis_state * state,int err)81 nis_error(nis_state *state, int err)
82 {
83     PyErr_SetString(state->nis_error, yperr_string(err));
84     return NULL;
85 }
86 
87 static struct nis_map {
88     char *alias;
89     char *map;
90     int  fix;
91 } aliases [] = {
92     {"passwd",          "passwd.byname",        0},
93     {"group",           "group.byname",         0},
94     {"networks",        "networks.byaddr",      0},
95     {"hosts",           "hosts.byname",         0},
96     {"protocols",       "protocols.bynumber",   0},
97     {"services",        "services.byname",      0},
98     {"aliases",         "mail.aliases",         1}, /* created with 'makedbm -a' */
99     {"ethers",          "ethers.byname",        0},
100     {0L,                0L,                     0}
101 };
102 
103 static char *
nis_mapname(char * map,int * pfix)104 nis_mapname(char *map, int *pfix)
105 {
106     int i;
107 
108     *pfix = 0;
109     for (i=0; aliases[i].alias != 0L; i++) {
110         if (!strcmp (aliases[i].alias, map) || !strcmp (aliases[i].map, map)) {
111             *pfix = aliases[i].fix;
112             return aliases[i].map;
113         }
114     }
115 
116     return map;
117 }
118 
119 #if defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__)
120 typedef int (*foreachfunc)(unsigned long, char *, int, char *, int, void *);
121 #else
122 typedef int (*foreachfunc)(int, char *, int, char *, int, char *);
123 #endif
124 
125 struct ypcallback_data {
126     PyObject            *dict;
127     int                         fix;
128     PyThreadState *state;
129 };
130 
131 static int
nis_foreach(int instatus,char * inkey,int inkeylen,char * inval,int invallen,struct ypcallback_data * indata)132 nis_foreach(int instatus, char *inkey, int inkeylen, char *inval,
133              int invallen, struct ypcallback_data *indata)
134 {
135     if (instatus == YP_TRUE) {
136         PyObject *key;
137         PyObject *val;
138         int err;
139 
140         PyEval_RestoreThread(indata->state);
141         if (indata->fix) {
142             if (inkeylen > 0 && inkey[inkeylen-1] == '\0')
143             inkeylen--;
144             if (invallen > 0 && inval[invallen-1] == '\0')
145             invallen--;
146         }
147         key = PyUnicode_DecodeFSDefaultAndSize(inkey, inkeylen);
148         val = PyUnicode_DecodeFSDefaultAndSize(inval, invallen);
149         if (key == NULL || val == NULL) {
150             /* XXX error -- don't know how to handle */
151             PyErr_Clear();
152             Py_XDECREF(key);
153             Py_XDECREF(val);
154             indata->state = PyEval_SaveThread();
155             return 1;
156         }
157         err = PyDict_SetItem(indata->dict, key, val);
158         Py_DECREF(key);
159         Py_DECREF(val);
160         if (err != 0)
161             PyErr_Clear();
162         indata->state = PyEval_SaveThread();
163         if (err != 0)
164             return 1;
165         return 0;
166     }
167     return 1;
168 }
169 
170 static PyObject *
nis_get_default_domain(PyObject * module,PyObject * Py_UNUSED (ignored))171 nis_get_default_domain(PyObject *module, PyObject *Py_UNUSED(ignored))
172 {
173     char *domain;
174     int err;
175     PyObject *res;
176     nis_state *state = get_nis_state(module);
177     if ((err = yp_get_default_domain(&domain)) != 0) {
178         return nis_error(state, err);
179     }
180 
181     res = PyUnicode_FromStringAndSize (domain, strlen(domain));
182     return res;
183 }
184 
185 static PyObject *
nis_match(PyObject * module,PyObject * args,PyObject * kwdict)186 nis_match(PyObject *module, PyObject *args, PyObject *kwdict)
187 {
188     char *match;
189     char *domain = NULL;
190     Py_ssize_t keylen;
191     int len;
192     char *key, *map;
193     int err;
194     PyObject *ukey, *bkey, *res;
195     int fix;
196     static char *kwlist[] = {"key", "map", "domain", NULL};
197 
198     if (!PyArg_ParseTupleAndKeywords(args, kwdict,
199                                      "Us|s:match", kwlist,
200                                      &ukey, &map, &domain)) {
201         return NULL;
202     }
203     if ((bkey = PyUnicode_EncodeFSDefault(ukey)) == NULL) {
204         return NULL;
205     }
206     /* check for embedded null bytes */
207     if (PyBytes_AsStringAndSize(bkey, &key, &keylen) == -1) {
208         Py_DECREF(bkey);
209         return NULL;
210     }
211 
212     nis_state *state = get_nis_state(module);
213     if (!domain && ((err = yp_get_default_domain(&domain)) != 0)) {
214         Py_DECREF(bkey);
215         return nis_error(state, err);
216     }
217     map = nis_mapname (map, &fix);
218     if (fix)
219         keylen++;
220     Py_BEGIN_ALLOW_THREADS
221     err = yp_match (domain, map, key, keylen, &match, &len);
222     Py_END_ALLOW_THREADS
223     Py_DECREF(bkey);
224     if (fix)
225         len--;
226     if (err != 0) {
227         return nis_error(state, err);
228     }
229     res = PyUnicode_DecodeFSDefaultAndSize(match, len);
230     free (match);
231     return res;
232 }
233 
234 static PyObject *
nis_cat(PyObject * module,PyObject * args,PyObject * kwdict)235 nis_cat(PyObject *module, PyObject *args, PyObject *kwdict)
236 {
237     char *domain = NULL;
238     char *map;
239     struct ypall_callback cb;
240     struct ypcallback_data data;
241     PyObject *dict;
242     int err;
243     static char *kwlist[] = {"map", "domain", NULL};
244 
245     if (!PyArg_ParseTupleAndKeywords(args, kwdict, "s|s:cat",
246                                      kwlist, &map, &domain)) {
247         return NULL;
248     }
249     nis_state *state = get_nis_state(module);
250     if (!domain && ((err = yp_get_default_domain(&domain)) != 0)) {
251         return nis_error(state, err);
252     }
253     dict = PyDict_New ();
254     if (dict == NULL)
255         return NULL;
256     cb.foreach = (foreachfunc)nis_foreach;
257     data.dict = dict;
258     map = nis_mapname (map, &data.fix);
259     cb.data = (char *)&data;
260     data.state = PyEval_SaveThread();
261     err = yp_all (domain, map, &cb);
262     PyEval_RestoreThread(data.state);
263     if (err != 0) {
264         Py_DECREF(dict);
265         return nis_error(state, err);
266     }
267     return dict;
268 }
269 
270 /* These should be u_long on Sun h/w but not on 64-bit h/w.
271    This is not portable to machines with 16-bit ints and no prototypes */
272 #ifndef YPPROC_MAPLIST
273 #define YPPROC_MAPLIST  11
274 #endif
275 #ifndef YPPROG
276 #define YPPROG          100004
277 #endif
278 #ifndef YPVERS
279 #define YPVERS          2
280 #endif
281 
282 typedef char *domainname;
283 typedef char *mapname;
284 
285 enum nisstat {
286     NIS_TRUE = 1,
287     NIS_NOMORE = 2,
288     NIS_FALSE = 0,
289     NIS_NOMAP = -1,
290     NIS_NODOM = -2,
291     NIS_NOKEY = -3,
292     NIS_BADOP = -4,
293     NIS_BADDB = -5,
294     NIS_YPERR = -6,
295     NIS_BADARGS = -7,
296     NIS_VERS = -8
297 };
298 typedef enum nisstat nisstat;
299 
300 struct nismaplist {
301     mapname map;
302     struct nismaplist *next;
303 };
304 typedef struct nismaplist nismaplist;
305 
306 struct nisresp_maplist {
307     nisstat stat;
308     nismaplist *maps;
309 };
310 typedef struct nisresp_maplist nisresp_maplist;
311 
312 static struct timeval TIMEOUT = { 25, 0 };
313 
314 static
315 bool_t
nis_xdr_domainname(XDR * xdrs,domainname * objp)316 nis_xdr_domainname(XDR *xdrs, domainname *objp)
317 {
318     if (!xdr_string(xdrs, objp, YPMAXDOMAIN)) {
319         return (FALSE);
320     }
321     return (TRUE);
322 }
323 
324 static
325 bool_t
nis_xdr_mapname(XDR * xdrs,mapname * objp)326 nis_xdr_mapname(XDR *xdrs, mapname *objp)
327 {
328     if (!xdr_string(xdrs, objp, YPMAXMAP)) {
329         return (FALSE);
330     }
331     return (TRUE);
332 }
333 
334 static
335 bool_t
nis_xdr_ypmaplist(XDR * xdrs,nismaplist * objp)336 nis_xdr_ypmaplist(XDR *xdrs, nismaplist *objp)
337 {
338     if (!nis_xdr_mapname(xdrs, &objp->map)) {
339         return (FALSE);
340     }
341     if (!xdr_pointer(xdrs, (char **)&objp->next,
342                      sizeof(nismaplist), (xdrproc_t)nis_xdr_ypmaplist))
343     {
344         return (FALSE);
345     }
346     return (TRUE);
347 }
348 
349 static
350 bool_t
nis_xdr_ypstat(XDR * xdrs,nisstat * objp)351 nis_xdr_ypstat(XDR *xdrs, nisstat *objp)
352 {
353     if (!xdr_enum(xdrs, (enum_t *)objp)) {
354         return (FALSE);
355     }
356     return (TRUE);
357 }
358 
359 
360 static
361 bool_t
nis_xdr_ypresp_maplist(XDR * xdrs,nisresp_maplist * objp)362 nis_xdr_ypresp_maplist(XDR *xdrs, nisresp_maplist *objp)
363 {
364     if (!nis_xdr_ypstat(xdrs, &objp->stat)) {
365         return (FALSE);
366     }
367     if (!xdr_pointer(xdrs, (char **)&objp->maps,
368                      sizeof(nismaplist), (xdrproc_t)nis_xdr_ypmaplist))
369     {
370         return (FALSE);
371     }
372     return (TRUE);
373 }
374 
375 
376 static
377 nisresp_maplist *
nisproc_maplist_2(domainname * argp,CLIENT * clnt)378 nisproc_maplist_2(domainname *argp, CLIENT *clnt)
379 {
380     static nisresp_maplist res;
381 
382     memset(&res, 0, sizeof(res));
383     if (clnt_call(clnt, YPPROC_MAPLIST,
384                   (xdrproc_t)nis_xdr_domainname, (caddr_t)argp,
385                   (xdrproc_t)nis_xdr_ypresp_maplist, (caddr_t)&res,
386                   TIMEOUT) != RPC_SUCCESS)
387     {
388         return (NULL);
389     }
390     return (&res);
391 }
392 
393 static
394 nismaplist *
nis_maplist(nis_state * state,char * dom)395 nis_maplist(nis_state *state, char *dom)
396 {
397     nisresp_maplist *list;
398     CLIENT *cl;
399     char *server = NULL;
400     int mapi = 0;
401 
402     while (!server && aliases[mapi].map != 0L) {
403         yp_master (dom, aliases[mapi].map, &server);
404         mapi++;
405     }
406     if (!server) {
407         PyErr_SetString(state->nis_error, "No NIS master found for any map");
408         return NULL;
409     }
410     cl = clnt_create(server, YPPROG, YPVERS, "tcp");
411     if (cl == NULL) {
412         PyErr_SetString(state->nis_error, clnt_spcreateerror(server));
413         goto finally;
414     }
415     list = nisproc_maplist_2 (&dom, cl);
416     clnt_destroy(cl);
417     if (list == NULL)
418         goto finally;
419     if (list->stat != NIS_TRUE)
420         goto finally;
421 
422     free(server);
423     return list->maps;
424 
425   finally:
426     free(server);
427     return NULL;
428 }
429 
430 static PyObject *
nis_maps(PyObject * module,PyObject * args,PyObject * kwdict)431 nis_maps (PyObject *module, PyObject *args, PyObject *kwdict)
432 {
433     char *domain = NULL;
434     nismaplist *maps;
435     PyObject *list;
436     int err;
437     static char *kwlist[] = {"domain", NULL};
438 
439     if (!PyArg_ParseTupleAndKeywords(args, kwdict,
440                                      "|s:maps", kwlist, &domain)) {
441         return NULL;
442     }
443 
444     nis_state *state = get_nis_state(module);
445     if (!domain && ((err = yp_get_default_domain (&domain)) != 0)) {
446         nis_error(state, err);
447         return NULL;
448     }
449 
450     if ((maps = nis_maplist(state, domain)) == NULL) {
451         return NULL;
452     }
453     if ((list = PyList_New(0)) == NULL) {
454         return NULL;
455     }
456     for (; maps; maps = maps->next) {
457         PyObject *str = PyUnicode_FromString(maps->map);
458         if (!str || PyList_Append(list, str) < 0)
459         {
460             Py_XDECREF(str);
461             Py_DECREF(list);
462             list = NULL;
463             break;
464         }
465         Py_DECREF(str);
466     }
467     /* XXX Shouldn't we free the list of maps now? */
468     return list;
469 }
470 
471 static PyMethodDef nis_methods[] = {
472     {"match",                   _PyCFunction_CAST(nis_match),
473                                     METH_VARARGS | METH_KEYWORDS,
474                                     match__doc__},
475     {"cat",                     _PyCFunction_CAST(nis_cat),
476                                     METH_VARARGS | METH_KEYWORDS,
477                                     cat__doc__},
478     {"maps",                    _PyCFunction_CAST(nis_maps),
479                                     METH_VARARGS | METH_KEYWORDS,
480                                     maps__doc__},
481     {"get_default_domain",      nis_get_default_domain,
482                                     METH_NOARGS,
483                                     get_default_domain__doc__},
484     {NULL,                      NULL}            /* Sentinel */
485 };
486 
487 static int
nis_exec(PyObject * module)488 nis_exec(PyObject *module)
489 {
490     nis_state* state = get_nis_state(module);
491     state->nis_error = PyErr_NewException("nis.error", NULL, NULL);
492     if (state->nis_error == NULL) {
493         return -1;
494     }
495 
496     Py_INCREF(state->nis_error);
497     if (PyModule_AddObject(module, "error", state->nis_error) < 0) {
498         Py_DECREF(state->nis_error);
499         return -1;
500     }
501     return 0;
502 }
503 
504 static PyModuleDef_Slot nis_slots[] = {
505     {Py_mod_exec, nis_exec},
506     {0, NULL}
507 };
508 
509 PyDoc_STRVAR(nis__doc__,
510 "This module contains functions for accessing NIS maps.\n");
511 
512 static struct PyModuleDef nismodule = {
513     PyModuleDef_HEAD_INIT,
514     .m_name = "nis",
515     .m_doc = nis__doc__,
516     .m_size = sizeof(nis_state),
517     .m_methods = nis_methods,
518     .m_traverse = nis_traverse,
519     .m_clear = nis_clear,
520     .m_free = nis_free,
521     .m_slots = nis_slots,
522 };
523 
524 PyMODINIT_FUNC
PyInit_nis(void)525 PyInit_nis(void)
526 {
527     if (PyErr_WarnEx(PyExc_DeprecationWarning,
528                      "'nis' is deprecated and slated for removal in "
529                      "Python 3.13",
530                      7)) {
531         return NULL;
532     }
533     return PyModuleDef_Init(&nismodule);
534 }
535