source: src/router/proftpd/src/modules.c @ 14672

Last change on this file since 14672 was 14672, checked in by BrainSlayer, 3 years ago

proftp update

File size: 19.0 KB
Line 
1/*
2 * ProFTPD - FTP server daemon
3 * Copyright (c) 1997, 1998 Public Flood Software
4 * Copyright (c) 2001-2008 The ProFTPD Project team
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
19 *
20 * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
21 * and other respective copyright holders give permission to link this program
22 * with OpenSSL, and distribute the resulting executable, without including
23 * the source code for OpenSSL in the source distribution.
24 */
25
26/*
27 * Module handling routines
28 * $Id: modules.c,v 1.58 2010/02/14 00:19:06 castaglia Exp $
29 */
30
31#include "conf.h"
32
33/* This local structure vastly speeds up symbol lookups. */
34struct stash {
35  struct stash *next,*prev;
36  pool *sym_pool;
37  const char *sym_name;
38  pr_stash_type_t sym_type;
39  module *sym_module;
40
41  union {
42    conftable *sym_conf;
43    cmdtable *sym_cmd;
44    authtable *sym_auth;
45    cmdtable *sym_hook;
46    void *sym_generic;
47  } ptr;
48};
49
50extern module *static_modules[];
51extern module *loaded_modules;
52
53/* Symbol hashes for each type */
54static xaset_t *symbol_table[PR_TUNABLE_HASH_TABLE_SIZE];
55static pool *symbol_pool = NULL;
56static struct stash *curr_sym = NULL;
57
58/* Currently running module */
59module *curr_module = NULL;
60
61/* Used to track the priority for loaded modules. */
62static unsigned int curr_module_pri = 0;
63
64typedef struct mod_cb {
65  struct mod_cb *next, *prev;
66
67  int (*module_cb)(void);
68} module_cb_t;
69
70/* Symbol stash lookup code and management */
71
72static struct stash *sym_alloc(void) {
73  pool *sub_pool;
74  struct stash *sym;
75
76  /* XXX Use a smaller pool size, since there are lots of sub-pools allocated
77   * for Stash symbols.  The default pool size (PR_TUNABLE_POOL_SIZE, 512
78   * by default) is a bit large for symbols.
79   */
80  sub_pool = pr_pool_create_sz(symbol_pool, 128);
81
82  sym = pcalloc(sub_pool, sizeof(struct stash));
83  sym->sym_pool = sub_pool;
84  pr_pool_tag(sub_pool, "symbol");
85
86  return sym;
87}
88
89static int sym_cmp(struct stash *s1, struct stash *s2) {
90  int res;
91
92  res = strcmp(s1->sym_name, s2->sym_name);
93
94  /* Higher priority modules must go BEFORE lower priority in the
95   * hash tables.
96   */
97
98  if (res == 0) {
99    if (s1->sym_module != NULL &&
100        s2->sym_module != NULL) {
101
102      if (s1->sym_module->priority > s2->sym_module->priority) {
103        return -1;
104      }
105   
106      if (s1->sym_module->priority < s2->sym_module->priority) {
107        return 1;
108      }
109
110      return res;
111    }
112
113    if (s1->sym_module != NULL &&
114        s2->sym_module == NULL) {
115      return -1;
116    }
117
118    if (s1->sym_module == NULL &&
119        s2->sym_module != NULL) {
120      return 1;
121    }
122
123    /* Both sym_module fields are null. */
124    return 0;
125  }
126
127  return res;
128}
129
130static int symtab_hash(const char *name) {
131  unsigned char *cp = NULL;
132  int total = 0;
133
134  if (!name)
135    return 0;
136
137  for (cp = (unsigned char *)name; *cp; cp++)
138    total += (int)*cp;
139
140  return (total < PR_TUNABLE_HASH_TABLE_SIZE ? total :
141    (total % PR_TUNABLE_HASH_TABLE_SIZE));
142}
143
144int pr_stash_add_symbol(pr_stash_type_t sym_type, void *data) {
145  struct stash *sym = NULL;
146  int idx = 0;
147
148  if (!data) {
149    errno = EINVAL;
150    return -1;
151  }
152
153  switch (sym_type) {
154    case PR_SYM_CONF:
155      sym = sym_alloc();
156      sym->sym_type = PR_SYM_CONF;
157      sym->sym_name = ((conftable *) data)->directive;
158      sym->sym_module = ((conftable *) data)->m;
159      sym->ptr.sym_conf = data;
160      break;
161
162    case PR_SYM_CMD:
163      sym = sym_alloc();
164      sym->sym_type = PR_SYM_CMD;
165      sym->sym_name = ((cmdtable *) data)->command;
166      sym->sym_module = ((cmdtable *) data)->m;
167      sym->ptr.sym_cmd = data;
168      break;
169
170    case PR_SYM_AUTH:
171      sym = sym_alloc();
172      sym->sym_type = PR_SYM_AUTH;
173      sym->sym_name = ((authtable *) data)->name;
174      sym->sym_module = ((authtable *) data)->m;
175      sym->ptr.sym_auth = data;
176      break;
177
178    case PR_SYM_HOOK:
179      sym = sym_alloc();
180      sym->sym_type = PR_SYM_HOOK;
181      sym->sym_name = ((cmdtable *) data)->command;
182      sym->sym_module = ((cmdtable *) data)->m;
183      sym->ptr.sym_hook = data;
184      break;
185
186    default:
187      errno = ENOENT;
188      return -1;
189  }
190
191  /* XXX Should we check for null sym->sym_module as well? */
192  if (sym->sym_name == NULL) {
193    destroy_pool(sym->sym_pool);
194    errno = EPERM;
195    return -1;
196  }
197
198  /* XXX Ugly hack to support mixed cases of directives in config files. */
199  if (sym_type != PR_SYM_CONF) {
200    idx = symtab_hash(sym->sym_name);
201
202  } else {
203    register unsigned int i;
204    char buf[1024];
205
206    memset(buf, '\0', sizeof(buf));
207    sstrncpy(buf, sym->sym_name, sizeof(buf)-1);
208
209    for (i = 0; i < strlen(buf); i++)
210      buf[i] = tolower((int) buf[i]);
211
212    idx = symtab_hash(buf);
213  }
214
215  if (!symbol_table[idx]) {
216    symbol_table[idx] = xaset_create(symbol_pool, (XASET_COMPARE) sym_cmp);
217  }
218
219  xaset_insert_sort(symbol_table[idx], (xasetmember_t *) sym, TRUE);
220  return 0;
221}
222
223static struct stash *stash_lookup(pr_stash_type_t sym_type,
224    const char *name, int idx) {
225  struct stash *sym = NULL;
226
227  if (symbol_table[idx]) {
228    for (sym = (struct stash *) symbol_table[idx]->xas_list; sym;
229        sym = sym->next)
230      if (sym->sym_type == sym_type &&
231          (!name || strcasecmp(sym->sym_name, name) == 0))
232        break;
233  }
234
235  return sym;
236}
237
238static struct stash *stash_lookup_next(pr_stash_type_t sym_type,
239    const char *name, int idx, void *prev) {
240  struct stash *sym = NULL;
241  int last_hit = 0;
242
243  if (symbol_table[idx]) {
244    for (sym = (struct stash *) symbol_table[idx]->xas_list; sym;
245        sym = sym->next) {
246      if (last_hit && sym->sym_type == sym_type &&
247          (!name || strcasecmp(sym->sym_name, name) == 0))
248        break;
249      if (sym->ptr.sym_generic == prev)
250        last_hit++;
251    }
252  }
253
254  return sym;
255}
256
257void *pr_stash_get_symbol(pr_stash_type_t sym_type, const char *name,
258    void *prev, int *idx_cache) {
259  int idx;
260  struct stash *sym = NULL;
261
262  if (idx_cache &&
263      *idx_cache != -1) {
264    idx = *idx_cache;
265
266  } else {
267
268    /* XXX Ugly hack to support mixed cases of directives in config files. */
269    if (sym_type != PR_SYM_CONF) {
270      idx = symtab_hash(name);
271
272    } else {
273      register unsigned int i;
274      char buf[1024];
275
276      memset(buf, '\0', sizeof(buf));
277      sstrncpy(buf, name, sizeof(buf)-1);
278
279      for (i = 0; i < strlen(buf); i++)
280        buf[i] = tolower((int) buf[i]);
281
282      idx = symtab_hash(buf);
283    }
284
285    if (idx_cache)
286      *idx_cache = idx;
287  }
288
289  if (idx >= PR_TUNABLE_HASH_TABLE_SIZE) {
290    if (*idx_cache)
291      *idx_cache = -1;
292
293    errno = EINVAL;
294    return NULL;
295  }
296
297  if (prev)
298    curr_sym = sym = stash_lookup_next(sym_type, name, idx, prev);
299  else
300    curr_sym = sym = stash_lookup(sym_type, name, idx);
301
302  switch (sym_type) {
303    case PR_SYM_CONF:
304      if (sym) {
305        return sym->ptr.sym_conf;
306      }
307
308      errno = ENOENT;
309      return NULL;
310
311    case PR_SYM_CMD:
312      if (sym) {
313        return sym->ptr.sym_cmd;
314      }
315
316      errno = ENOENT;
317      return NULL;
318
319    case PR_SYM_AUTH:
320      if (sym) {
321        return sym->ptr.sym_auth;
322      }
323
324      errno = ENOENT;
325      return NULL;
326
327    case PR_SYM_HOOK:
328      if (sym) {
329        return sym->ptr.sym_hook;
330      }
331
332      errno = ENOENT;
333      return NULL;
334  }
335
336  errno = EINVAL;
337  return NULL;
338}
339
340int pr_stash_remove_symbol(pr_stash_type_t sym_type, const char *sym_name,
341    module *sym_module) {
342  int count = 0, symtab_idx = 0;
343
344  if (!sym_name) {
345    errno = EINVAL;
346    return -1;
347  }
348
349  /* XXX Ugly hack to support mixed cases of directives in config files. */
350  if (sym_type != PR_SYM_CONF) {
351    symtab_idx = symtab_hash(sym_name);
352
353  } else {
354    register unsigned int i;
355    char buf[1024];
356
357    memset(buf, '\0', sizeof(buf));
358    sstrncpy(buf, sym_name, sizeof(buf)-1);
359
360    for (i = 0; i < strlen(buf); i++)
361      buf[i] = tolower((int) buf[i]);
362
363    symtab_idx = symtab_hash(buf);
364  }
365
366  switch (sym_type) {
367    case PR_SYM_CONF: {
368      int idx = -1;
369      conftable *tab;
370
371      tab = pr_stash_get_symbol(PR_SYM_CONF, sym_name, NULL, &idx);
372
373      while (tab) {
374        pr_signals_handle();
375
376        /* Note: this works because of a hack: the symbol lookup functions
377         * set a static pointer, curr_sym, to point to the struct stash
378         * just looked up.  curr_sym will not be NULL if pr_stash_get_symbol()
379         * returns non-NULL.
380         */
381
382        if (!sym_module ||
383            curr_sym->sym_module == sym_module) {
384          xaset_remove(symbol_table[symtab_idx], (xasetmember_t *) curr_sym);
385          destroy_pool(curr_sym->sym_pool);
386          curr_sym = NULL;
387          tab = NULL;
388          count++;
389        }
390
391        tab = pr_stash_get_symbol(PR_SYM_CONF, sym_name, tab, &idx);
392      }
393
394      break;
395    }
396
397    case PR_SYM_CMD: {
398      int idx = -1;
399      cmdtable *tab;
400
401      tab = pr_stash_get_symbol(PR_SYM_CMD, sym_name, NULL, &idx);
402
403      while (tab) {
404        pr_signals_handle();
405
406        /* Note: this works because of a hack: the symbol lookup functions
407         * set a static pointer, curr_sym, to point to the struct stash
408         * just looked up. 
409         */
410
411        if (!sym_module ||
412            curr_sym->sym_module == sym_module) {
413          xaset_remove(symbol_table[symtab_idx], (xasetmember_t *) curr_sym);
414          destroy_pool(curr_sym->sym_pool);
415          tab = NULL;
416          count++;
417        }
418
419        tab = pr_stash_get_symbol(PR_SYM_CMD, sym_name, tab, &idx);
420      }
421
422      break;
423    }
424
425    case PR_SYM_AUTH: {
426      int idx = -1;
427      authtable *tab;
428
429      tab = pr_stash_get_symbol(PR_SYM_AUTH, sym_name, NULL, &idx);
430
431      while (tab) {
432        pr_signals_handle();
433
434        /* Note: this works because of a hack: the symbol lookup functions
435         * set a static pointer, curr_sym, to point to the struct stash
436         * just looked up. 
437         */
438
439        if (!sym_module ||
440            curr_sym->sym_module == sym_module) {
441          xaset_remove(symbol_table[symtab_idx], (xasetmember_t *) curr_sym);
442          destroy_pool(curr_sym->sym_pool);
443          tab = NULL;
444          count++;
445        }
446
447        tab = pr_stash_get_symbol(PR_SYM_AUTH, sym_name, tab, &idx);
448      }
449
450      break;
451    }
452
453    case PR_SYM_HOOK: {
454      int idx = -1;
455      cmdtable *tab;
456
457      tab = pr_stash_get_symbol(PR_SYM_HOOK, sym_name, NULL, &idx);
458
459      while (tab) {
460        pr_signals_handle();
461
462        if (!sym_module ||
463            curr_sym->sym_module == sym_module) {
464          xaset_remove(symbol_table[symtab_idx], (xasetmember_t *) curr_sym);
465          destroy_pool(curr_sym->sym_pool);
466          tab = NULL;
467          count++;
468        }
469
470        tab = pr_stash_get_symbol(PR_SYM_HOOK, sym_name, tab, &idx);
471      }
472
473      break;
474    }
475
476    default:
477      errno = EINVAL;
478      return -1;
479  }
480
481  return count;
482}
483
484modret_t *pr_module_call(module *m, modret_t *(*func)(cmd_rec *),
485    cmd_rec *cmd) {
486  modret_t *res;
487  module *prev_module = curr_module;
488
489  if (m == NULL ||
490      func == NULL ||
491      cmd == NULL) {
492    errno = EINVAL;
493    return NULL;
494  }
495
496  if (!cmd->tmp_pool) {
497    cmd->tmp_pool = make_sub_pool(cmd->pool);
498    pr_pool_tag(cmd->tmp_pool, "Module call tmp_pool");
499  }
500
501  curr_module = m;
502  res = func(cmd);
503  curr_module = prev_module;
504
505  /* Note that we don't clear the pool here because the function may
506   * return data which resides in this pool.
507   */
508  return res;
509}
510
511modret_t *mod_create_data(cmd_rec *cmd, void *d) {
512  modret_t *res;
513
514  res = pcalloc(cmd->tmp_pool, sizeof(modret_t));
515  res->data = d;
516
517  return res;
518}
519
520modret_t *mod_create_ret(cmd_rec *cmd, unsigned char err, char *n, char *m) {
521  modret_t *res;
522
523  res = pcalloc(cmd->tmp_pool, sizeof(modret_t));
524  res->mr_handler_module = curr_module;
525  res->mr_error = err;
526
527  if (n) {
528    res->mr_numeric = pstrdup(cmd->tmp_pool, n);
529  }
530
531  if (m) {
532    res->mr_message = pstrdup(cmd->tmp_pool, m);
533  }
534
535  return res;
536}
537
538modret_t *mod_create_error(cmd_rec *cmd, int mr_errno) {
539  modret_t *res;
540
541  res = pcalloc(cmd->tmp_pool, sizeof(modret_t));
542  res->mr_handler_module = curr_module;
543  res->mr_error = mr_errno;
544
545  return res;
546}
547
548/* Called after forking in order to inform/initialize modules
549 * need to know we are a child and have a connection.
550 */
551int modules_session_init(void) {
552  module *prev_module = curr_module, *m;
553
554  for (m = loaded_modules; m; m = m->next) {
555    if (m && m->sess_init) {
556      curr_module = m;
557      if (m->sess_init() < 0) {
558        pr_log_pri(PR_LOG_ERR, "mod_%s.c: error initializing session: %s",
559          m->name, strerror(errno));
560        return -1;
561      }
562    }
563  }
564
565  curr_module = prev_module;
566  return 0;
567}
568
569unsigned char command_exists(char *name) {
570  int idx = -1;
571  cmdtable *cmdtab = pr_stash_get_symbol(PR_SYM_CMD, name, NULL, &idx);
572
573  while (cmdtab && cmdtab->cmd_type != CMD) {
574    pr_signals_handle();
575    cmdtab = pr_stash_get_symbol(PR_SYM_CMD, name, cmdtab, &idx);
576  }
577
578  return (cmdtab ? TRUE : FALSE);
579}
580
581unsigned char pr_module_exists(const char *name) {
582  return pr_module_get(name) != NULL ? TRUE : FALSE;
583}
584
585module *pr_module_get(const char *name) {
586  char buf[80] = {'\0'};
587  module *m;
588
589  if (!name) {
590    errno = EINVAL;
591    return NULL;
592  }
593
594  /* Check the list of compiled-in modules. */
595  for (m = loaded_modules; m; m = m->next) {
596    memset(buf, '\0', sizeof(buf));
597    snprintf(buf, sizeof(buf), "mod_%s.c", m->name);
598    buf[sizeof(buf)-1] = '\0';
599
600    if (strcmp(buf, name) == 0)
601      return m;
602  }
603
604  errno = ENOENT;
605  return NULL;
606}
607
608void modules_list(int flags) {
609
610  if (flags & PR_MODULES_LIST_FL_SHOW_STATIC) {
611    register unsigned int i = 0;
612
613    printf("Compiled-in modules:\n");
614    for (i = 0; static_modules[i]; i++) {
615      module *m = static_modules[i];
616
617      if (flags & PR_MODULES_LIST_FL_SHOW_VERSION) {
618        char *version = m->module_version;
619        if (version) {
620          printf("  %s\n", version);
621
622        } else {
623          printf("  mod_%s.c\n", m->name);
624        }
625
626      } else {
627        printf("  mod_%s.c\n", m->name);
628      }
629    }
630
631  } else {
632    module *m;
633
634    printf("Loaded modules:\n");
635    for (m = loaded_modules; m; m = m->next) {
636
637      if (flags & PR_MODULES_LIST_FL_SHOW_VERSION) {
638        char *version = m->module_version;
639        if (version) {
640          printf("  %s\n", version);
641
642        } else { 
643          printf("  mod_%s.c\n", m->name);
644        }
645
646      } else {
647        printf("  mod_%s.c\n", m->name);
648      }
649    }
650  }
651}
652
653int pr_module_load(module *m) {
654  char buf[256];
655
656  if (m == NULL ||
657      m->name == NULL) {
658    errno = EINVAL;
659    return -1;
660  }
661
662  /* Check the API version the module wants to use. */
663  if (m->api_version < PR_MODULE_API_VERSION) {
664    errno = EACCES;
665    return -1;
666  }
667
668  /* Do not allow multiple modules with the same name. */
669  memset(buf, '\0', sizeof(buf));
670  snprintf(buf, sizeof(buf), "mod_%s.c", m->name);
671  buf[sizeof(buf)-1] = '\0';
672
673  if (pr_module_get(buf) != NULL) {
674    errno = EEXIST;
675    return -1;
676  }
677
678  /* Invoke the module's initialization routine. */
679  if (!m->init ||
680      m->init() >= 0) {
681
682    /* Assign a priority to this module. */
683    m->priority = curr_module_pri++;
684
685    /* Add the module's config, cmd, and auth tables. */
686    if (m->conftable) {
687      conftable *conftab;
688
689      for (conftab = m->conftable; conftab->directive; conftab++) {
690        conftab->m = m;
691
692        if (pr_stash_add_symbol(PR_SYM_CONF, conftab) < 0) {
693          return -1;
694        }
695      }
696    }
697
698    if (m->cmdtable) {
699      cmdtable *cmdtab;
700
701      for (cmdtab = m->cmdtable; cmdtab->command; cmdtab++) {
702        cmdtab->m = m;
703
704        if (cmdtab->cmd_type == HOOK) {
705          if (pr_stash_add_symbol(PR_SYM_HOOK, cmdtab) < 0) {
706            return -1;
707          }
708
709        } else {
710          /* All other cmd_types are for CMDs: PRE_CMD, CMD, POST_CMD, etc. */
711          if (pr_stash_add_symbol(PR_SYM_CMD, cmdtab) < 0) {
712            return -1;
713          }
714        }
715      }
716    }
717
718    if (m->authtable) {
719      authtable *authtab;
720
721      for (authtab = m->authtable; authtab->name; authtab++) {
722        authtab->m = m;
723
724        if (pr_stash_add_symbol(PR_SYM_AUTH, authtab) < 0) {
725          return -1;
726        }
727      }
728    }
729
730    /* Add the module to the loaded_modules list. */
731    if (loaded_modules) {
732      m->next = loaded_modules;
733      loaded_modules->prev = m;
734    }
735
736    loaded_modules = m;
737
738    /* Generate an event. */
739    pr_event_generate("core.module-load", buf);
740    return 0;
741  }
742
743  errno = EPERM;
744  return -1;
745}
746
747int pr_module_unload(module *m) {
748  char buf[256];
749
750  if (m == NULL ||
751      m->name == NULL) {
752    errno = EINVAL;
753    return -1;
754  }
755
756  /* Make sure this module has been loaded.  We can't unload a module that
757   * has not been loaded, now can we?
758   */
759
760  memset(buf, '\0', sizeof(buf));
761  snprintf(buf, sizeof(buf), "mod_%s.c", m->name);
762  buf[sizeof(buf)-1] = '\0';
763
764  if (pr_module_get(buf) == NULL) {
765    errno = ENOENT;
766    return -1;
767  }
768
769  /* Generate an event. */
770  pr_event_generate("core.module-unload", buf);
771
772  /* Remove the module from the loaded_modules list. */
773  if (m->prev) {
774    m->prev->next = m->next;
775
776  } else {
777    /* This module is the start of the loaded_modules list (prev is NULL),
778     * so we need to update that pointer, too.
779     */
780    loaded_modules = m->next;
781  }
782
783  if (m->next)
784    m->next->prev = m->prev;
785
786  m->prev = m->next = NULL;
787
788  /* Remove the module's config, cmd, and auth tables. */
789  if (m->conftable) {
790    conftable *conftab;
791
792    for (conftab = m->conftable; conftab->directive; conftab++) {
793      pr_stash_remove_symbol(PR_SYM_CONF, conftab->directive, conftab->m);
794    }
795  }
796
797  if (m->cmdtable) {
798    cmdtable *cmdtab;
799
800    for (cmdtab = m->cmdtable; cmdtab->command; cmdtab++) {
801      if (cmdtab->cmd_type == HOOK) {
802        pr_stash_remove_symbol(PR_SYM_HOOK, cmdtab->command, cmdtab->m);
803
804      } else {
805        /* All other cmd_types are for CMDs: PRE_CMD, CMD, POST_CMD, etc. */
806        pr_stash_remove_symbol(PR_SYM_CMD, cmdtab->command, cmdtab->m);
807      }
808    }
809  }
810
811  if (m->authtable) {
812    authtable *authtab;
813
814    for (authtab = m->authtable; authtab->name; authtab++) {
815      pr_stash_remove_symbol(PR_SYM_AUTH, authtab->name, authtab->m);
816    }
817  }
818
819  return 0;
820}
821
822int modules_init(void) {
823  register unsigned int i = 0;
824
825  for (i = 0; static_modules[i]; i++) {
826    module *m = static_modules[i];
827
828    if (pr_module_load(m) < 0) {
829      pr_log_pri(PR_LOG_ERR, "Fatal: unable to load module 'mod_%s.c': %s",
830        m->name, strerror(errno));
831      exit(1);
832    }
833  }
834
835  return 0;
836}
837
838int init_stash(void) {
839  if (symbol_pool)
840    destroy_pool(symbol_pool);
841
842  symbol_pool = make_sub_pool(permanent_pool);
843  pr_pool_tag(symbol_pool, "Stash Pool");
844  memset(symbol_table, '\0', sizeof(symbol_table));
845
846  return 0;
847}
Note: See TracBrowser for help on using the repository browser.